You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eagle.apache.org by hd...@apache.org on 2016/02/26 22:26:19 UTC

[1/9] incubator-eagle git commit: Eagle -168 - AlertEmail Initialization Issue

Repository: incubator-eagle
Updated Branches:
  refs/heads/branch-0.3.0 f9a921157 -> 1965b4aff


Eagle -168 - AlertEmail Initialization Issue

https://issues.apache.org/jira/browse/EAGLE-168

1. Fix the alert email initialization issue
2. Not to exclude the zookeeper jar from the assembly

Author: Kumar, Senthil
Reviewer: @yonzhang, @ralphsu

Closes #101


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

Branch: refs/heads/branch-0.3.0
Commit: eb0734b6f1eb0d868f7d8d39d5e543a11d87d0bb
Parents: f9a9211
Author: Ralph, Su <su...@gmail.com>
Authored: Tue Feb 23 09:58:14 2016 +0800
Committer: Ralph, Su <su...@gmail.com>
Committed: Tue Feb 23 09:58:14 2016 +0800

----------------------------------------------------------------------
 .../org/apache/eagle/notification/plugin/AlertEmailPlugin.java | 6 ++++--
 .../src/assembly/eagle-topology-assembly.xml                   | 1 -
 2 files changed, 4 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/eb0734b6/eagle-core/eagle-alert/eagle-alert-notification-plugin/src/main/java/org/apache/eagle/notification/plugin/AlertEmailPlugin.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-alert/eagle-alert-notification-plugin/src/main/java/org/apache/eagle/notification/plugin/AlertEmailPlugin.java b/eagle-core/eagle-alert/eagle-alert-notification-plugin/src/main/java/org/apache/eagle/notification/plugin/AlertEmailPlugin.java
index 0577f5c..f9a7ed3 100644
--- a/eagle-core/eagle-alert/eagle-alert-notification-plugin/src/main/java/org/apache/eagle/notification/plugin/AlertEmailPlugin.java
+++ b/eagle-core/eagle-alert/eagle-alert-notification-plugin/src/main/java/org/apache/eagle/notification/plugin/AlertEmailPlugin.java
@@ -48,9 +48,11 @@ public class AlertEmailPlugin implements NotificationPlugin {
 	private final static long DEFAULT_THREAD_POOL_SHRINK_TIME = 60000L; // 1 minute
 	private transient ThreadPoolExecutor executorPool;
 	private NotificationStatus status = new NotificationStatus();
+	private Config config;
 
 	@Override
 	public void init(Config config, List<AlertDefinitionAPIEntity> initAlertDefs) throws Exception {
+		this.config = config;
 		executorPool = new ThreadPoolExecutor(DEFAULT_THREAD_POOL_CORE_SIZE, DEFAULT_THREAD_POOL_MAX_SIZE, DEFAULT_THREAD_POOL_SHRINK_TIME, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
 		LOG.info(" Creating Email Generator... ");
 		for( AlertDefinitionAPIEntity  entity : initAlertDefs ){
@@ -59,7 +61,7 @@ public class AlertEmailPlugin implements NotificationPlugin {
 				String notificationType = notificationConfigMap.get(NotificationConstants.NOTIFICATION_TYPE);
 				// for backward compatibility, default notification is email
 				if(notificationType == null || notificationType.equalsIgnoreCase(NotificationConstants.EMAIL_NOTIFICATION)){
-					AlertEmailGenerator generator = createEmailGenerator(notificationConfigMap);
+					AlertEmailGenerator generator = createEmailGenerator( notificationConfigMap );
 						this.emailGenerators.put(entity.getTags().get(Constants.POLICY_ID), generator);
 						LOG.info("Successfully initialized email notification for policy " + entity.getTags().get(Constants.POLICY_ID) + ",with " + notificationConfigMap);
 				}
@@ -117,7 +119,7 @@ public class AlertEmailPlugin implements NotificationPlugin {
 			tplFileName = "ALERT_DEFAULT.vm";
 		}
 		AlertEmailGenerator gen = AlertEmailGeneratorBuilder.newBuilder().
-				withEagleProps(EagleConfigFactory.load().getConfig().getObject("eagleProps")).
+				withEagleProps(this.config.getObject("eagleProps")).
 				withSubject(notificationConfig.get(NotificationConstants.SUBJECT)).
 				withSender(notificationConfig.get(NotificationConstants.SENDER)).
 				withRecipients(notificationConfig.get(NotificationConstants.RECIPIENTS)).

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/eb0734b6/eagle-topology-assembly/src/assembly/eagle-topology-assembly.xml
----------------------------------------------------------------------
diff --git a/eagle-topology-assembly/src/assembly/eagle-topology-assembly.xml b/eagle-topology-assembly/src/assembly/eagle-topology-assembly.xml
index e397efa..2a0d211 100644
--- a/eagle-topology-assembly/src/assembly/eagle-topology-assembly.xml
+++ b/eagle-topology-assembly/src/assembly/eagle-topology-assembly.xml
@@ -47,7 +47,6 @@
                 <exclude>asm:asm</exclude>
                 <exclude>org.apache.log4j.wso2:log4j</exclude>
                 <exclude>log4j:apache-log4j-extras</exclude>
-                <exclude>org.apache.zookeeper:zookeeper</exclude>
             </excludes>
         </dependencySet>
     </dependencySets>


[8/9] incubator-eagle git commit: EAGLE-139 EAGLE-163 Eagle UI Modularization and fix bugs in policy extensions

Posted by hd...@apache.org.
EAGLE-139 EAGLE-163 Eagle UI Modularization and fix bugs in policy extensions

https://issues.apache.org/jira/browse/EAGLE-139

Each site contains multi applications. Application works for different monitoring purpose. Add there is no communication between applications. For example, Eagle Dam is a application for security monitoring.
Each application is made up with serval features. And feature can share between applications. Feature does the basic job of the application. For example, Policies & Alerts in Eagle DAM is the base feature. Each application includes customized application using this feature wills both display the policies & Alerts in navigation bar. If you want to extend the application, you can customize feature. But avoid modify original feature for special usage.

Author:
@zombieJ <ji...@ebay.com>
@haoch <ha...@apache.org>
@qingwen220 <qi...@ebay.com>

Reviewer:
@haoch <ha...@apache.org>
@qingwen220 <qi...@ebay.com>
@anyway1021 <zi...@ebay.com>

Closes #103


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

Branch: refs/heads/branch-0.3.0
Commit: d37643b0ae7c3fe22fe2794a9fa18ec74b8f9eb2
Parents: eb0734b
Author: Hao Chen <ha...@apache.org>
Authored: Thu Feb 25 18:25:07 2016 +0800
Committer: Hao Chen <ha...@apache.org>
Committed: Thu Feb 25 18:25:07 2016 +0800

----------------------------------------------------------------------
 .../src/main/bin/eagle-create-table.rb          |   54 +-
 .../src/main/bin/eagle-drop-tables.sh           |   26 +
 eagle-assembly/src/main/bin/eagle-env.sh        |    4 +
 .../src/main/bin/eagle-service-init.sh          |    6 +-
 .../src/main/bin/eagle-topology-init.sh         |   63 +-
 .../main/bin/hbase-securitylog-schema-create.sh |   41 -
 .../src/test/resources/str.siddhiext            |    3 +
 .../alert/resolver/SiteApplicationObject.java   |   62 ++
 .../alert/resolver/SiteApplicationResource.java |  189 ++++
 .../alert/entity/AlertDataSourceEntity.java     |    1 +
 .../alert/entity/AlertEntityRepository.java     |    7 +-
 .../entity/ApplicationDescServiceEntity.java    |   94 ++
 .../alert/entity/FeatureDescServiceEntity.java  |   59 +
 .../alert/entity/GenericResourceEntity.java     |   47 +
 .../entity/SiteApplicationServiceEntity.java    |   60 +
 .../alert/entity/SiteDescServiceEntity.java     |   48 +
 .../apache/eagle/policy/common/Constants.java   |    7 +
 .../eagle/policy/dao/AlertDataSourceDAO.java    |   25 -
 .../policy/dao/AlertDataSourceDAOImpl.java      |   61 -
 .../src/main/resources/str.siddhiext            |    4 +
 .../generic/GenericEntityServiceResource.java   |   41 +
 .../src/main/resources/hadoop-metric-init.sh    |   25 +-
 .../dao/HbaseMetadataAccessConfigDAOImpl.java   |    7 +-
 .../src/main/resources/application.conf         |    4 +-
 .../security/hdfs/HDFSResourceUtils.java        |    7 +-
 .../hdfs/resolver/HDFSCommandResolver.java      |    2 +
 .../dao/HiveMetadataAccessConfigDAOImpl.java    |   19 +-
 .../src/main/resources/eagleSecurity.xml        |    4 +-
 eagle-webservice/src/main/webapp/Gruntfile.js   |   31 +-
 eagle-webservice/src/main/webapp/app/index.html |  137 ++-
 .../webapp/app/partials/config/application.html |  103 ++
 .../webapp/app/partials/config/feature.html     |   64 ++
 .../main/webapp/app/partials/config/site.html   |   94 ++
 .../webapp/app/partials/dam/alertDetail.html    |   61 -
 .../main/webapp/app/partials/dam/alertList.html |   83 --
 .../src/main/webapp/app/partials/dam/login.html |   49 -
 .../src/main/webapp/app/partials/dam/nvd3.html  |   19 -
 .../webapp/app/partials/dam/policyDetail.html   |  171 ---
 .../webapp/app/partials/dam/policyEdit.html     |  359 ------
 .../webapp/app/partials/dam/policyList.html     |   98 --
 .../webapp/app/partials/dam/sensitivity.html    |   32 -
 .../app/partials/dam/sensitivity/folder.html    |  110 --
 .../app/partials/dam/sensitivity/table.html     |  150 ---
 .../app/partials/dam/sensitivitySummary.html    |  143 ---
 .../main/webapp/app/partials/dam/siteList.html  |  162 ---
 .../webapp/app/partials/dam/streamList.html     |  179 ---
 .../main/webapp/app/partials/dam/summary.html   |   45 -
 .../app/partials/dam/userProfileDetail.html     |   87 --
 .../app/partials/dam/userProfileList.html       |  138 ---
 .../src/main/webapp/app/partials/landing.html   |   12 +
 .../src/main/webapp/app/partials/login.html     |   49 +
 .../main/webapp/app/public/css/animation.css    |   46 +
 .../src/main/webapp/app/public/css/main.css     |  206 ++--
 .../public/feature/classification/controller.js |  269 +++++
 .../classification/page/sensitivity.html        |   40 +
 .../classification/page/sensitivity/folder.html |  110 ++
 .../classification/page/sensitivity/table.html  |  150 +++
 .../app/public/feature/common/controller.js     | 1023 +++++++++++++++++
 .../public/feature/common/page/alertDetail.html |   61 +
 .../public/feature/common/page/alertList.html   |   69 ++
 .../feature/common/page/dataSrcConfig.html      |  125 +++
 .../feature/common/page/policyDetail.html       |  172 +++
 .../public/feature/common/page/policyEdit.html  |  359 ++++++
 .../public/feature/common/page/policyList.html  |   84 ++
 .../app/public/feature/metadata/controller.js   |   66 ++
 .../feature/metadata/page/streamList.html       |   84 ++
 .../public/feature/userProfile/controller.js    |  268 +++++
 .../public/feature/userProfile/page/detail.html |   87 ++
 .../public/feature/userProfile/page/list.html   |  138 +++
 .../webapp/app/public/images/favicon_white.png  |  Bin 0 -> 1621 bytes
 .../src/main/webapp/app/public/js/app.config.js |  179 +--
 .../src/main/webapp/app/public/js/app.js        | 1042 +++++++-----------
 .../src/main/webapp/app/public/js/app.ui.js     |  140 +--
 .../src/main/webapp/app/public/js/common.js     |  435 ++++----
 .../app/public/js/ctrl/alertController.js       |  128 ---
 .../webapp/app/public/js/ctrl/authController.js |   82 +-
 .../public/js/ctrl/configurationController.js   |  332 ++++++
 .../webapp/app/public/js/ctrl/damController.js  |  111 --
 .../src/main/webapp/app/public/js/ctrl/main.js  |   42 +
 .../app/public/js/ctrl/policyController.js      |  898 ---------------
 .../app/public/js/ctrl/sensitivityController.js |  454 --------
 .../webapp/app/public/js/ctrl/siteController.js |  209 ----
 .../app/public/js/ctrl/streamController.js      |  207 ----
 .../app/public/js/ctrl/userProfileController.js |  261 -----
 .../webapp/app/public/js/srv/applicationSrv.js  |  168 +++
 .../app/public/js/srv/authorizationSrv.js       |  131 +++
 .../webapp/app/public/js/srv/entitiesSrv.js     |  281 +++++
 .../src/main/webapp/app/public/js/srv/main.js   |   19 +
 .../main/webapp/app/public/js/srv/pageSrv.js    |  131 +++
 .../main/webapp/app/public/js/srv/siteSrv.js    |  193 ++++
 .../src/main/webapp/app/public/js/srv/uiSrv.js  |  218 ++++
 .../webapp/app/public/js/srv/wrapStateSrv.js    |  109 ++
 eagle-webservice/src/main/webapp/grunt.json     |    5 +-
 eagle-webservice/src/main/webapp/package.json   |    9 +-
 94 files changed, 6969 insertions(+), 5518 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-assembly/src/main/bin/eagle-create-table.rb
----------------------------------------------------------------------
diff --git a/eagle-assembly/src/main/bin/eagle-create-table.rb b/eagle-assembly/src/main/bin/eagle-create-table.rb
index cbef6f4..b1b790c 100644
--- a/eagle-assembly/src/main/bin/eagle-create-table.rb
+++ b/eagle-assembly/src/main/bin/eagle-create-table.rb
@@ -20,42 +20,28 @@ import org.apache.hadoop.hbase.HBaseConfiguration
 import org.apache.hadoop.hbase.client.HBaseAdmin
 
 def createEagleTable(admin, tableName)
-    if !admin.tableExists(tableName)
-        # create tableName, {NAME => 'f', VERSIONS => '1', BLOOMFILTER => 'ROW', COMPRESSION => 'GZ'}
-        create tableName, {NAME => 'f', VERSIONS => '1', BLOOMFILTER => 'ROW', COMPRESSION => 'SNAPPY'}
-	puts "Create Table #{tableName} successfully"
-    elsif admin.isTableDisabled(tableName)
-        admin.enableTable(tableName)
-	  puts "Table #{tableName} already exists"
-    else
-	  puts "Table #{tableName} already exists"
-    end
+  if !admin.tableExists(tableName)
+    # create tableName, {NAME => 'f', VERSIONS => '1', BLOOMFILTER => 'ROW', COMPRESSION => 'GZ'}
+    create tableName, {NAME => 'f', VERSIONS => '1', BLOOMFILTER => 'ROW', COMPRESSION => 'SNAPPY'}
+    puts "Create Table #{tableName} successfully"
+  elsif admin.isTableDisabled(tableName)
+    admin.enableTable(tableName)
+    puts "Table #{tableName} already exists"
+  else
+    puts "Table #{tableName} already exists"
+  end
 end
 
 conf = HBaseConfiguration.new
 admin = HBaseAdmin.new(conf)
 
-createEagleTable(admin, 'alertdef')
-createEagleTable(admin, 'ipzone')
-createEagleTable(admin, 'streamMetadata')
-createEagleTable(admin, 'alertdetail')
-createEagleTable(admin, 'fileSensitivity')
-createEagleTable(admin, 'eaglehdfs_alert')
-createEagleTable(admin, 'streamdef')
-createEagleTable(admin, 'eagle_metric')
-createEagleTable(admin, 'alertDataSource')
-createEagleTable(admin, 'alertExecutor')
-createEagleTable(admin, 'alertStream')
-createEagleTable(admin, 'alertStreamSchema')
-createEagleTable(admin, 'hiveResourceSensitivity')
-createEagleTable(admin, 'hbaseResourceSensitivity')
-createEagleTable(admin, 'mlmodel')
-createEagleTable(admin, 'userprofile')
-createEagleTable(admin, 'hfdsusercommandpattern')
-createEagleTable(admin, 'appCommand')
-createEagleTable(admin, 'appDefinition')
-createEagleTable(admin, 'serviceAudit')
-createEagleTable(admin, 'aggregatedef')
-createEagleTable(admin, 'alertNotifications')
-
-exit
+if ARGV.empty?
+  puts "Table list is empty, please go back to bin/eagle-env.sh and export EAGLE_TABLE_LIST"
+  exit 1
+end
+
+tableListVal=ARGV.first
+
+tableListVal.split(' ').map { |i| createEagleTable(admin, i) }
+
+exit 0

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-assembly/src/main/bin/eagle-drop-tables.sh
----------------------------------------------------------------------
diff --git a/eagle-assembly/src/main/bin/eagle-drop-tables.sh b/eagle-assembly/src/main/bin/eagle-drop-tables.sh
new file mode 100644
index 0000000..37fb540
--- /dev/null
+++ b/eagle-assembly/src/main/bin/eagle-drop-tables.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+source $(dirname $0)/eagle-env.sh
+
+commands=''
+for i in $EAGLE_TABLE_LIST; do
+    commands="disable '$i'\ndrop '$i'\n"$commands
+done
+
+echo -e $commands | hbase shell
+#echo commands | hbase shell -n > /dev/null 2>&1
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-assembly/src/main/bin/eagle-env.sh
----------------------------------------------------------------------
diff --git a/eagle-assembly/src/main/bin/eagle-env.sh b/eagle-assembly/src/main/bin/eagle-env.sh
index 79ff5fa..cfffa0d 100755
--- a/eagle-assembly/src/main/bin/eagle-env.sh
+++ b/eagle-assembly/src/main/bin/eagle-env.sh
@@ -42,3 +42,7 @@ export EAGLE_CLASSPATH=$EAGLE_HOME/conf
 for file in $EAGLE_HOME/lib/share/*;do
 	EAGLE_CLASSPATH=$EAGLE_CLASSPATH:$file
 done
+
+# EAGLE_TABLE_LIST
+export EAGLE_TABLE_LIST='alertdef ipzone streamMetadata alertdetail fileSensitivity eaglehdfs_alert streamdef eagle_metric alertExecutor alertStream alertStreamSchema hiveResourceSensitivity hbaseResourceSensitivity mlmodel userprofile hfdsusercommandpattern appCommand appDefinition serviceAudit aggregatedef alertNotifications eagleSiteDesc eagleSiteApplication eagleApplicationDesc eagleFeatureDesc eagle_metadata'
+

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-assembly/src/main/bin/eagle-service-init.sh
----------------------------------------------------------------------
diff --git a/eagle-assembly/src/main/bin/eagle-service-init.sh b/eagle-assembly/src/main/bin/eagle-service-init.sh
index 01bddb5..a9c1cb2 100755
--- a/eagle-assembly/src/main/bin/eagle-service-init.sh
+++ b/eagle-assembly/src/main/bin/eagle-service-init.sh
@@ -15,7 +15,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-eagle_bin=$(dirname $0)
+source $(dirname $0)/eagle-env.sh
 
 ###################################################
 ###        create hbase tables for eagle
@@ -23,7 +23,7 @@ eagle_bin=$(dirname $0)
 
 echo "Creating hbase tables for eagle ... "
 
-hbase shell $eagle_bin/eagle-create-table.rb
+hbase shell ${EAGLE_HOME}/bin/eagle-create-table.rb "$EAGLE_TABLE_LIST"
 
 if [ $? = 0 ];then
 	echo "==> Successfully created hbase tables"
@@ -32,6 +32,4 @@ else
 	exit 1
 fi
 
-echo "Finished initializing for eagle service"
-
 exit 0
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-assembly/src/main/bin/eagle-topology-init.sh
----------------------------------------------------------------------
diff --git a/eagle-assembly/src/main/bin/eagle-topology-init.sh b/eagle-assembly/src/main/bin/eagle-topology-init.sh
index 03b2a9a..2a3032e 100755
--- a/eagle-assembly/src/main/bin/eagle-topology-init.sh
+++ b/eagle-assembly/src/main/bin/eagle-topology-init.sh
@@ -24,9 +24,38 @@ eagle_bin=$EAGLE_HOME/bin
 #####################################################################
 
 ## AlertDataSource: data sources bound to sites
-echo "Importing AlertDataSourceService for HDFS... "
 
-curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertDataSourceService" -d '[{"prefix":"alertDataSource","tags":{"site" : "sandbox", "dataSource":"hdfsAuditLog"}, "enabled": "true", "config" : "{\"hdfsEndpoint\":\"hdfs://sandbox.hortonworks.com:8020\"}", "desc":"HDFS"}]'
+echo "Begin to initialize HBase tables ..."
+
+echo ""
+echo "Importing sample site ..."
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=SiteDescService" -d '[{"prefix":"eagleSiteDesc","tags":{"site" : "sandbox"}, "enabled": true}]'
+
+echo ""
+echo "Importing applications for sample site ..."
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=SiteApplicationService" -d '[{"prefix":"eagleSiteApplication","tags":{"site" : "sandbox", "application":"hdfsAuditLog"}, "enabled": true, "config" : "{\"hdfsEndpoint\":\"hdfs://sandbox.hortonworks.com:8020\"}"}]'
+
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=SiteApplicationService" -d '[{"prefix":"eagleSiteApplication","tags":{"site" : "sandbox", "application":"hbaseSecurityLog"}, "enabled": true, "config" : "{\"zkClientPort\":\"2181\", \"zkQuorum\":\"localhost\"}"}]'
+
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=SiteApplicationService" -d '[{"prefix":"eagleSiteApplication","tags":{"site" : "sandbox", "application":"hiveQueryLog"}, "enabled": true, "config":"{\"accessType\":\"metastoredb_jdbc\",\"password\":\"hive\",\"user\":\"hive\",\"jdbcDriverClassName\":\"com.mysql.jdbc.Driver\",\"jdbcUrl\":\"jdbc:mysql://sandbox.hortonworks.com/hive?createDatabaseIfNotExist=true\"}"}]'
+
+echo ""
+echo "Importing application definitions ..."
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=ApplicationDescService" -d '[{"prefix":"eagleApplicationDesc","tags":{"application":"hdfsAuditLog"},"desc":"HDFS audit log security check application","alias":"HDFS","group":"DAM","features":["common","classification","userProfile","metadata"],"config":"{\n\t\"view\": {\n\t\t\"prefix\": \"fileSensitivity\",\n\t\t\"service\": \"FileSensitivityService\",\n\t\t\"keys\": [\n\t\t\t\"filedir\",\n\t\t\t\"sensitivityType\"\n\t\t],\n\t\t\"type\": \"folder\",\n\t\t\"api\": \"hdfsResource\"\n\t}\n}"}]'
+
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=ApplicationDescService" -d '[{"prefix":"eagleApplicationDesc","tags":{"application":"hbaseSecurityLog"},"desc":"HBASE audit log security check application","alias":"HBASE","group":"DAM","features":["common","classification","userProfile","metadata"],"config":"{\n\t\"view\": {\n\t\t\"prefix\": \"hbaseResourceSensitivity\",\n\t\t\"service\": \"HbaseResourceSensitivityService\",\n\t\t\"keys\": [\n\t\t\t\"hbaseResource\",\n\t\t\t\"sensitivityType\"\n\t\t],\n\t\t\"type\": \"table\",\n\t\t\"api\": {\n\t\t\t\"database\": \"hbaseResource/namespaces\",\n\t\t\t\"table\": \"hbaseResource/tables\",\n\t\t\t\"column\": \"hbaseResource/columns\"\n\t\t},\n\t\t\"mapping\": {\n\t\t\t\"database\": \"namespace\",\n\t\t\t\"table\": \"table\",\n\t\t\t\"column\": \"columnFamily\"\n\t\t}\n\t}\n}"}]'
+
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=ApplicationDescService" -d '[{"prefix":"eagleApplicationDesc","tags":{"application":"hiveQueryLog"},"desc":"Hive query log security check application","alias":"HIVE","group":"DAM","features":["common","classification","userProfile","metadata"], "config":"{\n\t\"view\": {\n\t\t\"prefix\": \"hiveResourceSensitivity\",\n\t\t\"service\": \"HiveResourceSensitivityService\",\n\t\t\"keys\": [\n\t\t\t\"hiveResource\",\n\t\t\t\"sensitivityType\"\n\t\t],\n\t\t\"type\": \"table\",\n\t\t\"api\": {\n\t\t\t\"database\": \"hiveResource/databases\",\n\t\t\t\"table\": \"hiveResource/tables\",\n\t\t\t\"column\": \"hiveResource/columns\"\n\t\t},\n\t\t\"mapping\": {\n\t\t\t\"database\": \"database\",\n\t\t\t\"table\": \"table\",\n\t\t\t\"column\": \"column\"\n\t\t}\n\t}\n}"}]'
+
+echo ""
+echo "Importing feature definitions ..."
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=FeatureDescService" -d '[{"prefix":"eagleFeatureDesc","tags":{"feature":"common"},"desc":"Provide the Policy & Alert feature.","version":"01"}]'
+
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=FeatureDescService" -d '[{"prefix":"eagleFeatureDesc","tags":{"feature":"classification"},"desc":"Sensitivity browser of the data classification.","version":"01"}]'
+
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=FeatureDescService" -d '[{"prefix":"eagleFeatureDesc","tags":{"feature":"userProfile"},"desc":"Machine learning of the user profile","version":"01"}]'
+
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=FeatureDescService" -d '[{"prefix":"eagleFeatureDesc","tags":{"feature":"metadata"},"desc":"Stream metadata viewer","version":"01"}]'
 
 
 ## AlertStreamService: alert streams generated from data source
@@ -44,14 +73,32 @@ echo ""
 echo "Importing AlertStreamSchemaService for HDFS... "
 curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertStreamSchemaService" -d '[{"prefix":"alertStreamSchema","tags":{"dataSource":"hdfsAuditLog","streamName":"hdfsAuditLogEventStream","attrName":"src"},"attrDescription":"source directory or file, such as /tmp","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hdfs.resolver.HDFSResourceResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hdfsAuditLog","streamName":"hdfsAuditLogEventStream","attrName":"dst"},"attrDescription":"destination directory, such as /tmp","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hdfs.resolver.HDFSResourceResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hdfsAuditLog","streamName":"hdfsAuditLogEventStream","attrName":"host"},"attrDescription":"hostname, such as loc
 alhost","attrType":"string","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hdfsAuditLog","streamName":"hdfsAuditLogEventStream","attrName":"timestamp"},"attrDescription":"milliseconds of the datetime","attrType":"long","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hdfsAuditLog","streamName":"hdfsAuditLogEventStream","attrName":"allowed"},"attrDescription":"true, false or none","attrType":"bool","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hdfsAuditLog","streamName":"hdfsAuditLogEventStream","attrName":"user"},"attrDescription":"process user","attrType":"string","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hdfsAuditLog","streamName":"hdfsAuditLogEventStream","attrName":"cmd"},"attrDescription":"file/directory operation, such as getfileinfo, open, listStatus and so on","attrType":"string","category":"","attrVal
 ueResolver":"org.apache.eagle.service.security.hdfs.resolver.HDFSCommandResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hdfsAuditLog","streamName":"hdfsAuditLogEventStream","attrName":"sensitivityType"},"attrDescription":"mark such as AUDITLOG, SECURITYLOG","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hdfs.resolver.HDFSSensitivityTypeResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hdfsAuditLog","streamName":"hdfsAuditLogEventStream","attrName":"securityZone"},"attrDescription":"","attrType":"string","category":"","attrValueResolver":""}]'
 
+
 #####################################################################
-#            Import stream metadata for HIVE
+#            Import stream metadata for HBASE
 #####################################################################
 
-## AlertDataSource: data sources bound to sites
+#### AlertStreamService: alert streams generated from data source
+echo ""
+echo "Importing AlertStreamService for HBASE... "
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertStreamService" -d '[{"prefix":"alertStream","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream"},"desc":"alert event stream from hbase security audit log"}]'
+
+
+#### AlertExecutorService: what alert streams are consumed by alert executor
+echo ""
+echo "Importing AlertExecutorService for HBASE... "
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertExecutorService" -d '[{"prefix":"alertExecutor","tags":{"dataSource":"hbaseSecurityLog","alertExecutorId":"hbaseSecurityLogAlertExecutor","streamName":"hbaseSecurityLogEventStream"},"desc":"alert executor for hbase security log event stream"}]'
+
+
+#### AlertStreamSchemaService: schema for event from alert stream
 echo ""
-echo "Importing AlertDataSourceService for HIVE... "
-curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertDataSourceService" -d '[{"prefix":"alertDataSource","tags":{"site" : "sandbox", "dataSource":"hiveQueryLog"},"enabled": "true", "config" : "{\"accessType\":\"metastoredb_jdbc\",\"password\":\"hive\",\"user\":\"hive\",\"jdbcDriverClassName\":\"com.mysql.jdbc.Driver\",\"jdbcUrl\":\"jdbc:mysql://sandbox.hortonworks.com/hive?createDatabaseIfNotExist=true\"}", "desc":"HIVE"}]'
+echo "Importing AlertStreamSchemaService for HBASE... "
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertStreamSchemaService" -d '[{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"host"},"attrDescription":"remote ip address to access hbase","attrType":"string","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"request"},"attrDescription":"","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hbase.resolver.HbaseRequestResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"status"},"attrDescription":"access status: allowed or denied","attrType":"string","category":"","attrValueResolver":""},{"pre
 fix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"user"},"attrDescription":"hbase user","attrType":"string","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"timestamp"},"attrDescription":"milliseconds of the datetime","attrType":"long","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"scope"},"attrDescription":"the resources which users are then granted specific permissions (Read, Write, Execute, Create, Admin) against","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hbase.resolver.HbaseMetadataResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"action"},"attrDescripti
 on":"action types, such as read, write, create, execute, and admin","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hbase.resolver.HbaseActionResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"sensitivityType"},"attrDescription":"","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hbase.resolver.HbaseSensitivityTypeResolver"}]'
+
+
+#####################################################################
+#            Import stream metadata for HIVE
+#####################################################################
 
 ## AlertStreamService: alert streams generated from data source
 echo ""
@@ -71,9 +118,7 @@ curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:a
 #####################################################################
 #            Import stream metadata for UserProfile
 #####################################################################
-echo ""
-echo "Importing AlertDataSourceService for USERPROFILE"
-curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertDataSourceService" -d '[{"prefix":"alertDataSource","tags":{"site" : "sandbox", "dataSource":"userProfile"}, "enabled": "true", "config" : "{\"features\":\"getfileinfo,open,listStatus,setTimes,setPermission,rename,mkdirs,create,setReplication,contentSummary,delete,setOwner,fsck\"}", "desc":"USERPROFILE"}]'
+
 
 echo ""
 echo "Importing AlertDefinitionService for USERPROFILE"

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-assembly/src/main/bin/hbase-securitylog-schema-create.sh
----------------------------------------------------------------------
diff --git a/eagle-assembly/src/main/bin/hbase-securitylog-schema-create.sh b/eagle-assembly/src/main/bin/hbase-securitylog-schema-create.sh
deleted file mode 100755
index 7cdd6de..0000000
--- a/eagle-assembly/src/main/bin/hbase-securitylog-schema-create.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/bash
-
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with`
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/eagle-env.sh
-
-echo "Importing AlertDataSourceService for HBase... "
-
-#### AlertStreamService: alert streams generated from data source
-
-curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertStreamService" -d '[{"prefix":"alertStream","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream"},"desc":"alert event stream from hbase security audit log"}]'
-
-
-#### AlertExecutorService: what alert streams are consumed by alert executor
-
-curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertExecutorService" -d '[{"prefix":"alertExecutor","tags":{"dataSource":"hbaseSecurityLog","alertExecutorId":"hbaseSecurityLogAlertExecutor","streamName":"hbaseSecurityLogEventStream"},"desc":"alert executor for hbase security log event stream"}]'
-
-
-#### AlertStreamSchemaService: schema for event from alert stream
-
-curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertStreamSchemaService" -d '[{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"host"},"attrDescription":"remote ip address to access hbase","attrType":"string","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"request"},"attrDescription":"","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hbase.resolver.HbaseRequestResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"status"},"attrDescription":"access status: allowed or denied","attrType":"string","category":"","attrValueResolver":""},{"pre
 fix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"user"},"attrDescription":"hbase user","attrType":"string","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"timestamp"},"attrDescription":"milliseconds of the datetime","attrType":"long","category":"","attrValueResolver":""},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"scope"},"attrDescription":"the resources which users are then granted specific permissions (Read, Write, Execute, Create, Admin) against","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hbase.resolver.HbaseMetadataResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"action"},"attrDescripti
 on":"action types, such as read, write, create, execute, and admin","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hbase.resolver.HbaseActionResolver"},{"prefix":"alertStreamSchema","tags":{"dataSource":"hbaseSecurityLog","streamName":"hbaseSecurityLogEventStream","attrName":"sensitivityType"},"attrDescription":"","attrType":"string","category":"","attrValueResolver":"org.apache.eagle.service.security.hbase.resolver.HbaseSensitivityTypeResolver"}]'
-
-echo ""
-
-## AlertDataSource: data sources bound to sites
-echo "Importing AlertDataSourceService for hbase... "
-
-curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertDataSourceService" -d '[{"prefix":"alertDataSource","tags":{"site":"sandbox","dataSource":"hbaseSecurityLog"},"enabled":"true","config":"{\"zkClientPort\":\"2181\", \"zkQuorum\":\"localhost\"}","desc":"HBASE"}]'
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-alert/eagle-alert-process/src/test/resources/str.siddhiext
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-alert/eagle-alert-process/src/test/resources/str.siddhiext b/eagle-core/eagle-alert/eagle-alert-process/src/test/resources/str.siddhiext
index 6b64e53..435b4c3 100644
--- a/eagle-core/eagle-alert/eagle-alert-process/src/test/resources/str.siddhiext
+++ b/eagle-core/eagle-alert/eagle-alert-process/src/test/resources/str.siddhiext
@@ -31,6 +31,9 @@ trim=org.wso2.siddhi.extension.string.TrimFunctionExtension
 upper=org.wso2.siddhi.extension.string.UpperFunctionExtension
 hex=org.wso2.siddhi.extension.string.HexFunctionExtension
 unhex=org.wso2.siddhi.extension.string.UnhexFunctionExtension
+contains=org.wso2.siddhi.extension.string.ContainsFunctionExtension
+
+# Eagle Siddhi Extension
 equalsIgnoreCase=org.apache.eagle.policy.siddhi.extension.EqualsIgnoreCaseExtension
 containsIgnoreCase=org.apache.eagle.policy.siddhi.extension.ContainsIgnoreCaseExtension
 regexpIgnoreCase=org.apache.eagle.policy.siddhi.extension.RegexpIgnoreCaseFunctionExtension
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-alert/eagle-alert-service/src/main/java/org/apache/eagle/service/alert/resolver/SiteApplicationObject.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-alert/eagle-alert-service/src/main/java/org/apache/eagle/service/alert/resolver/SiteApplicationObject.java b/eagle-core/eagle-alert/eagle-alert-service/src/main/java/org/apache/eagle/service/alert/resolver/SiteApplicationObject.java
new file mode 100644
index 0000000..42f95f7
--- /dev/null
+++ b/eagle-core/eagle-alert/eagle-alert-service/src/main/java/org/apache/eagle/service/alert/resolver/SiteApplicationObject.java
@@ -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.
+ *
+ */
+
+package org.apache.eagle.service.alert.resolver;
+
+
+import org.apache.eagle.alert.entity.SiteApplicationServiceEntity;
+import org.apache.eagle.log.base.taggedlog.TaggedLogAPIEntity;
+
+import java.util.List;
+import java.util.Map;
+
+public class SiteApplicationObject extends TaggedLogAPIEntity {
+
+    public Boolean getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(Boolean enabled) {
+        this.enabled = enabled;
+        valueChanged("enabled");
+    }
+
+    public List<SiteApplicationServiceEntity> getApplications() {
+        return applications;
+    }
+
+    public void setApplications(List<SiteApplicationServiceEntity> applications) {
+        this.applications = applications;
+        valueChanged("applicationList");
+    }
+
+    @Override
+    public Map<String, String> getTags() {
+        return tags;
+    }
+
+    @Override
+    public void setTags(Map<String, String> tags) {
+        this.tags = tags;
+        valueChanged("tags");
+    }
+
+    Map<String, String> tags;
+    Boolean enabled;
+    List<SiteApplicationServiceEntity> applications;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-alert/eagle-alert-service/src/main/java/org/apache/eagle/service/alert/resolver/SiteApplicationResource.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-alert/eagle-alert-service/src/main/java/org/apache/eagle/service/alert/resolver/SiteApplicationResource.java b/eagle-core/eagle-alert/eagle-alert-service/src/main/java/org/apache/eagle/service/alert/resolver/SiteApplicationResource.java
new file mode 100644
index 0000000..903a220
--- /dev/null
+++ b/eagle-core/eagle-alert/eagle-alert-service/src/main/java/org/apache/eagle/service/alert/resolver/SiteApplicationResource.java
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ *
+ */
+
+package org.apache.eagle.service.alert.resolver;
+
+import org.apache.eagle.alert.entity.ApplicationDescServiceEntity;
+import org.apache.eagle.alert.entity.SiteApplicationServiceEntity;
+import org.apache.eagle.alert.entity.SiteDescServiceEntity;
+import org.apache.eagle.log.base.taggedlog.TaggedLogAPIEntity;
+import org.apache.eagle.log.entity.GenericServiceAPIResponseEntity;
+import org.apache.eagle.policy.common.Constants;
+import org.apache.eagle.service.generic.GenericEntityServiceResource;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.type.TypeFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+@Path(SiteApplicationResource.ROOT_PATH)
+public class SiteApplicationResource {
+    private final static Logger LOG = LoggerFactory.getLogger(SiteApplicationResource.class);
+    private final static GenericEntityServiceResource resource = new GenericEntityServiceResource();
+    public final static String ROOT_PATH = "/module";
+
+    @Path("site")
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public GenericServiceAPIResponseEntity deleteSite(@QueryParam("site") String site) {
+        String siteQuery = Constants.SITE_DESCRIPTION_SERVICE_ENDPOINT_NAME+ "[@site=\"" + site + "\"]{*}";
+        String siteApplicationQuery = Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME + "[@site=\"" + site + "\"]{*}";
+        int pageSize = Integer.MAX_VALUE;
+
+        GenericServiceAPIResponseEntity response = resource.deleteByQuery(siteQuery, null, null, pageSize, null, false, false, 0L, 0, true, 0, null, false);
+        if(response.isSuccess()) {
+            response = resource.deleteByQuery(siteApplicationQuery, null, null, pageSize, null, false, false, 0L, 0, true, 0, null, false);
+            if(!response.isSuccess()) {
+                LOG.error(response.getException());
+            }
+        } else {
+            LOG.error(response.getException());
+        }
+        return response;
+    }
+
+    @Path("application")
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public GenericServiceAPIResponseEntity deleteApplication(@QueryParam("application") String application) {
+        String applicationQuery = Constants.APPLICATION_DESCRIPTION_SERVICE_ENDPOINT_NAME+ "[@application=\"" + application + "\"]{*}";
+        String siteApplicationQuery = Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME + "[@application=\"" + application + "\"]{*}";
+        int pageSize = Integer.MAX_VALUE;
+
+        GenericServiceAPIResponseEntity response = resource.deleteByQuery(applicationQuery, null, null, pageSize, null, false, false, 0L, 0, true, 0, null, false);
+        if(response.isSuccess()) {
+            response = resource.deleteByQuery(siteApplicationQuery, null, null, pageSize, null, false, false, 0L, 0, true, 0, null, false);
+            if(!response.isSuccess()) {
+                LOG.error(response.getException());
+            }
+        } else {
+            LOG.error(response.getException());
+        }
+        return response;
+    }
+
+    @Path("feature")
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public GenericServiceAPIResponseEntity deleteFeature(@QueryParam("feature") String feature) {
+        String featureQuery = Constants.FEATURE_DESCRIPTION_SERVICE_ENDPOINT_NAME+ "[@feature=\"" + feature + "\"]{*}";
+        String applicationQuery = Constants.APPLICATION_DESCRIPTION_SERVICE_ENDPOINT_NAME + "[]{*}";
+        int pageSize = Integer.MAX_VALUE;
+
+        GenericServiceAPIResponseEntity response = resource.deleteByQuery(featureQuery, null, null, pageSize, null, false, false, 0L, 0, true, 0, null, false);
+        if(response.isSuccess()) {
+            response = resource.search(applicationQuery, null, null, pageSize, null, false, false, 0L, 0, true, 0, null, false);
+            if(response.isSuccess()) {
+                List<ApplicationDescServiceEntity> entityList = response.getObj();
+                Boolean isModified = false;
+                for(ApplicationDescServiceEntity entity : entityList) {
+                    if(entity.getFeatures().contains(feature)) {
+                        List<String> features = entity.getFeatures();
+                        features.remove(feature);
+                        entity.setFeatures(features);
+                        isModified = true;
+                    }
+                }
+                if(isModified) {
+                    response = resource.updateEntities(entityList, Constants.APPLICATION_DESCRIPTION_SERVICE_ENDPOINT_NAME);
+                }
+            }
+        }
+        if(!response.isSuccess()) {
+            LOG.error(response.getException());
+        }
+        return response;
+    }
+
+    @Path("siteApplication")
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public GenericServiceAPIResponseEntity createSiteApplications(InputStream inputStream) {
+        GenericServiceAPIResponseEntity response = new GenericServiceAPIResponseEntity<>();
+        int pageSize = Integer.MAX_VALUE;
+        try {
+            List<SiteApplicationObject> entities = (List<SiteApplicationObject>) unmarshalSiteApplicationEntities(inputStream);
+            if(entities == null) {
+                throw new IllegalArgumentException("cannot convert to SiteApplicationObject");
+            }
+            List<SiteDescServiceEntity> siteEntities = new LinkedList<>();
+            List<SiteApplicationServiceEntity> applicationEntities = new LinkedList<>();
+            Set<String> sites = new HashSet<>();
+            for(SiteApplicationObject e : entities) {
+                sites.add(e.getTags().get("site"));
+                SiteDescServiceEntity entity = new SiteDescServiceEntity();
+                entity.setEnabled(e.getEnabled());
+                entity.setTags(e.getTags());
+                siteEntities.add(entity);
+                applicationEntities.addAll(e.getApplications());
+            }
+            response = resource.updateEntities(siteEntities, Constants.SITE_DESCRIPTION_SERVICE_ENDPOINT_NAME);
+            if(response.isSuccess()) {
+                String query = buildQueryWithAttributeList(Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME, "site", sites);
+                LOG.info("query=" + query);
+                response = resource.search(query, null, null, pageSize, null, false, false, 0L, 0, true, 0, null, false);
+                if(response.isSuccess()) {
+                    List<SiteApplicationServiceEntity> applications = response.getObj();
+                    for(SiteApplicationServiceEntity app : applications) {
+                        app.setEnabled(false);
+                    }
+                    response = resource.updateEntities(applications, Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME);
+                    if(response.isSuccess()) {
+                        response = resource.updateEntities(applicationEntities, Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME);
+                    }
+                }
+            }
+            if(!response.isSuccess()) {
+                LOG.error(response.getException());
+            }
+        } catch (Exception ex) {
+            LOG.error(ex.getMessage(), ex);
+            response.setException(ex);
+        }
+        return response;
+    }
+
+    private String buildQueryWithAttributeList(String serviceName, String attr, Set<String> sets) {
+        StringBuilder builder = new StringBuilder(serviceName + "[");
+        String attribute = "@" + attr + "=";
+        String condition = " OR ";
+        for(String s : sets) {
+            String value = String.format("\"%s\"", s);
+            builder.append(attribute + value);
+            builder.append(condition);
+        }
+        String result = builder.substring(0, builder.length()-condition.length());
+        result = result + "]{*}";
+        return result;
+    }
+
+    private List<? extends TaggedLogAPIEntity> unmarshalSiteApplicationEntities(InputStream inputStream) throws IllegalAccessException, InstantiationException, IOException {
+        ObjectMapper objectMapper = new ObjectMapper();
+        return objectMapper.readValue(inputStream, TypeFactory.defaultInstance().constructCollectionType(LinkedList.class, SiteApplicationObject.class));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertDataSourceEntity.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertDataSourceEntity.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertDataSourceEntity.java
index 173481d..0778ffc 100644
--- a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertDataSourceEntity.java
+++ b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertDataSourceEntity.java
@@ -30,6 +30,7 @@ import org.codehaus.jackson.map.annotate.JsonSerialize;
 @JsonIgnoreProperties(ignoreUnknown = true)
 @TimeSeries(false)
 @Tags({"site", "dataSource"})
+@Deprecated
 public class AlertDataSourceEntity extends TaggedLogAPIEntity{
     @Column("a")
     private boolean enabled;

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertEntityRepository.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertEntityRepository.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertEntityRepository.java
index 0525126..507c482 100644
--- a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertEntityRepository.java
+++ b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/AlertEntityRepository.java
@@ -26,8 +26,13 @@ public class AlertEntityRepository extends EntityRepository {
 		entitySet.add(AlertDefinitionAPIEntity.class);
 		entitySet.add(AlertStreamSchemaEntity.class);
 		entitySet.add(AlertStreamEntity.class);
-		entitySet.add(AlertDataSourceEntity.class);
+		//entitySet.add(AlertDataSourceEntity.class);
         entitySet.add(AlertExecutorEntity.class);
+		entitySet.add(ApplicationDescServiceEntity.class);
+		entitySet.add(FeatureDescServiceEntity.class);
+		entitySet.add(SiteDescServiceEntity.class);
+		entitySet.add(SiteApplicationServiceEntity.class);
+		this.registerEntity(GenericResourceEntity.class);
 		entitySet.add(AlertNotificationEntity.class);
 	}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/ApplicationDescServiceEntity.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/ApplicationDescServiceEntity.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/ApplicationDescServiceEntity.java
new file mode 100644
index 0000000..76a2500
--- /dev/null
+++ b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/ApplicationDescServiceEntity.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ *
+ */
+
+package org.apache.eagle.alert.entity;
+
+
+import org.apache.eagle.log.base.taggedlog.TaggedLogAPIEntity;
+import org.apache.eagle.log.entity.meta.*;
+import org.apache.eagle.policy.common.Constants;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import java.util.List;
+
+@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
+@Table("eagleApplicationDesc")
+@ColumnFamily("f")
+@Prefix("eagleApplicationDesc")
+@Service(Constants.APPLICATION_DESCRIPTION_SERVICE_ENDPOINT_NAME)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@TimeSeries(false)
+@Tags({"application"})
+public class ApplicationDescServiceEntity extends TaggedLogAPIEntity {
+    @Column("a")
+    private String desc;
+    @Column("b")
+    private String alias;
+    @Column("c")
+    private String group;
+    @Column("d")
+    private List<String> features;
+    @Column("e")
+    private String config;
+
+    public String getConfig() {
+        return config;
+    }
+
+    public void setConfig(String config) {
+        this.config = config;
+        valueChanged("config");
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public void setDesc(String desc) {
+        this.desc = desc;
+        valueChanged("desc");
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+        valueChanged("group");
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public void setAlias(String alias) {
+        this.alias = alias;
+        valueChanged("alias");
+    }
+
+    public List<String> getFeatures() {
+        return features;
+    }
+
+    public void setFeatures(List<String> features) {
+        this.features = features;
+        valueChanged("features");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/FeatureDescServiceEntity.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/FeatureDescServiceEntity.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/FeatureDescServiceEntity.java
new file mode 100644
index 0000000..4fc6dac
--- /dev/null
+++ b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/FeatureDescServiceEntity.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ *
+ */
+
+package org.apache.eagle.alert.entity;
+
+
+import org.apache.eagle.log.base.taggedlog.TaggedLogAPIEntity;
+import org.apache.eagle.log.entity.meta.*;
+import org.apache.eagle.policy.common.Constants;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
+@Table("eagleFeatureDesc")
+@ColumnFamily("f")
+@Prefix("eagleFeatureDesc")
+@Service(Constants.FEATURE_DESCRIPTION_SERVICE_ENDPOINT_NAME)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@TimeSeries(false)
+@Tags({"feature"})
+public class FeatureDescServiceEntity extends TaggedLogAPIEntity {
+    @Column("a")
+    private String desc;
+    @Column("b")
+    private String version;
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+        valueChanged("version");
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public void setDesc(String desc) {
+        this.desc = desc;
+        valueChanged("desc");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/GenericResourceEntity.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/GenericResourceEntity.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/GenericResourceEntity.java
new file mode 100644
index 0000000..c855499
--- /dev/null
+++ b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/GenericResourceEntity.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ *
+ */
+
+package org.apache.eagle.alert.entity;
+
+import org.apache.eagle.log.base.taggedlog.TaggedLogAPIEntity;
+import org.apache.eagle.log.entity.meta.*;
+import org.apache.eagle.policy.common.Constants;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
+@Table("eagle_metadata")
+@ColumnFamily("f")
+@Prefix("generic_resource")
+@Service(Constants.GENERIC_RESOURCE_SERVICE_ENDPOINT_NAME)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@TimeSeries(false)
+@Tags({"site", "name"})
+public class GenericResourceEntity extends TaggedLogAPIEntity {
+    @Column("a")
+    private String value;
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+        valueChanged("value");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/SiteApplicationServiceEntity.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/SiteApplicationServiceEntity.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/SiteApplicationServiceEntity.java
new file mode 100644
index 0000000..5eea4b3
--- /dev/null
+++ b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/SiteApplicationServiceEntity.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.eagle.alert.entity;
+
+
+import org.apache.eagle.log.base.taggedlog.TaggedLogAPIEntity;
+import org.apache.eagle.log.entity.meta.*;
+import org.apache.eagle.policy.common.Constants;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+
+@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
+@Table("eagleSiteApplication")
+@ColumnFamily("f")
+@Prefix("eagleSiteApplication")
+@Service(Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@TimeSeries(false)
+@Tags({"site", "application"})
+public class SiteApplicationServiceEntity extends TaggedLogAPIEntity {
+    @Column("a")
+    private String config;
+    @Column("b")
+    private Boolean enabled;
+
+    public String getConfig() {
+        return config;
+    }
+
+    public void setConfig(String config) {
+        this.config = config;
+        valueChanged("config");
+    }
+
+    public Boolean getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(Boolean enabled) {
+        this.enabled = enabled;
+        valueChanged("enabled");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/SiteDescServiceEntity.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/SiteDescServiceEntity.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/SiteDescServiceEntity.java
new file mode 100644
index 0000000..a8245cf
--- /dev/null
+++ b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/alert/entity/SiteDescServiceEntity.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ *
+ */
+
+package org.apache.eagle.alert.entity;
+
+
+import org.apache.eagle.log.base.taggedlog.TaggedLogAPIEntity;
+import org.apache.eagle.log.entity.meta.*;
+import org.apache.eagle.policy.common.Constants;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
+@Table("eagleSiteDesc")
+@ColumnFamily("f")
+@Prefix("eagleSiteDesc")
+@Service(Constants.SITE_DESCRIPTION_SERVICE_ENDPOINT_NAME)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@TimeSeries(false)
+@Tags({"site"})
+public class SiteDescServiceEntity extends TaggedLogAPIEntity {
+    @Column("a")
+    private Boolean enabled;
+
+    public Boolean getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(Boolean enabled) {
+        this.enabled = enabled;
+        valueChanged("enabled");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/common/Constants.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/common/Constants.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/common/Constants.java
index 256e46b..ca65669 100644
--- a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/common/Constants.java
+++ b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/common/Constants.java
@@ -26,6 +26,13 @@ public class Constants {
 	public final static String ALERT_NOTIFICATION_SERVICE_ENDPOINT_NAME = "AlertNotificationService";
 	public static final String ALERT_EMAIL_ORIGIN_PROPERTY = "alertEmailOrigin";
 	public static final String ALERT_TIMESTAMP_PROPERTY = "alertTimestamp";
+
+	public static final String SITE_APPLICATION_SERVICE_ENDPOINT_NAME = "SiteApplicationService";
+	public static final String SITE_DESCRIPTION_SERVICE_ENDPOINT_NAME = "SiteDescService";
+	public static final String APPLICATION_DESCRIPTION_SERVICE_ENDPOINT_NAME = "ApplicationDescService";
+	public static final String FEATURE_DESCRIPTION_SERVICE_ENDPOINT_NAME = "FeatureDescService";
+
+	public static final String GENERIC_RESOURCE_SERVICE_ENDPOINT_NAME = "GenericResourceService";
 	
 	public final static String AGGREGATE_DEFINITION_SERVICE_ENDPOINT_NAME = "AggregateDefinitionService";
 

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/dao/AlertDataSourceDAO.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/dao/AlertDataSourceDAO.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/dao/AlertDataSourceDAO.java
deleted file mode 100644
index 650defc..0000000
--- a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/dao/AlertDataSourceDAO.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.eagle.policy.dao;
-
-import org.apache.eagle.alert.entity.AlertDataSourceEntity;
-
-import java.util.List;
-
-public interface AlertDataSourceDAO {
-    List<AlertDataSourceEntity> findAlertDataSourceBySite(String site) throws Exception;
-}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/dao/AlertDataSourceDAOImpl.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/dao/AlertDataSourceDAOImpl.java b/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/dao/AlertDataSourceDAOImpl.java
deleted file mode 100644
index ca037a3..0000000
--- a/eagle-core/eagle-policy/eagle-policy-base/src/main/java/org/apache/eagle/policy/dao/AlertDataSourceDAOImpl.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.eagle.policy.dao;
-
-import org.apache.eagle.policy.common.Constants;
-import org.apache.eagle.alert.entity.AlertDataSourceEntity;
-import org.apache.eagle.log.entity.GenericServiceAPIResponseEntity;
-import org.apache.eagle.service.client.EagleServiceConnector;
-import org.apache.eagle.service.client.IEagleServiceClient;
-import org.apache.eagle.service.client.impl.EagleServiceClientImpl;
-import org.apache.commons.lang.time.DateUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.List;
-
-public class AlertDataSourceDAOImpl implements AlertDataSourceDAO{
-    private final Logger LOG = LoggerFactory.getLogger(AlertDataSourceDAOImpl.class);
-    private final EagleServiceConnector connector;
-
-    public AlertDataSourceDAOImpl(EagleServiceConnector connector){
-        this.connector = connector;
-    }
-
-    @Override
-    public List<AlertDataSourceEntity> findAlertDataSourceBySite(String site) throws Exception{
-        try {
-            IEagleServiceClient client = new EagleServiceClientImpl(connector);
-            String query = Constants.ALERT_STREAM_SERVICE_ENDPOINT_NAME + "[@site=\"" + site + "\"]{*}";
-            GenericServiceAPIResponseEntity<AlertDataSourceEntity> response =  client.search()
-                    .startTime(0)
-                    .endTime(10 * DateUtils.MILLIS_PER_DAY)
-                    .pageSize(Integer.MAX_VALUE)
-                    .query(query)
-                    .send();
-            client.close();
-            if (response.getException() != null) {
-                throw new Exception("Got an exception when query eagle service: " + response.getException());
-            }
-            return response.getObj();
-        }
-        catch (Exception ex) {
-            LOG.error("Got an exception when query stream metadata service ", ex);
-            throw new IllegalStateException(ex);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-policy/eagle-policy-base/src/main/resources/str.siddhiext
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-policy/eagle-policy-base/src/main/resources/str.siddhiext b/eagle-core/eagle-policy/eagle-policy-base/src/main/resources/str.siddhiext
index 8c1672b..479cdb0 100644
--- a/eagle-core/eagle-policy/eagle-policy-base/src/main/resources/str.siddhiext
+++ b/eagle-core/eagle-policy/eagle-policy-base/src/main/resources/str.siddhiext
@@ -31,6 +31,10 @@ trim=org.wso2.siddhi.extension.string.TrimFunctionExtension
 upper=org.wso2.siddhi.extension.string.UpperFunctionExtension
 hex=org.wso2.siddhi.extension.string.HexFunctionExtension
 unhex=org.wso2.siddhi.extension.string.UnhexFunctionExtension
+contains=org.wso2.siddhi.extension.string.ContainsFunctionExtension
+
+# Eagle Siddhi Extension
 equalsIgnoreCase=org.apache.eagle.policy.siddhi.extension.EqualsIgnoreCaseExtension
 containsIgnoreCase=org.apache.eagle.policy.siddhi.extension.ContainsIgnoreCaseExtension
 regexpIgnoreCase=org.apache.eagle.policy.siddhi.extension.RegexpIgnoreCaseFunctionExtension
+

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-core/eagle-query/eagle-service-base/src/main/java/org/apache/eagle/service/generic/GenericEntityServiceResource.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-query/eagle-service-base/src/main/java/org/apache/eagle/service/generic/GenericEntityServiceResource.java b/eagle-core/eagle-query/eagle-service-base/src/main/java/org/apache/eagle/service/generic/GenericEntityServiceResource.java
index 43302c8..fb52352 100644
--- a/eagle-core/eagle-query/eagle-service-base/src/main/java/org/apache/eagle/service/generic/GenericEntityServiceResource.java
+++ b/eagle-core/eagle-query/eagle-service-base/src/main/java/org/apache/eagle/service/generic/GenericEntityServiceResource.java
@@ -73,6 +73,47 @@ public class GenericEntityServiceResource {
         return objectMapper.readValue(inputStream, TypeFactory.defaultInstance().constructCollectionType(LinkedList.class, String.class));
     }
 
+
+    public GenericServiceAPIResponseEntity updateDatabase(Statement<ModifyResult<String>> statement) {
+        GenericServiceAPIResponseEntity<String> response = new GenericServiceAPIResponseEntity<>();
+        Map<String,Object> meta = new HashMap<>();
+        StopWatch stopWatch = new StopWatch();
+
+        try {
+            stopWatch.start();
+            DataStorage dataStorage = DataStorageManager.getDataStorageByEagleConfig();
+            if(dataStorage == null){
+                LOG.error("Data storage is null");
+                throw new IllegalDataStorageException("Data storage is null");
+            }
+            ModifyResult<String> result = statement.execute(dataStorage);
+            if(result.isSuccess()) {
+                List<String> keys =result.getIdentifiers();
+                if(keys != null) {
+                    response.setObj(keys, String.class);
+                    meta.put(TOTAL_RESULTS, keys.size());
+                } else {
+                    meta.put(TOTAL_RESULTS, 0);
+                }
+                meta.put(ELAPSEDMS,stopWatch.getTime());
+                response.setMeta(meta);
+                response.setSuccess(true);
+            }
+        } catch (Exception e) {
+            LOG.error(e.getMessage(), e);
+            response.setException(e);
+        }finally {
+            stopWatch.stop();
+        }
+        return response;
+    }
+
+    public GenericServiceAPIResponseEntity updateEntities(List<? extends TaggedLogAPIEntity> entities, String serviceName) {
+        CreateStatement createStatement = new CreateStatement(entities, serviceName);
+        GenericServiceAPIResponseEntity<String> response = updateDatabase(createStatement);
+        return response;
+    }
+
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-hadoop-metric/src/main/resources/hadoop-metric-init.sh
----------------------------------------------------------------------
diff --git a/eagle-hadoop-metric/src/main/resources/hadoop-metric-init.sh b/eagle-hadoop-metric/src/main/resources/hadoop-metric-init.sh
index 8405c19..17923c7 100755
--- a/eagle-hadoop-metric/src/main/resources/hadoop-metric-init.sh
+++ b/eagle-hadoop-metric/src/main/resources/hadoop-metric-init.sh
@@ -25,18 +25,33 @@ source $(dirname $0)/eagle-env.sh
 echo "Importing AlertDataSourceService for persist... "
 
 curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' \
- "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=AlertDataSourceService" \
+ "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=SiteApplicationService" \
   -d '
   [
      {
-        "prefix":"alertDataSource",
         "tags":{
            "site":"sandbox",
-           "dataSource":"hadoopJmxMetricDataSource"
+           "application":"hadoopJmxMetricDataSource"
         },
         "enabled": true,
-        "config":"",
-        "desc":"hadoop"
+        "config":"{}"
+     }
+  ]
+  '
+
+curl -u ${EAGLE_SERVICE_USER}:${EAGLE_SERVICE_PASSWD} -X POST -H 'Content-Type:application/json' \
+ "http://${EAGLE_SERVICE_HOST}:${EAGLE_SERVICE_PORT}/eagle-service/rest/entities?serviceName=ApplicationDescService" \
+  -d '
+  [
+     {
+        "tags":{
+           "application":"hadoopJmxMetricDataSource"
+        },
+        "desc":"hadoop jmx metric monitoring",
+        "alias":"JmxMetricMonitor",
+        "group":"METRIC",
+        "config":"{}",
+        "features":["common","metadata"]
      }
   ]
   '

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-security/eagle-security-hbase-web/src/main/java/org/apache/eagle/service/security/hbase/dao/HbaseMetadataAccessConfigDAOImpl.java
----------------------------------------------------------------------
diff --git a/eagle-security/eagle-security-hbase-web/src/main/java/org/apache/eagle/service/security/hbase/dao/HbaseMetadataAccessConfigDAOImpl.java b/eagle-security/eagle-security-hbase-web/src/main/java/org/apache/eagle/service/security/hbase/dao/HbaseMetadataAccessConfigDAOImpl.java
index 1378032..152facb 100644
--- a/eagle-security/eagle-security-hbase-web/src/main/java/org/apache/eagle/service/security/hbase/dao/HbaseMetadataAccessConfigDAOImpl.java
+++ b/eagle-security/eagle-security-hbase-web/src/main/java/org/apache/eagle/service/security/hbase/dao/HbaseMetadataAccessConfigDAOImpl.java
@@ -20,8 +20,9 @@ package org.apache.eagle.service.security.hbase.dao;
 
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.eagle.alert.entity.AlertDataSourceEntity;
+import org.apache.eagle.alert.entity.SiteApplicationServiceEntity;
 import org.apache.eagle.log.entity.ListQueryAPIResponseEntity;
+import org.apache.eagle.policy.common.Constants;
 import org.apache.eagle.security.util.BadMetadataAccessConfigException;
 import org.apache.eagle.service.generic.ListQueryResource;
 import org.slf4j.Logger;
@@ -49,9 +50,9 @@ public class HbaseMetadataAccessConfigDAOImpl {
         ListQueryResource resource = new ListQueryResource();
         /* parameters are: query, startTime, endTime, pageSzie, startRowkey, treeAgg, timeSeries, intervalmin, top, filterIfMissing,
         * parallel, metricName*/
-        String queryFormat = "AlertDataSourceService[@dataSource=\"hbaseSecurityLog\" AND @site=\"%s\"]{*}";
+        String queryFormat = Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME + "[@application=\"hbaseSecurityLog\" AND @site=\"%s\"]{*}";
         ListQueryAPIResponseEntity ret = resource.listQuery(String.format(queryFormat, site), null, null, Integer.MAX_VALUE, null, false, false, 0L, 0, false, 0, null);
-        List<AlertDataSourceEntity> list = (List<AlertDataSourceEntity>) ret.getObj();
+        List<SiteApplicationServiceEntity> list = (List<SiteApplicationServiceEntity>) ret.getObj();
         if(list == null || list.size() ==0)
             throw new BadMetadataAccessConfigException("config is empty for site " + site);
         return convert(list.get(0).getConfig());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-security/eagle-security-hdfs-auditlog/src/main/resources/application.conf
----------------------------------------------------------------------
diff --git a/eagle-security/eagle-security-hdfs-auditlog/src/main/resources/application.conf b/eagle-security/eagle-security-hdfs-auditlog/src/main/resources/application.conf
index 2f8ea8b..697b4f6 100644
--- a/eagle-security/eagle-security-hdfs-auditlog/src/main/resources/application.conf
+++ b/eagle-security/eagle-security-hdfs-auditlog/src/main/resources/application.conf
@@ -26,12 +26,12 @@
   },
   "dataSourceConfig": {
     "topic" : "sandbox_hdfs_audit_log",
-    "zkConnection" : "localhost:2181",
+    "zkConnection" : "sandbox.hortonworks.com:2181",
     "zkConnectionTimeoutMS" : 15000,
     "consumerGroupId" : "EagleConsumer",
     "fetchSize" : 1048586,
     "deserializerClass" : "org.apache.eagle.security.auditlog.HdfsAuditLogKafkaDeserializer",
-    "transactionZKServers" : "localhost",
+    "transactionZKServers" : "sandbox.hortonworks.com",
     "transactionZKPort" : 2181,
     "transactionZKRoot" : "/consumers",
     "transactionStateUpdateMS" : 2000

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/HDFSResourceUtils.java
----------------------------------------------------------------------
diff --git a/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/HDFSResourceUtils.java b/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/HDFSResourceUtils.java
index 7d57ef7..bece538 100644
--- a/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/HDFSResourceUtils.java
+++ b/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/HDFSResourceUtils.java
@@ -18,9 +18,10 @@ package org.apache.eagle.service.security.hdfs;
 
 import java.util.List;
 
+import org.apache.eagle.alert.entity.SiteApplicationServiceEntity;
+import org.apache.eagle.policy.common.Constants;
 import org.apache.eagle.service.generic.ListQueryResource;
 
-import org.apache.eagle.alert.entity.AlertDataSourceEntity;
 import org.apache.eagle.log.entity.ListQueryAPIResponseEntity;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
@@ -34,9 +35,9 @@ public class HDFSResourceUtils {
 	public static HDFSResourceAccessConfig  getConfig(String siteId ) throws Exception
 	{
 		ListQueryResource resource = new ListQueryResource();
-		String queryFormat = "AlertDataSourceService[@dataSource=\""+HDFSResourceConstants.HDFS_DATA_SOURCE+"\" AND @site=\"%s\"]{*}";
+		String queryFormat = Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME+"[@application=\""+HDFSResourceConstants.HDFS_DATA_SOURCE+"\" AND @site=\"%s\"]{*}";
 		ListQueryAPIResponseEntity ret = resource.listQuery(String.format(queryFormat, siteId), null, null,Integer.MAX_VALUE, null, false, false, 0L, 0, false, 0, null);
-		List<AlertDataSourceEntity> list = (List<AlertDataSourceEntity>) ret.getObj();
+		List<SiteApplicationServiceEntity> list = (List<SiteApplicationServiceEntity>) ret.getObj();
 		if (list == null || list.size() == 0)
 			throw new Exception("Config is empty for site " + siteId +".");
 	    

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/resolver/HDFSCommandResolver.java
----------------------------------------------------------------------
diff --git a/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/resolver/HDFSCommandResolver.java b/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/resolver/HDFSCommandResolver.java
index 23694e5..b2a2671 100644
--- a/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/resolver/HDFSCommandResolver.java
+++ b/eagle-security/eagle-security-hdfs-web/src/main/java/org/apache/eagle/service/security/hdfs/resolver/HDFSCommandResolver.java
@@ -37,6 +37,8 @@ public class HDFSCommandResolver implements AttributeResolvable<GenericAttribute
             "mkdirs", "listStatus", "setReplication", "setOwner", "setPermission", "setTimes", "setXAttr", "removeXAttr", "getXAttrs",
             "contentSummary", "createEncryptionZone", "checkAccess"};
 
+    //private final static String [] cmdStrs = {"appendToFile","cat","checksum","chgrp","chmod","chown","copyFromLocal","copyToLocal","count","cp","createSnapshot","deleteSnapshot","df","du","dus","expunge","find","get","getfacl","getfattr","getmerge","help","ls","lsr","mkdir","moveFromLocal","moveToLocal","mv","put","renameSnapshot","rm","rmdir","rmr","setfacl","setfattr","setrep","stat","tail","test","text","touchz","truncate","usage"};
+
     private final static String HDFS_CMD_RESOLVE_FORMAT_HINT = String.format("hdfs command must be in {%s}", StringUtils.join(cmdStrs, ","));
 
     private final static List<String> commands = Arrays.asList(cmdStrs);

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-security/eagle-security-hive-web/src/main/java/org/apache/eagle/service/security/hive/dao/HiveMetadataAccessConfigDAOImpl.java
----------------------------------------------------------------------
diff --git a/eagle-security/eagle-security-hive-web/src/main/java/org/apache/eagle/service/security/hive/dao/HiveMetadataAccessConfigDAOImpl.java b/eagle-security/eagle-security-hive-web/src/main/java/org/apache/eagle/service/security/hive/dao/HiveMetadataAccessConfigDAOImpl.java
index 8a6f965..d0c18ed 100644
--- a/eagle-security/eagle-security-hive-web/src/main/java/org/apache/eagle/service/security/hive/dao/HiveMetadataAccessConfigDAOImpl.java
+++ b/eagle-security/eagle-security-hive-web/src/main/java/org/apache/eagle/service/security/hive/dao/HiveMetadataAccessConfigDAOImpl.java
@@ -16,8 +16,9 @@
  */
 package org.apache.eagle.service.security.hive.dao;
 
-import org.apache.eagle.alert.entity.AlertDataSourceEntity;
+import org.apache.eagle.alert.entity.SiteApplicationServiceEntity;
 import org.apache.eagle.log.entity.ListQueryAPIResponseEntity;
+import org.apache.eagle.policy.common.Constants;
 import org.apache.eagle.service.generic.ListQueryResource;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
@@ -36,11 +37,11 @@ public class HiveMetadataAccessConfigDAOImpl implements HiveMetadataAccessConfig
         ListQueryResource resource = new ListQueryResource();
         /* parameters are: query, startTime, endTime, pageSzie, startRowkey, treeAgg, timeSeries, intervalmin, top, filterIfMissing,
         * parallel, metricName*/
-        ListQueryAPIResponseEntity ret = resource.listQuery("AlertDataSourceService[@dataSource=\"hiveQueryLog\"]{*}", null, null, Integer.MAX_VALUE, null, false, false, 0L, 0, false,
+        ListQueryAPIResponseEntity ret = resource.listQuery(Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME+"[@application=\"hiveQueryLog\"]{*}", null, null, Integer.MAX_VALUE, null, false, false, 0L, 0, false,
                 0, null);
-        List<AlertDataSourceEntity> list = (List<AlertDataSourceEntity>) ret.getObj();
-        Map<String, HiveMetadataAccessConfig> siteHiveConfigs = new HashMap<String, HiveMetadataAccessConfig>();
-        for(AlertDataSourceEntity hiveDataSource : list){
+        List<SiteApplicationServiceEntity> list = (List<SiteApplicationServiceEntity>) ret.getObj();
+        Map<String, HiveMetadataAccessConfig> siteHiveConfigs = new HashMap<>();
+        for(SiteApplicationServiceEntity hiveDataSource : list){
             siteHiveConfigs.put(hiveDataSource.getTags().get("site"), convert(hiveDataSource.getConfig()));
         }
         return siteHiveConfigs;
@@ -48,7 +49,7 @@ public class HiveMetadataAccessConfigDAOImpl implements HiveMetadataAccessConfig
 
     private HiveMetadataAccessConfig convert(String config){
         ObjectMapper mapper = new ObjectMapper();
-        HiveMetadataAccessConfig c = null;
+        HiveMetadataAccessConfig c;
         try {
             c = mapper.readValue(config, HiveMetadataAccessConfig.class);
         }catch(Exception ex){
@@ -64,12 +65,12 @@ public class HiveMetadataAccessConfigDAOImpl implements HiveMetadataAccessConfig
         ListQueryResource resource = new ListQueryResource();
         /* parameters are: query, startTime, endTime, pageSzie, startRowkey, treeAgg, timeSeries, intervalmin, top, filterIfMissing,
         * parallel, metricName*/
-        String queryFormat = "AlertDataSourceService[@dataSource=\"hiveQueryLog\" AND @site=\"%s\"]{*}";
+        String queryFormat = Constants.SITE_APPLICATION_SERVICE_ENDPOINT_NAME+"[@application=\"hiveQueryLog\" AND @site=\"%s\"]{*}";
         ListQueryAPIResponseEntity ret = resource.listQuery(String.format(queryFormat, site), null, null, Integer.MAX_VALUE, null, false, false, 0L, 0, false,
                 0, null);
-        List<AlertDataSourceEntity> list = (List<AlertDataSourceEntity>) ret.getObj();
+        List<SiteApplicationServiceEntity> list = (List<SiteApplicationServiceEntity>) ret.getObj();
         if(list == null || list.size() ==0)
             throw new BadHiveMetadataAccessConfigException("config is empty for site " + site);
         return convert(list.get(0).getConfig());
     }
-}
+}
\ No newline at end of file



[9/9] incubator-eagle git commit: EAGLE-167 refactor metric collector script

Posted by hd...@apache.org.
EAGLE-167 refactor metric collector script

https://issues.apache.org/jira/browse/EAGLE-167

Author: @haoch <ha...@apache.org>
Reviewer: @haoch <ha...@apache.org>


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

Branch: refs/heads/branch-0.3.0
Commit: 1965b4aff6f27b971effc7bd5be0fd773a686789
Parents: d37643b
Author: Hao Chen <ha...@apache.org>
Authored: Fri Feb 26 16:17:09 2016 +0800
Committer: Hao Chen <ha...@apache.org>
Committed: Fri Feb 26 16:17:09 2016 +0800

----------------------------------------------------------------------
 eagle-external/hadoop_jmx_collector/base.py     | 250 -------------
 .../hadoop_jmx_collector/hadoop_ha_checker.py   |  10 +-
 .../hadoop_jmx_collector/hadoop_jmx_kafka.py    | 228 ++++--------
 .../hadoop_jmx_collector/metric_collector.py    | 354 +++++++++++++++++++
 .../hadoop_jmx_collector/metric_extensions.py   |  95 -----
 5 files changed, 420 insertions(+), 517 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1965b4af/eagle-external/hadoop_jmx_collector/base.py
----------------------------------------------------------------------
diff --git a/eagle-external/hadoop_jmx_collector/base.py b/eagle-external/hadoop_jmx_collector/base.py
deleted file mode 100644
index bbb2344..0000000
--- a/eagle-external/hadoop_jmx_collector/base.py
+++ /dev/null
@@ -1,250 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-# !/usr/bin/python
-
-
-# !/usr/bin/python
-
-import os
-import re
-import time
-import json
-import urllib2
-import sys
-import socket
-import types
-import httplib
-import logging
-import threading
-
-# load six
-sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '', 'lib/six'))
-import six
-
-# load kafka-python
-sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '', 'lib/kafka-python'))
-from kafka import KafkaClient, SimpleProducer, SimpleConsumer
-
-logging.basicConfig(level=logging.INFO,
-                    format='%(asctime)s %(name)-12s %(levelname)-6s %(message)s',
-                    datefmt='%m-%d %H:%M')
-
-class Helper:
-    def __init__(self):
-        pass
-
-    @staticmethod
-    def load_config(config_file="config.json"):
-        """
-
-        :param config_file:
-        :return:
-        """
-        # read the self-defined filters
-        script_dir = os.path.dirname(__file__)
-        rel_path = "./" + config_file
-        abs_file_path = os.path.join(script_dir, rel_path)
-        if not os.path.isfile(abs_file_path):
-            logging.error(abs_file_path + " doesn't exist, please rename config-sample.json to config.json")
-            exit(1)
-        f = open(abs_file_path, 'r')
-        json_file = f.read()
-        f.close()
-        config = json.loads(json_file)
-        return config
-
-    @staticmethod
-    def is_number(str):
-        """
-
-        :param str:
-        :return:
-        """
-        try:
-            if str == None or isinstance(str, (bool)):
-                return False
-            float(str)
-            return True
-        except:
-            return False
-
-    @staticmethod
-    def http_get(host, port, https=False, path=None):
-        """
-        Read url by GET method
-
-        :param path:
-        :param url:
-        :param https:
-        :return:
-        """
-        url = ":".join([host, str(port)])
-        result = None
-        response = None
-        try:
-            if https:
-                logging.info("Reading https://" + str(url) + path)
-                c = httplib.HTTPSConnection(url, timeout=57)
-                c.request("GET", path)
-                response = c.getresponse()
-            else:
-                logging.info("Reading http://" + str(url) + path)
-                response = urllib2.urlopen("http://" + str(url) + path, timeout=57)
-            logging.debug("Got response")
-            result = response.read()
-        finally:
-            if response is not None:
-                response.close()
-            return result
-
-
-class JmxReader(object):
-    def __init__(self, host, port, https=False):
-        self.host = host
-        self.port = port
-        self.https = https
-        self.jmx_json = None
-        self.jmx_beans = None
-        self.jmx_raw = None
-
-    def open(self):
-        """
-        :return: JmxReader
-        """
-        self.read_raw()
-        self.set_raw(self.jmx_raw)
-        return self
-
-    def read_raw(self):
-        """
-        transfer the json string into dict
-        :param host:
-        :param port:
-        :param https:
-        :return: text
-        """
-        self.jmx_raw = Helper.http_get(self.host, self.port, self.https, "/jmx?anonymous=true")
-        if self.jmx_raw is None:
-            raise Exception("Response from " + url + " is None")
-        return self
-
-    def set_raw(self, text):
-        self.jmx_json = json.loads(text)
-        self.jmx_beans = self.jmx_json[u'beans']
-        self.jmx_raw = text
-        return self
-
-    def get_jmx_beans(self):
-        return self.jmx_beans
-
-    def get_jmx_bean_by_name(self, name):
-        for bean in self.jmx_beans:
-            if bean.has_key("name") and bean["name"] == name:
-                return bean
-
-
-class YarnWSReader:
-    def __init__(self, host, port, https=False):
-        self.host = host
-        self.port = port
-        self.https = https
-
-    def read_cluster_info(self):
-        cluster_info = Helper.http_get(self.host, self.port, self.https, "/ws/v1/cluster/info")
-        logging.debug(cluster_info)
-        return json.loads(cluster_info)
-
-class MetricSender(object):
-    def __init__(self, config):
-        pass
-
-    def open(self):
-        pass
-
-    def send(self, msg):
-        raise Exception("should be overrode")
-
-    def close(self):
-        pass
-
-
-class KafkaMetricSender(MetricSender):
-    def __init__(self, config):
-        super(KafkaMetricSender, self).__init__(config)
-        kafka_config = config["output"]["kafka"]
-        # default topic
-        self.topic = kafka_config["topic"].encode('utf-8')
-        # producer
-        self.broker_list = kafka_config["brokerList"]
-        self.kafka_client = None
-        self.kafka_producer = None
-
-    def open(self):
-        self.kafka_client = KafkaClient(self.broker_list, timeout=59)
-        self.kafka_producer = SimpleProducer(self.kafka_client, batch_send=True, batch_send_every_n=500,
-                                             batch_send_every_t=30)
-
-    def send(self, msg):
-        self.kafka_producer.send_messages(self.topic, json.dumps(msg))
-
-    def close(self):
-        if self.kafka_producer is not None:
-            self.kafka_producer.stop()
-        if self.kafka_client is not None:
-            self.kafka_client.close()
-
-
-class MetricCollector(threading.Thread):
-    def __init__(self):
-        threading.Thread.__init__(self)
-
-        self.config = Helper.load_config()
-        self.sender = KafkaMetricSender(self.config)
-
-    def start(self):
-        try:
-            self.sender.open()
-            self.run()
-        finally:
-            self.sender.close()
-
-    def collect(self, msg):
-        if not msg.has_key("timestamp"):
-            msg["timestamp"] = int(round(time.time() * 1000))
-        if msg.has_key("value"):
-            msg["value"] = float(str(msg["value"]))
-        if not msg.has_key("host") or len(msg["host"]) == 0:
-            msg["host"] = socket.getfqdn()
-        if not msg.has_key("site"):
-            msg["site"] = self.config["env"]["site"]
-
-        self.sender.send(msg)
-
-    def run(self):
-        raise Exception("`run` method should be overrode by sub-class before being called")
-
-class Runner(object):
-    @staticmethod
-    def run(*threads):
-        """
-        Execute concurrently
-
-        :param threads:
-        :return:
-        """
-        for thread in threads:
-            thread.start()
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1965b4af/eagle-external/hadoop_jmx_collector/hadoop_ha_checker.py
----------------------------------------------------------------------
diff --git a/eagle-external/hadoop_jmx_collector/hadoop_ha_checker.py b/eagle-external/hadoop_jmx_collector/hadoop_ha_checker.py
index 063279b..2a5096b 100644
--- a/eagle-external/hadoop_jmx_collector/hadoop_ha_checker.py
+++ b/eagle-external/hadoop_jmx_collector/hadoop_ha_checker.py
@@ -1,3 +1,5 @@
+# !/usr/bin/python
+#
 # 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.
@@ -12,14 +14,11 @@
 # 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.
+#
 
-
-# !/usr/bin/python
-
-from base import MetricCollector, JmxReader, YarnWSReader, Runner
+from metric_collector import MetricCollector, JmxReader, YarnWSReader, Runner
 import logging
 
-
 class HadoopNNHAChecker(MetricCollector):
     def run(self):
         if not self.config["env"].has_key("name_node"):
@@ -126,5 +125,4 @@ class HadoopRMHAChecker(MetricCollector):
         })
 
 if __name__ == '__main__':
-    # HadoopNNHAChecker().start()
     Runner.run(HadoopNNHAChecker(), HadoopRMHAChecker())
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1965b4af/eagle-external/hadoop_jmx_collector/hadoop_jmx_kafka.py
----------------------------------------------------------------------
diff --git a/eagle-external/hadoop_jmx_collector/hadoop_jmx_kafka.py b/eagle-external/hadoop_jmx_collector/hadoop_jmx_kafka.py
index b342a5e..8837c30 100644
--- a/eagle-external/hadoop_jmx_collector/hadoop_jmx_kafka.py
+++ b/eagle-external/hadoop_jmx_collector/hadoop_jmx_kafka.py
@@ -1,5 +1,5 @@
-#!/usr/bin/python
-
+# !/usr/bin/python
+#
 # 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.
@@ -14,170 +14,66 @@
 # 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.
+#
 
-import os
-import re
-import time
+from metric_collector import JmxMetricCollector,JmxMetricListener,Runner
 import json
-import urllib2
-import sys
-import socket
-import types
-import httplib
-
-# load six
-sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '', 'lib/six'))
-import six
-
-# load kafka-python
-sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '', 'lib/kafka-python'))
-from kafka import KafkaClient, SimpleProducer, SimpleConsumer
-
-from util_func import *
-from metric_extensions import *
-
-
-DATA_TYPE = "hadoop"
-
-def readUrl(url, https=False):
-    jmxjson = 'error'
-    try:
-        if https:
-            print "Reading https://" + str(url) + "/jmx?anonymous=true"
-            c = httplib.HTTPSConnection(url, timeout=57)
-            c.request("GET", "/jmx?anonymous=true")
-            response = c.getresponse()
-        else:
-            print "Reading http://" + str(url) + "/jmx?anonymous=true"
-            response = urllib2.urlopen("http://" + url + '/jmx?anonymous=true', timeout=57)
-    except Exception, e:
-        print 'Reason: ', e
-    else:
-        # everything is fine
-        jmxjson = response.read()
-        response.close()
-    finally:
-        return jmxjson
-
-
-def get_metric_prefix_name(mbean_attribute, context):
-    mbean_list = list(prop.split("=", 1)
-                      for prop in mbean_attribute.split(","))
-    metric_prefix_name = None
-    if context == "":
-        metric_prefix_name = '.'.join([i[1] for i in mbean_list])
-    else:
-        name_index = [i[0] for i in mbean_list].index('name')
-        mbean_list[name_index][1] = context
-        metric_prefix_name = '.'.join([i[1] for i in mbean_list])
-    return (DATA_TYPE + "." + metric_prefix_name).replace(" ","").lower()
-
-def parse_hadoop_jmx(producer, topic, config, beans, dataMap, fat_bean):
-    selected_group = [s.encode('utf-8') for s in config[u'filter'].get('monitoring.group.selected')]
-    #print selected_group
-
-    for bean in beans:
-        kafka_dict = dataMap.copy()
-
-        # mbean is of the form "domain:key=value,...,foo=bar"
-        mbean = bean[u'name']
-        mbean_domain, mbean_attribute = mbean.rstrip().split(":", 1)
-        mbean_domain = mbean_domain.lower()
-
-        # print mbean_domain
-        if mbean_domain not in selected_group:
-            # print "Unexpected mbean domain = %s on %s" % (mbean_domain, mbean)
-            continue
-        fat_bean.update(bean)
-        context = bean.get("tag.Context", "")
-        metric_prefix_name = get_metric_prefix_name(mbean_attribute, context)
-
-        # print kafka_dict
-        for key, value in bean.iteritems():
-            #print key, value
-            key = key.lower()
-            if re.match(r'tag.*', key):
-                continue
-
-            if mbean_domain == 'hadoop' and re.match(r'^namespace', key):
-                #print key
-                items = re.split('_table_', key)
-                key = items[1]
-                items = re.split('_region_', key)
-                kafka_dict['table'] = items[0]
-                items = re.split('_metric_', items[1])
-                kafka_dict['region'] = items[0]
-                key = items[1]
-
-            metric = metric_prefix_name + '.' + key
-
-            single_metric_callback(producer, topic, kafka_dict, metric, value)
-
-def get_jmx_beans(host, port, https):
-    # port = inputConfig.get('port')
-    # https = inputConfig.get('https')
-    url = host + ':' + port
-    #print url
-
-    jmxjson = readUrl(url, https)
-
-    if jmxjson == 'error':
-        print 'jmx load error'
-
-    # transfer the json string into dict
-    jmx = json.loads(jmxjson)
-    beans = jmx[u'beans']
-
-    return beans
-
-def main():
-    kafka = None
-    producer = None
-    topic = None
-    brokerList = None
-
-    try:
-        #start = time.clock()
-
-        # read the kafka.ini
-        if (len(sys.argv) > 1):
-            config = load_config(sys.argv[1])
-        else:
-            config = load_config('config.json')
-        #print config
-
-        site = config[u'env'].get('site').encode('utf-8')
-        component = config[u'input'].get('component').encode('utf-8')
-
-        if config[u'input'].has_key("host"):
-            host = config[u'input'].get("host").encode('utf-8')
-        else:
-            host = socket.getfqdn()
-
-        port = config[u'input'].get('port')
-        https = config[u'input'].get('https')
-        kafkaConfig = config[u'output'].get(u'kafka')
-        if kafkaConfig != None :
-            brokerList = kafkaConfig.get('brokerList')
-            topic = kafkaConfig.get('topic').encode('utf-8')
-
-        beans = get_jmx_beans(host, port, https)
-        #print brokerList
-        if brokerList != None:
-            kafka, producer = kafka_connect(brokerList)
-
-        default_metric = {"site": site, "host": host, "timestamp": '', "component": component, "metric": '', "value": ''}
-        fat_bean = dict()
-        parse_hadoop_jmx(producer, topic, config, beans, default_metric, fat_bean)
-        metrics_bean_callback(producer, topic, default_metric, fat_bean)
-    # except Exception, e:
-    #     print 'main except: ', e
-    finally:
-        if kafka != None and producer != None:
-            kafka_close(kafka, producer)
-
-        #elapsed = (time.clock() - start)
-        #print("Time used:",elapsed)
 
-if __name__ == "__main__":
-    main()
+class NNSafeModeMetric(JmxMetricListener):
+    def on_metric(self, metric):
+        if metric["metric"] == "hadoop.namenode.fsnamesystemstate.fsstate":
+            if metric["value"] == "safeMode":
+                metric["value"] = 1
+            else:
+                metric["value"] = 0
+            self.collector.collect(metric)
+
+
+class NNHAMetric(JmxMetricListener):
+    PREFIX = "hadoop.namenode.fsnamesystem"
+
+    def on_bean(self, bean):
+        if bean["name"] == "Hadoop:service=NameNode,name=FSNamesystem":
+            if bean[u"tag.HAState"] == "active":
+                self.collector.on_bean_kv(self.PREFIX, "hastate", 0)
+            else:
+                self.collector.on_bean_kv(self.PREFIX, "hastate", 1)
+
+
+class MemortUsageMetric(JmxMetricListener):
+    PREFIX = "hadoop.namenode.jvm"
+
+    def on_bean(self, bean):
+        if bean["name"] == "Hadoop:service=NameNode,name=JvmMetrics":
+            memnonheapusedusage = round(float(bean['MemNonHeapUsedM']) / float(bean['MemNonHeapMaxM']) * 100.0, 2)
+            self.collector.on_bean_kv(self.PREFIX, "memnonheapusedusage", memnonheapusedusage)
+            memnonheapcommittedusage = round(float(bean['MemNonHeapCommittedM']) / float(bean['MemNonHeapMaxM']) * 100,
+                                             2)
+            self.collector.on_bean_kv(self.PREFIX, "memnonheapcommittedusage", memnonheapcommittedusage)
+            memheapusedusage = round(float(bean['MemHeapUsedM']) / float(bean['MemHeapMaxM']) * 100, 2)
+            self.collector.on_bean_kv(self.PREFIX, "memheapusedusage", memheapusedusage)
+            memheapcommittedusage = round(float(bean['MemHeapCommittedM']) / float(bean['MemHeapMaxM']) * 100, 2)
+            self.collector.on_bean_kv(self.PREFIX, "memheapcommittedusage", memheapcommittedusage)
+
+
+class JournalTransactionInfoMetric(JmxMetricListener):
+    PREFIX = "hadoop.namenode.journaltransaction"
+
+    def on_bean(self, bean):
+        if bean.has_key("JournalTransactionInfo"):
+            JournalTransactionInfo = json.loads(bean.get("JournalTransactionInfo"))
+            LastAppliedOrWrittenTxId = float(JournalTransactionInfo.get("LastAppliedOrWrittenTxId"))
+            MostRecentCheckpointTxId = float(JournalTransactionInfo.get("MostRecentCheckpointTxId"))
+            self.collector.on_bean_kv(self.PREFIX, "LastAppliedOrWrittenTxId", LastAppliedOrWrittenTxId)
+            self.collector.on_bean_kv(self.PREFIX, "MostRecentCheckpointTxId", MostRecentCheckpointTxId)
+
+
+if __name__ == '__main__':
+    collector = JmxMetricCollector()
+    collector.register(
+            NNSafeModeMetric(),
+            NNHAMetric(),
+            MemortUsageMetric(),
+            JournalTransactionInfoMetric()
+    )
+    Runner.run(collector)

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1965b4af/eagle-external/hadoop_jmx_collector/metric_collector.py
----------------------------------------------------------------------
diff --git a/eagle-external/hadoop_jmx_collector/metric_collector.py b/eagle-external/hadoop_jmx_collector/metric_collector.py
new file mode 100644
index 0000000..939b811
--- /dev/null
+++ b/eagle-external/hadoop_jmx_collector/metric_collector.py
@@ -0,0 +1,354 @@
+# !/usr/bin/python
+#
+# 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.
+#
+
+import os
+import re
+import time
+import json
+import urllib2
+import sys
+import socket
+import types
+import httplib
+import logging
+import threading
+
+# load six
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '', 'lib/six'))
+import six
+
+# load kafka-python
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '', 'lib/kafka-python'))
+from kafka import KafkaClient, SimpleProducer, SimpleConsumer
+
+logging.basicConfig(level=logging.INFO,
+                    format='%(asctime)s %(name)-12s %(levelname)-6s %(message)s',
+                    datefmt='%m-%d %H:%M')
+
+
+class Helper:
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def load_config(config_file="config.json"):
+        """
+
+        :param config_file:
+        :return:
+        """
+        # read the self-defined filters
+        script_dir = os.path.dirname(__file__)
+        rel_path = "./" + config_file
+        abs_file_path = os.path.join(script_dir, rel_path)
+        if not os.path.isfile(abs_file_path):
+            logging.error(abs_file_path + " doesn't exist, please rename config-sample.json to config.json")
+            exit(1)
+        f = open(abs_file_path, 'r')
+        json_file = f.read()
+        f.close()
+        config = json.loads(json_file)
+        return config
+
+    @staticmethod
+    def is_number(str):
+        """
+
+        :param str:
+        :return:
+        """
+        try:
+            if str == None or isinstance(str, (bool)):
+                return False
+            float(str)
+            return True
+        except:
+            return False
+
+    @staticmethod
+    def http_get(host, port, https=False, path=None):
+        """
+        Read url by GET method
+
+        :param path:
+        :param url:
+        :param https:
+        :return:
+        """
+        url = ":".join([host, str(port)])
+        result = None
+        response = None
+        try:
+            if https:
+                logging.info("Reading https://" + str(url) + path)
+                c = httplib.HTTPSConnection(url, timeout=57)
+                c.request("GET", path)
+                response = c.getresponse()
+            else:
+                logging.info("Reading http://" + str(url) + path)
+                response = urllib2.urlopen("http://" + str(url) + path, timeout=57)
+            logging.debug("Got response")
+            result = response.read()
+        finally:
+            if response is not None:
+                response.close()
+            return result
+
+
+class JmxReader(object):
+    def __init__(self, host, port, https=False):
+        self.host = host
+        self.port = port
+        self.https = https
+        self.jmx_json = None
+        self.jmx_beans = None
+        self.jmx_raw = None
+
+    def open(self):
+        """
+        :return: JmxReader
+        """
+        self.read_raw()
+        self.set_raw(self.jmx_raw)
+        return self
+
+    def read_raw(self):
+        """
+        transfer the json string into dict
+        :param host:
+        :param port:
+        :param https:
+        :return: text
+        """
+        self.jmx_raw = Helper.http_get(self.host, self.port, self.https, "/jmx?anonymous=true")
+        if self.jmx_raw is None:
+            raise Exception("Response from " + url + " is None")
+        return self
+
+    def set_raw(self, text):
+        self.jmx_json = json.loads(text)
+        self.jmx_beans = self.jmx_json[u'beans']
+        self.jmx_raw = text
+        return self
+
+    def get_jmx_beans(self):
+        return self.jmx_beans
+
+    def get_jmx_bean_by_name(self, name):
+        for bean in self.jmx_beans:
+            if bean.has_key("name") and bean["name"] == name:
+                return bean
+
+
+class YarnWSReader:
+    def __init__(self, host, port, https=False):
+        self.host = host
+        self.port = port
+        self.https = https
+
+    def read_cluster_info(self):
+        cluster_info = Helper.http_get(self.host, self.port, self.https, "/ws/v1/cluster/info")
+        logging.debug(cluster_info)
+        return json.loads(cluster_info)
+
+
+class MetricSender(object):
+    def __init__(self, config):
+        pass
+
+    def open(self):
+        pass
+
+    def send(self, msg):
+        raise Exception("should be overrode")
+
+    def close(self):
+        pass
+
+
+class KafkaMetricSender(MetricSender):
+    def __init__(self, config):
+        super(KafkaMetricSender, self).__init__(config)
+        kafka_config = config["output"]["kafka"]
+        # default topic
+        self.topic = kafka_config["topic"].encode('utf-8')
+        # producer
+        self.broker_list = kafka_config["brokerList"]
+        self.kafka_client = None
+        self.kafka_producer = None
+
+    def open(self):
+        self.kafka_client = KafkaClient(self.broker_list, timeout=59)
+        self.kafka_producer = SimpleProducer(self.kafka_client, batch_send=True, batch_send_every_n=500,
+                                             batch_send_every_t=30)
+
+    def send(self, msg):
+        self.kafka_producer.send_messages(self.topic, json.dumps(msg))
+
+    def close(self):
+        if self.kafka_producer is not None:
+            self.kafka_producer.stop()
+        if self.kafka_client is not None:
+            self.kafka_client.close()
+
+
+class MetricCollector(threading.Thread):
+    def __init__(self):
+        threading.Thread.__init__(self)
+        self.config = Helper.load_config()
+        self.sender = KafkaMetricSender(self.config)
+        self.fqdn = socket.getfqdn()
+        self.init(self.config)
+
+    def init(self, config):
+        pass
+
+    def start(self):
+        try:
+            self.sender.open()
+            self.run()
+        finally:
+            self.sender.close()
+
+    def collect(self, msg):
+        if not msg.has_key("timestamp"):
+            msg["timestamp"] = int(round(time.time() * 1000))
+        if msg.has_key("value"):
+            msg["value"] = float(str(msg["value"]))
+        if not msg.has_key("host") or len(msg["host"]) == 0:
+            msg["host"] = self.fqdn
+        if not msg.has_key("site"):
+            msg["site"] = self.config["env"]["site"]
+
+        self.sender.send(msg)
+
+    def run(self):
+        raise Exception("`run` method should be overrode by sub-class before being called")
+
+
+class Runner(object):
+    @staticmethod
+    def run(*threads):
+        """
+        Execute concurrently
+
+        :param threads:
+        :return:
+        """
+        for thread in threads:
+            thread.start()
+
+class JmxMetricCollector(MetricCollector):
+    selected_domain = None
+    component = None
+    https = False
+    port = None
+    listeners = []
+
+    def init(self, config):
+        if config["input"].has_key("host"):
+            self.host = config["input"]["host"]
+        else:
+            self.host = self.fqdn
+        self.port = config["input"]["port"]
+        self.https = config["input"]["https"]
+        self.component = config["input"]["component"]
+        self.selected_domain = [s.encode('utf-8') for s in config[u'filter'].get('monitoring.group.selected')]
+        self.listeners = []
+
+    def register(self, *listeners):
+        """
+        :param listeners: type of HadoopJmxListener
+        :return:
+        """
+        for listener in listeners:
+            listener.init(self)
+            self.listeners.append(listener)
+
+    def run(self):
+        try:
+            beans = JmxReader(self.host, self.port, self.https).open().get_jmx_beans()
+            self.on_beans(beans)
+        except Exception as e:
+            logging.exception("Failed to read jmx for " + self.host)
+
+    def filter_bean(self, bean, mbean_domain):
+        return mbean_domain in self.selected_domain
+
+    def on_beans(self, beans):
+        for bean in beans:
+            self.on_bean(bean)
+
+    def on_bean(self, bean):
+        # mbean is of the form "domain:key=value,...,foo=bar"
+        mbean = bean[u'name']
+        mbean_domain, mbean_attribute = mbean.rstrip().split(":", 1)
+        mbean_domain = mbean_domain.lower()
+
+        if not self.filter_bean(bean, mbean_domain):
+            return
+
+        context = bean.get("tag.Context", "")
+        metric_prefix_name = self.__build_metric_prefix(mbean_attribute, context)
+
+        # print kafka_dict
+        for key, value in bean.iteritems():
+            self.on_bean_kv(metric_prefix_name, key, value)
+
+        for listener in self.listeners:
+            listener.on_bean(bean.copy())
+
+    def on_bean_kv(self, prefix, key, value):
+        # Skip Tags
+        if re.match(r'tag.*', key):
+            return
+        metric_name = (prefix + '.' + key).lower()
+        self.on_metric({
+            "metric": metric_name,
+            "value": value
+        })
+
+    def on_metric(self, metric):
+        metric["component"] = self.component
+
+        if Helper.is_number(metric["value"]):
+            self.collect(metric)
+
+        for listener in self.listeners:
+            listener.on_metric(metric.copy())
+
+    def __build_metric_prefix(self, mbean_attribute, context):
+        mbean_list = list(prop.split("=", 1) for prop in mbean_attribute.split(","))
+        if context == "":
+            metric_prefix_name = '.'.join([i[1] for i in mbean_list])
+        else:
+            name_index = [i[0] for i in mbean_list].index('name')
+            mbean_list[name_index][1] = context
+            metric_prefix_name = '.'.join([i[1] for i in mbean_list])
+        return ("hadoop." + metric_prefix_name).replace(" ", "").lower()
+
+
+class JmxMetricListener:
+    def init(self, collector):
+        self.collector = collector
+
+    def on_bean(self, bean):
+        pass
+
+    def on_metric(self, metric):
+        pass
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/1965b4af/eagle-external/hadoop_jmx_collector/metric_extensions.py
----------------------------------------------------------------------
diff --git a/eagle-external/hadoop_jmx_collector/metric_extensions.py b/eagle-external/hadoop_jmx_collector/metric_extensions.py
deleted file mode 100644
index 0028204..0000000
--- a/eagle-external/hadoop_jmx_collector/metric_extensions.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-# !/usr/bin/python
-
-from util_func import *
-import json
-
-
-# Metric Parsing Callback Entry
-def single_metric_callback(producer, topic, kafka_dict, metric, value):
-    if isNumber(value):
-        numeric_metric_callack(producer, topic, kafka_dict, metric, value)
-    else:
-        nonnumeric_metric_callack(producer, topic, kafka_dict, metric, value)
-
-
-def metrics_bean_callback(producer, topic, metric, bean):
-    cal_mem_usage(producer, topic, bean, metric, "hadoop.namenode.jvm")
-    journal_transaction_info(producer, topic, bean, metric, "hadoop.namenode.journaltransaction")
-    nn_hastate(producer,topic,bean,metric,"hadoop.namenode.fsnamesystem")
-
-#################################################
-# Metric Parsing Extensions
-#################################################
-
-def numeric_metric_callack(producer, topic, kafka_dict, metric, value):
-    # Send out numeric value directly
-    send_output_message(producer, topic, kafka_dict, metric, value)
-
-
-def nonnumeric_metric_callack(producer, topic, kafka_dict, metric, value):
-    nn_safe_mode_metric(producer, topic, kafka_dict, metric, value)
-
-def nn_safe_mode_metric(producer, topic, kafka_dict, metric, value):
-    if metric == "hadoop.namenode.fsnamesystemstate.fsstate":
-        if value == "safeMode":
-            value = 1
-        else:
-            value = 0
-
-        send_output_message(producer, topic, kafka_dict, metric, value)
-
-def nn_hastate(producer, topic, bean, metricMap, metric_prefix_name="hadoop.namenode.fsnamesystem"):
-    kafka_dict = metricMap.copy()
-    if bean[u'tag.HAState'] == "active":
-        value = 0
-    else:
-        value = 1
-
-    send_output_message(producer, topic, kafka_dict, metric_prefix_name + ".hastate", value)
-
-def cal_mem_usage(producer, topic, bean, metricMap, metric_prefix_name):
-    kafka_dict = metricMap.copy()
-    PercentVal = None
-    PercentVal = round(float(bean['MemNonHeapUsedM']) / float(bean['MemNonHeapMaxM']) * 100.0, 2)
-    send_output_message(producer, topic, kafka_dict, metric_prefix_name + ".MemNonHeapUsedUsage", PercentVal)
-
-    PercentVal = round(float(bean['MemNonHeapCommittedM']) / float(bean['MemNonHeapMaxM']) * 100, 2)
-    send_output_message(producer, topic, kafka_dict, metric_prefix_name + ".MemNonHeapCommittedUsage", PercentVal)
-
-    PercentVal = round(float(bean['MemHeapUsedM']) / float(bean['MemHeapMaxM']) * 100, 2)
-    send_output_message(producer, topic, kafka_dict, metric_prefix_name + ".MemHeapUsedUsage", PercentVal)
-
-    PercentVal = round(float(bean['MemHeapCommittedM']) / float(bean['MemHeapMaxM']) * 100, 2)
-    send_output_message(producer, topic, kafka_dict, metric_prefix_name + ".MemHeapCommittedUsage", PercentVal)
-
-
-def journal_transaction_info(producer, topic, bean, metric, metric_prefix_name):
-    new_metric = metric.copy()
-    if bean.has_key("JournalTransactionInfo"):
-        JournalTransactionInfo = json.loads(bean.get("JournalTransactionInfo"))
-
-        LastAppliedOrWrittenTxId = int(JournalTransactionInfo.get("LastAppliedOrWrittenTxId"))
-        MostRecentCheckpointTxId = int(JournalTransactionInfo.get("MostRecentCheckpointTxId"))
-
-        send_output_message(producer, topic, new_metric, metric_prefix_name + ".LastAppliedOrWrittenTxId",
-                            LastAppliedOrWrittenTxId)
-        send_output_message(producer, topic, new_metric, metric_prefix_name + ".MostRecentCheckpointTxId",
-                            MostRecentCheckpointTxId)
-    else:
-        raise Exception("JournalTransactionInfo not found")


[5/9] incubator-eagle git commit: EAGLE-139 EAGLE-163 Eagle UI Modularization and fix bugs in policy extensions

Posted by hd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/common/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/common/controller.js
new file mode 100644
index 0000000..50f377c
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/common/controller.js
@@ -0,0 +1,1023 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var featureControllers = angular.module("featureControllers");
+	var feature = featureControllers.register("common");
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+	feature.service("Policy", function(Entities) {
+		var Policy = function () {};
+
+		Policy.updatePolicyStatus = function(policy, status) {
+			$.dialog({
+				title: "Confirm",
+				content: "Do you want to " + (status ? "enable" : "disable") + " policy[" + policy.tags.policyId + "]?",
+				confirm: true
+			}, function(ret) {
+				if(ret) {
+					policy.enabled = status;
+					Entities.updateEntity("AlertDefinitionService", policy);
+				}
+			});
+		};
+		Policy.deletePolicy = function(policy, callback) {
+			$.dialog({
+				title: "Confirm",
+				content: "Do you want to delete policy[" + policy.tags.policyId + "]?",
+				confirm: true
+			}, function(ret) {
+				if(ret) {
+					policy.enabled = status;
+					Entities.deleteEntity("AlertDefinitionService", policy)._promise.finally(function() {
+						if(callback) {
+							callback(policy);
+						}
+					});
+				}
+			});
+		};
+		return Policy;
+	});
+
+	// ==============================================================
+	// =                          Policies                          =
+	// ==============================================================
+
+	// ========================= Policy List ========================
+	feature.navItem("policyList", "Policies", "list");
+	feature.controller('policyList', function(PageConfig, Site, $scope, Application, Entities, Policy) {
+		PageConfig.pageTitle = "Policy List";
+		PageConfig.pageSubTitle = Site.current().tags.site;
+
+		// Initial load
+		$scope.policyList = [];
+		$scope.application = Application.current();
+
+		// List policies
+		var _policyList = Entities.queryEntities("AlertDefinitionService", {site: Site.current().tags.site, dataSource: $scope.application.tags.application});
+		_policyList._promise.then(function() {
+			$.each(_policyList, function(i, policy) {
+				policy.__mailStr = common.getValueByPath(common.parseJSON(policy.notificationDef, {}), "0.recipients", "");
+				policy.__mailList = policy.__mailStr.trim() === "" ? [] : policy.__mailStr.split(/[,;]/);
+				policy.__expression = common.parseJSON(policy.policyDef, {}).expression;
+
+				$scope.policyList.push(policy);
+			});
+		});
+		$scope.policyList._promise = _policyList._promise;
+
+		// Function
+		$scope.searchFunc = function(item) {
+			var key = $scope.search;
+			if(!key) return true;
+
+			var _key = key.toLowerCase();
+			function _hasKey(item, path) {
+				var _value = common.getValueByPath(item, path, "").toLowerCase();
+				return _value.indexOf(_key) !== -1;
+			}
+			return _hasKey(item, "tags.policyId") || _hasKey(item, "__expression") || _hasKey(item, "desc") || _hasKey(item, "owner") || _hasKey(item, "__mailStr");
+		};
+
+		$scope.updatePolicyStatus = Policy.updatePolicyStatus;
+		$scope.deletePolicy = function(policy) {
+			Policy.deletePolicy(policy, function(policy) {
+				var _index = $scope.policyList.indexOf(policy);
+				$scope.policyList.splice(_index, 1);
+			});
+		};
+	});
+
+	// ======================= Policy Detail ========================
+	feature.controller('policyDetail', function(PageConfig, Site, $scope, $wrapState, Entities, Policy, nvd3) {
+		var MAX_PAGESIZE = 10000;
+
+		PageConfig.pageTitle = "Policy Detail";
+		PageConfig.lockSite = true;
+		PageConfig
+			.addNavPath("Policy List", "/common/policyList")
+			.addNavPath("Policy Detail");
+
+		$scope.chartConfig = {
+			chart: "line",
+			xType: "time"
+		};
+
+		// Query policy
+		if($wrapState.param.filter) {
+			$scope.policyList = Entities.queryEntity("AlertDefinitionService", $wrapState.param.filter);
+		} else {
+			$scope.policyList = Entities.queryEntities("AlertDefinitionService", {
+				policyId: $wrapState.param.policy,
+				site: $wrapState.param.site,
+				alertExecutorId: $wrapState.param.executor
+			});
+		}
+
+		$scope.policyList._promise.then(function() {
+			var policy = null;
+
+			if($scope.policyList.length === 0) {
+				$.dialog({
+					title: "OPS!",
+					content: "Policy not found!"
+				}, function() {
+					location.href = "#/common/policyList";
+				});
+				return;
+			} else {
+				policy = $scope.policyList[0];
+
+				policy.__mailStr = common.getValueByPath(common.parseJSON(policy.notificationDef, {}), "0.recipients", "");
+				policy.__mailList = policy.__mailStr.trim() === "" ? [] : policy.__mailStr.split(/[,;]/);
+				policy.__expression = common.parseJSON(policy.policyDef, {}).expression;
+
+				$scope.policy = policy;
+				Site.current(Site.find($scope.policy.tags.site));
+				console.log($scope.policy);
+			}
+
+			// Visualization
+			var _endTime = app.time.now().hour(23).minute(59).second(59).millisecond(0);
+			var _startTime = _endTime.clone().subtract(1, "month").hour(0).minute(0).second(0).millisecond(0);
+			var _cond = {
+				dataSource: policy.tags.dataSource,
+				policyId: policy.tags.policyId,
+				_startTime: _startTime,
+				_endTime: _endTime
+			};
+
+			// > eagle.policy.eval.count
+			$scope.policyEvalSeries = nvd3.convert.eagle([Entities.querySeries("GenericMetricService", $.extend({_metricName: "eagle.policy.eval.count"}, _cond), "@cluster", "sum(value)", 60 * 24)]);
+
+			// > eagle.policy.eval.fail.count
+			$scope.policyEvalFailSeries = nvd3.convert.eagle([Entities.querySeries("GenericMetricService", $.extend({_metricName: "eagle.policy.eval.fail.count"}, _cond), "@cluster", "sum(value)", 60 * 24)]);
+
+			// > eagle.alert.count
+			$scope.alertSeries = nvd3.convert.eagle([Entities.querySeries("GenericMetricService", $.extend({_metricName: "eagle.alert.count"}, _cond), "@cluster", "sum(value)", 60 * 24)]);
+
+			// > eagle.alert.fail.count
+			$scope.alertFailSeries = nvd3.convert.eagle([Entities.querySeries("GenericMetricService", $.extend({_metricName: "eagle.alert.fail.count"}, _cond), "@cluster", "sum(value)", 60 * 24)]);
+
+			// Alert list
+			$scope.alertList = Entities.queryEntities("AlertService", {
+				site: Site.current().tags.site,
+				dataSource: policy.tags.dataSource,
+				policyId: policy.tags.policyId,
+				_pageSize: MAX_PAGESIZE,
+				_duration: 1000 * 60 * 60 * 24 * 30,
+				__ETD: 1000 * 60 * 60 * 24
+			});
+		});
+
+		// Function
+		$scope.updatePolicyStatus = Policy.updatePolicyStatus;
+		$scope.deletePolicy = function(policy) {
+			Policy.deletePolicy(policy, function() {
+				location.href = "#/common/policyList";
+			});
+		};
+	});
+
+	// ======================== Policy Edit =========================
+	function policyCtrl(create, PageConfig, Site, Policy, $scope, $wrapState, $q, Entities, Application, Authorization) {
+		PageConfig.pageTitle = create ? "Policy Create" : "Policy Edit";
+		PageConfig.pageSubTitle = Site.current().tags.site;
+		PageConfig
+			.addNavPath("Policy List", "/common/policyList")
+			.addNavPath("Policy Edit");
+
+		var _winTimeDesc = "Number unit[millisecond, sec, min, hour, day, month, year]. e.g. 23 sec";
+		var _winTimeRegex = /^\d+\s+(millisecond|milliseconds|second|seconds|sec|minute|minutes|min|hour|hours|day|days|week|weeks|month|months|year|years)$/;
+		var _winTimeDefaultValue = '10 min';
+		$scope.config = {
+			window: [
+				// Display name, window type, required columns[Title, Description || "LONG_FIELD", Regex check, default value]
+				{
+					title: "Message Time Slide",
+					description: "Using timestamp filed from input is used as event's timestamp",
+					type: "externalTime",
+					fields:[
+						{title: "Field", defaultValue: "timestamp", hide: true},
+						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue}
+					]
+				},
+				{
+					title: "System Time Slide",
+					description: "Using System time is used as timestamp for event's timestamp",
+					type: "time",
+					fields:[
+						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue}
+					]
+				},
+				{
+					title: "System Time Batch",
+					description: "Same as System Time Window except the policy is evaluated at fixed duration",
+					type: "timeBatch",
+					fields:[
+						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue}
+					]
+				},
+				{
+					title: "Length Slide",
+					description: "The slide window has a fixed length",
+					type: "length",
+					fields:[
+						{title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/}
+					]
+				},
+				{
+					title: "Length Batch",
+					description: "Same as Length window except the policy is evaluated in batch mode when fixed event count reached",
+					type: "lengthBatch",
+					fields:[
+						{title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/}
+					]
+				}
+			]
+		};
+
+		$scope.create = create;
+		$scope.encodedRowkey = $wrapState.param.filter;
+
+		$scope.step = 0;
+		$scope.dataSources = {};
+		$scope.policy = {};
+
+		// ==========================================
+		// =            Data Preparation            =
+		// ==========================================
+		// Steam list
+		var _streamList = Entities.queryEntities("AlertStreamService", {dataSource: Application.current().tags.application});
+		var _executorList = Entities.queryEntities("AlertExecutorService", {dataSource: Application.current().tags.application});
+		$scope.streamList = _streamList;
+		$scope.executorList = _executorList;
+		$scope.streamReady = false;
+
+		$q.all([_streamList._promise, _executorList._promise]).then(function() {
+			// Map executor with stream
+			$.each(_executorList, function(i, executor) {
+				$.each(_streamList, function(j, stream) {
+					if(stream.tags.dataSource === executor.tags.dataSource && stream.tags.streamName === executor.tags.streamName) {
+						stream.alertExecutor = executor;
+						return false;
+					}
+				});
+			});
+
+			// Fill stream list
+			$.each(_streamList, function(i, unit) {
+				var _srcStreamList = $scope.dataSources[unit.tags.dataSource] = $scope.dataSources[unit.tags.dataSource] || [];
+				_srcStreamList.push(unit);
+			});
+
+			$scope.streamReady = true;
+
+			// ==========================================
+			// =                Function                =
+			// ==========================================
+			function _findStream(dataSource, streamName) {
+				var _streamList = $scope.dataSources[dataSource];
+				if(!_streamList) return null;
+
+				for(var i = 0 ; i < _streamList.length ; i += 1) {
+					if(_streamList[i].tags.streamName === streamName) {
+						return _streamList[i];
+					}
+				}
+				return null;
+			}
+
+			// ==========================================
+			// =              Step control              =
+			// ==========================================
+			$scope.steps = [
+				// >> Select stream
+				{
+					title: "Select Stream",
+					ready: function() {
+						return $scope.streamReady;
+					},
+					init: function() {
+						$scope.policy.__.streamName = $scope.policy.__.streamName ||
+							common.array.find($scope.policy.tags.dataSource, _streamList, "tags.dataSource").tags.streamName;
+					},
+					nextable: function() {
+						var _streamName = common.getValueByPath($scope.policy, "__.streamName");
+						if(!_streamName) return false;
+
+						// Detect stream in current data source list
+						return !!common.array.find(_streamName, $scope.dataSources[$scope.policy.tags.dataSource], "tags.streamName");
+					}
+				},
+
+				// >> Define Alert Policy
+				{
+					title: "Define Alert Policy",
+					init: function() {
+						// Normal mode will fetch meta list
+						if(!$scope.policy.__.advanced) {
+							var _stream = _findStream($scope.policy.tags.dataSource, $scope.policy.__.streamName);
+							$scope._stream = _stream;
+
+							if(!_stream.metas) {
+								_stream.metas = Entities.queryEntities("AlertStreamSchemaService", {dataSource: $scope.policy.tags.dataSource, streamName: $scope.policy.__.streamName});
+								_stream.metas._promise.then(function() {
+									_stream.metas.sort(function(a, b) {
+										if(a.tags.attrName < b.tags.attrName) {
+											return -1;
+										} else if(a.tags.attrName > b.tags.attrName) {
+											return 1;
+										}
+										return 0;
+									});
+								});
+							}
+						}
+					},
+					ready: function() {
+						if(!$scope.policy.__.advanced) {
+							return $scope._stream.metas._promise.$$state.status === 1;
+						}
+						return true;
+					},
+					nextable: function() {
+						if($scope.policy.__.advanced) {
+							// Check stream source
+							$scope._stream = null;
+							$.each($scope.dataSources[$scope.policy.tags.dataSource], function(i, stream) {
+								if(($scope.policy.__._expression || "").indexOf(stream.tags.streamName) !== -1) {
+									$scope._stream = stream;
+									return false;
+								}
+							});
+							return $scope._stream;
+						} else {
+							// Window
+							if($scope.policy.__.windowConfig) {
+								var _winMatch = true;
+								var _winConds = $scope.getWindow().fields;
+								$.each(_winConds, function(i, cond) {
+									if(!(cond.val || "").match(cond.regex)) {
+										_winMatch = false;
+										return false;
+									}
+								});
+								if(!_winMatch) return false;
+
+								// Aggregation
+								if($scope.policy.__.groupAgg) {
+									if(!$scope.policy.__.groupAggPath ||
+										!$scope.policy.__.groupCondOp ||
+										!$scope.policy.__.groupCondVal) {
+										return false;
+									}
+								}
+							}
+						}
+						return true;
+					}
+				},
+
+				// >> Configuration & Notification
+				{
+					title: "Configuration & Notification",
+					nextable: function() {
+						return !!$scope.policy.tags.policyId;
+					}
+				}
+			];
+
+			// ==========================================
+			// =              Policy Logic              =
+			// ==========================================
+			_streamList._promise.then(function() {
+				// Initial policy entity
+				if(create) {
+					$scope.policy = {
+						__: {
+							toJSON: jQuery.noop,
+							conditions: {},
+							notification: [],
+							dedupe: {
+								alertDedupIntervalMin: 10,
+								emailDedupIntervalMin: 10
+							},
+							policy: {},
+							window: "externalTime",
+							group: "",
+							groupAgg: "count",
+							groupAggPath: "timestamp",
+							groupCondOp: ">=",
+							groupCondVal: "2"
+						},
+						desc: "",
+						enabled: true,
+						prefix: "alertdef",
+						remediationDef: "",
+						tags: {
+							dataSource: Application.current().tags.application,
+							policyType: "siddhiCEPEngine"
+						}
+					};
+
+					// If configured data source
+					if($wrapState.param.dataSrc) {
+						$scope.policy.tags.dataSource = $wrapState.param.dataSrc;
+						if(common.array.find($wrapState.param.dataSrc, Site.current().dataSrcList, "tags.dataSource")) {
+							setTimeout(function() {
+								$scope.changeStep(0, 2, false);
+								$scope.$apply();
+							}, 1);
+						}
+					}
+
+					// Start step
+					$scope.changeStep(0, 1, false);
+					console.log($scope.policy);
+				} else {
+					var _policy = Entities.queryEntity("AlertDefinitionService", $scope.encodedRowkey);
+					_policy._promise.then(function() {
+						if(_policy.length) {
+							$scope.policy = _policy[0];
+							$scope.policy.__ = {
+								toJSON: jQuery.noop
+							};
+
+							Site.current(Site.find($scope.policy.tags.site));
+						} else {
+							$.dialog({
+								title: "OPS",
+								content: "Policy not found!"
+							}, function() {
+								$location.path("/common/policyList");
+								$scope.$apply();
+							});
+							return;
+						}
+
+						// === Revert inner data ===
+						// >> De-dupe
+						$scope.policy.__.dedupe = common.parseJSON($scope.policy.dedupeDef, {});
+
+						// >> Notification
+						$scope.policy.__.notification = common.parseJSON($scope.policy.notificationDef, []);
+
+						// >> Policy
+						var _policyUnit = $scope.policy.__.policy = common.parseJSON($scope.policy.policyDef);
+
+						// >> Parse expression
+						$scope.policy.__.conditions = {};
+						var _condition = _policyUnit.expression.match(/from\s+(\w+)(\[(.*)])?(#window[^\)]*\))?\s+(select (\w+, )?(\w+)\((\w+)\) as [\w\d_]+ (group by (\w+) )?having ([\w\d_]+) ([<>=]+) ([^\s]+))?/);
+						var _cond_stream = _condition[1];
+						var _cond_query = _condition[3] || "";
+						var _cond_window = _condition[4];
+						var _cond_group = _condition[5];
+						var _cond_groupUnit = _condition.slice(7,14);
+
+						if(!_condition) {
+							$scope.policy.__.advanced = true;
+						} else {
+							// > StreamName
+							var _streamName = _cond_stream;
+							var _cond = _cond_query;
+
+							$scope.policy.__.streamName = _streamName;
+
+							// > Conditions
+							// Loop condition groups
+							if(_cond.trim() !== "" && /^\(.*\)$/.test(_cond)) {
+								var _condGrps = _cond.substring(1, _cond.length - 1).split(/\)\s+and\s+\(/);
+								$.each(_condGrps, function(i, line) {
+									// Loop condition cells
+									var _condCells = line.split(/\s+or\s+/);
+									$.each(_condCells, function(i, cell) {
+										var _opMatch = cell.match(/(\S*)\s*(==|!=|>|<|>=|<=|contains)\s*('(?:[^'\\]|\\.)*'|[\w\d]+)/);
+										if(!common.isEmpty(_opMatch)) {
+											var _key = _opMatch[1];
+											var _op = _opMatch[2];
+											var _val = _opMatch[3];
+											var _conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || [];
+											var _type = "";
+											if(_val.match(/'.*'/)) {
+												_val = _val.slice(1, -1);
+												_type = "string";
+											} else if(_val === "true" || _val === "false") {
+												var _regexMatch = _key.match(/^str:regexp\((\w+),'(.*)'\)/);
+												var _containsMatch = _key.match(/^str:contains\((\w+),'(.*)'\)/);
+												var _mathes = _regexMatch || _containsMatch;
+												if(_mathes) {
+													_key = _mathes[1];
+													_val = _mathes[2];
+													_type = "string";
+													_op = _regexMatch ? "regex" : "contains";
+													_conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || [];
+												} else {
+													_type = "bool";
+												}
+											} else {
+												_type = "number";
+											}
+											_conds.push($scope._CondUnit(_key, _op, _val, _type));
+										}
+									});
+								});
+							} else if(_cond_query !== "") {
+								$scope.policy.__.advanced = true;
+							}
+						}
+
+						if($scope.policy.__.advanced) {
+							$scope.policy.__._expression = _policyUnit.expression;
+						} else {
+							// > window
+							if(!_cond_window) {
+								$scope.policy.__.window = "externalTime";
+								$scope.policy.__.group = "";
+								$scope.policy.__.groupAgg = "count";
+								$scope.policy.__.groupAggPath = "timestamp";
+								$scope.policy.__.groupCondOp = ">=";
+								$scope.policy.__.groupCondVal = "2";
+							} else {
+								try {
+									$scope.policy.__.windowConfig = true;
+
+									var _winCells = _cond_window.match(/\.(\w+)\((.*)\)/);
+									$scope.policy.__.window = _winCells[1];
+									var _winConds = $scope.getWindow().fields;
+									$.each(_winCells[2].split(","), function(i, val) {
+										_winConds[i].val = val;
+									});
+
+									// Group
+									if(_cond_group) {
+										$scope.policy.__.group = _cond_groupUnit[3];
+										$scope.policy.__.groupAgg = _cond_groupUnit[0];
+										$scope.policy.__.groupAggPath = _cond_groupUnit[1];
+										$scope.policy.__.groupAggAlias = _cond_groupUnit[4] === "aggValue" ? "" : _cond_groupUnit[4];
+										$scope.policy.__.groupCondOp = _cond_groupUnit[5];
+										$scope.policy.__.groupCondVal = _cond_groupUnit[6];
+									} else {
+										$scope.policy.__.group = "";
+										$scope.policy.__.groupAgg = "count";
+										$scope.policy.__.groupAggPath = "timestamp";
+										$scope.policy.__.groupCondOp = ">=";
+										$scope.policy.__.groupCondVal = "2";
+									}
+								} catch(err) {
+									$scope.policy.__.window = "externalTime";
+								}
+							}
+						}
+
+						$scope.changeStep(0, 2, false);
+						console.log($scope.policy);
+					});
+				}
+			});
+
+			// ==========================================
+			// =                Function                =
+			// ==========================================
+			// UI: Highlight select step
+			$scope.stepSelect = function(step) {
+				return step === $scope.step ? "active" : "";
+			};
+
+			// UI: Collapse all
+			$scope.collapse = function(cntr) {
+				var _list = $(cntr).find(".collapse").css("height", "auto");
+				if(_list.hasClass("in")) {
+					_list.removeClass("in");
+				} else {
+					_list.addClass("in");
+				}
+			};
+
+			// Step process. Will fetch target step attribute and return boolean
+			function _check(key, step) {
+				var _step = $scope.steps[step - 1];
+				if(!_step) return;
+
+				var _value = _step[key];
+				if(typeof _value === "function") {
+					return _value();
+				} else if(typeof _value === "boolean") {
+					return _value;
+				}
+				return true;
+			}
+			// Check step is ready. Otherwise will display load animation
+			$scope.stepReady = function(step) {
+				return _check("ready", step);
+			};
+			// Check whether process next step. Otherwise will disable next button
+			$scope.checkNextable = function(step) {
+				return !_check("nextable", step);
+			};
+			// Switch step
+			$scope.changeStep = function(step, targetStep, check) {
+				if(check === false || _check("checkStep", step)) {
+					$scope.step =  targetStep;
+
+					_check("init", targetStep);
+				}
+			};
+
+			// Window
+			$scope.getWindow = function() {
+				if(!$scope.policy || !$scope.policy.__) return null;
+				return common.array.find($scope.policy.__.window, $scope.config.window, "type");
+			};
+
+			// Aggregation
+			$scope.groupAggPathList = function() {
+				return $.grep(common.getValueByPath($scope, "_stream.metas", []), function(meta) {
+					return $.inArray(meta.attrType, ['long','integer','number', 'double', 'float']) !== -1;
+				});
+			};
+
+			$scope.updateGroupAgg = function() {
+				$scope.policy.__.groupAggPath = $scope.policy.__.groupAggPath || common.getValueByPath($scope.groupAggPathList()[0], "tags.attrName");
+
+				if($scope.policy.__.groupAgg === 'count') {
+					$scope.policy.__.groupAggPath = 'timestamp';
+				}
+			};
+
+			// Resolver
+			$scope.resolverTypeahead = function(value, resolver) {
+				var _resolverList = Entities.query("stream/attributeresolve", {
+					site: Site.current().tags.site,
+					resolver: resolver,
+					query: value
+				});
+				return _resolverList._promise.then(function() {
+					return _resolverList;
+				});
+			};
+
+			// Used for input box when pressing enter
+			$scope.conditionPress = function(event) {
+				if(event.which == 13) {
+					setTimeout(function() {
+						$(event.currentTarget).closest(".input-group").find("button").click();
+					}, 1);
+				}
+			};
+			// Check whether has condition
+			$scope.hasCondition = function(key, type) {
+				var _list = common.getValueByPath($scope.policy.__.conditions, key, []);
+				if(_list.length === 0) return false;
+
+				if(type === "bool") {
+					return !_list[0].ignored();
+				}
+				return true;
+			};
+			// Condition unit definition
+			$scope._CondUnit = function(key, op, value, type) {
+				return {
+					key: key,
+					op: op,
+					val: value,
+					type: type,
+					ignored: function() {
+						return this.type === "bool" && this.val === "none";
+					},
+					getVal: function() {
+						return this.type === "string" ? "'" + this.val + "'" : this.val;
+					},
+					toString: function() {
+						return this.op + " " + this.getVal();
+					},
+					toCondString: function() {
+						var _op = this.op === "=" ? "==" : this.op;
+						if(_op === "regex") {
+							return "str:regexp(" + this.key + "," + this.getVal() + ")==true";
+						} else if(_op === "contains") {
+							return "str:contains(" + this.key + "," + this.getVal() + ")==true";
+						} else {
+							return this.key + " " + _op + " " + this.getVal();
+						}
+					}
+				};
+			};
+			// Add condition for policy
+			$scope.addCondition = function(key, op, value, type) {
+				if(value === "" || value === undefined) return false;
+
+				var _condList = $scope.policy.__.conditions[key] = $scope.policy.__.conditions[key] || [];
+				_condList.push($scope._CondUnit(key, op, value, type));
+				return true;
+			};
+			// Convert condition list to description string
+			$scope.parseConditionDesc = function(key) {
+				return $.map($scope.policy.__.conditions[key] || [], function(cond) {
+					if(!cond.ignored()) return "[" + cond.toString() + "]";
+				}).join(" or ");
+			};
+
+			// To query
+			$scope.toQuery = function() {
+				if(!$scope.policy.__) return "";
+
+				if($scope.policy.__.advanced) return $scope.policy.__._expression;
+
+				// > Query
+				var _query = $.map(common.getValueByPath($scope.policy, "__.conditions", {}), function(list) {
+					var _conds = $.map(list, function(cond) {
+						if(!cond.ignored()) return cond.toCondString();
+					});
+					if(_conds.length) {
+						return "(" + _conds.join(" or ") + ")";
+					}
+				}).join(" and ");
+				if(_query) {
+					_query = "[" + _query + "]";
+				}
+
+				// > Window
+				var _window = $scope.getWindow();
+				var _windowStr = "";
+				if($scope.policy.__.windowConfig) {
+					_windowStr = $.map(_window.fields, function(field) {
+						return field.val;
+					}).join(",");
+					_windowStr = "#window." + _window.type + "(" + _windowStr + ")";
+
+					// > Group
+					if($scope.policy.__.group) {
+						_windowStr += common.template(" select ${group}, ${groupAgg}(${groupAggPath}) as ${groupAggAlias} group by ${group} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", {
+							group: $scope.policy.__.group,
+							groupAgg: $scope.policy.__.groupAgg,
+							groupAggPath: $scope.policy.__.groupAggPath,
+							groupCondOp: $scope.policy.__.groupCondOp,
+							groupCondVal: $scope.policy.__.groupCondVal,
+							groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue"
+						});
+					} else {
+						_windowStr += common.template(" select ${groupAgg}(${groupAggPath}) as ${groupAggAlias} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", {
+							groupAgg: $scope.policy.__.groupAgg,
+							groupAggPath: $scope.policy.__.groupAggPath,
+							groupCondOp: $scope.policy.__.groupCondOp,
+							groupCondVal: $scope.policy.__.groupCondVal,
+							groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue"
+						});
+					}
+				} else {
+					_windowStr = " select *";
+				}
+
+				return common.template("from ${stream}${query}${window} insert into outputStream;", {
+					stream: $scope.policy.__.streamName,
+					query: _query,
+					window: _windowStr
+				});
+			};
+
+			// ==========================================
+			// =             Update Policy              =
+			// ==========================================
+			// dedupeDef notificationDef policyDef
+			$scope.finishPolicy = function() {
+				$scope.lock = true;
+
+				// dedupeDef
+				$scope.policy.dedupeDef = JSON.stringify($scope.policy.__.dedupe);
+
+				// notificationDef
+				$scope.policy.__.notification = $scope.policy.__.notification || [];
+				var _notificationUnit = $scope.policy.__.notification[0];
+				if(_notificationUnit) {
+					_notificationUnit.flavor = "email";
+					_notificationUnit.id = "email_1";
+					_notificationUnit.tplFileName = "";
+				}
+				$scope.policy.notificationDef = JSON.stringify($scope.policy.__.notification);
+
+				// policyDef
+				$scope.policy.__._dedupTags = $scope.policy.__._dedupTags || {};
+				$scope.policy.__.policy = {
+					expression: $scope.toQuery(),
+					type: "siddhiCEPEngine"
+				};
+				$scope.policy.policyDef = JSON.stringify($scope.policy.__.policy);
+
+				// alertExecutorId
+				if($scope._stream.alertExecutor) {
+					$scope.policy.tags.alertExecutorId = $scope._stream.alertExecutor.tags.alertExecutorId;
+				} else {
+					$scope.lock = false;
+					$.dialog({
+						title: "OPS!",
+						content: "Alert Executor not defined! Please check 'AlertExecutorService'!"
+					});
+					return;
+				}
+
+				// site
+				$scope.policy.tags.site = $scope.policy.tags.site || Site.current().tags.site;
+
+				// owner
+				$scope.policy.owner = Authorization.userProfile.username;
+
+				// Update function
+				function _updatePolicy() {
+					Entities.updateEntity("AlertDefinitionService", $scope.policy)._promise.success(function(data) {
+						$.dialog({
+							title: "Success",
+							content: (create ? "Create" : "Update") + " success!"
+						}, function() {
+							if(data.success) {
+								location.href = "#/common/policyList";
+							} else {
+								$.dialog({
+									title: "OPS",
+									content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data)
+								});
+							}
+						});
+
+						$scope.create = create = false;
+						$scope.encodedRowkey = data.obj[0];
+					}).error(function(data) {
+						$.dialog({
+							title: "OPS",
+							content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data)
+						});
+					}).then(function() {
+						$scope.lock = false;
+					});
+				}
+
+				// Check if already exist
+				if($scope.create) {
+					var _checkList = Entities.queryEntities("AlertDefinitionService", {
+						alertExecutorId: $scope.policy.tags.alertExecutorId,
+						policyId: $scope.policy.tags.policyId,
+						policyType: "siddhiCEPEngine",
+						dataSource: $scope.policy.tags.dataSource
+					});
+					_checkList._promise.then(function() {
+						if(_checkList.length) {
+							$.dialog({
+								title: "Override Confirm",
+								content: "Already exists PolicyID '" + $scope.policy.tags.policyId + "'. Do you want to override?",
+								confirm: true
+							}, function(ret) {
+								if(ret) {
+									_updatePolicy();
+								} else {
+									$scope.lock = false;
+									$scope.$apply();
+								}
+							});
+						} else {
+							_updatePolicy();
+						}
+					});
+				} else {
+					_updatePolicy();
+				}
+			};
+		});
+	}
+
+	feature.controller('policyCreate', function(PageConfig, Site, Policy, $scope, $wrapState, $q, Entities, Application, Authorization) {
+		var _args = [true];
+		_args.push.apply(_args, arguments);
+		policyCtrl.apply(this, _args);
+	}, "policyEdit");
+	feature.controller('policyEdit', function(PageConfig, Site, Policy, $scope, $wrapState, $q, Entities, Application, Authorization) {
+		PageConfig.lockSite = true;
+		var _args = [false];
+		_args.push.apply(_args, arguments);
+		policyCtrl.apply(this, _args);
+	});
+
+	// ==============================================================
+	// =                           Alerts                           =
+	// ==============================================================
+
+	// ========================= Alert List =========================
+	feature.navItem("alertList", "Alerts", "exclamation-triangle");
+	feature.controller('alertList', function(PageConfig, Site, $scope, $wrapState, $interval, $timeout, Entities, Application) {
+		PageConfig.pageSubTitle = Site.current().tags.site;
+
+		var MAX_PAGESIZE = 10000;
+
+		// Initial load
+		$scope.application = Application.current();
+
+		$scope.alertList = [];
+		$scope.alertList.ready = false;
+
+		// Load data
+		function _loadAlerts() {
+			if($scope.alertList._promise) {
+				$scope.alertList._promise.abort();
+			}
+
+			var _list = Entities.queryEntities("AlertService", {
+				site: Site.current().tags.site,
+				dataSource: $scope.application.tags.application,
+				hostname: null,
+				_pageSize: MAX_PAGESIZE,
+				_duration: 1000 * 60 * 60 * 24 * 30,
+				__ETD: 1000 * 60 * 60 * 24
+			});
+			$scope.alertList._promise = _list._promise;
+			_list._promise.then(function() {
+				var index;
+
+				if($scope.alertList[0]) {
+					// List new alerts
+					for(index = 0 ; index < _list.length ; index += 1) {
+						var _alert = _list[index];
+						_alert.__new = true;
+						if(_alert.encodedRowkey === $scope.alertList[0].encodedRowkey) {
+							break;
+						}
+					}
+
+					if(index > 0) {
+						$scope.alertList.unshift.apply($scope.alertList, _list.slice(0, index));
+
+						// Clean up UI highlight
+						$timeout(function() {
+							$.each(_list, function(i, alert) {
+								delete alert.__new;
+							});
+						}, 100);
+					}
+				} else {
+					// List all alerts
+					$scope.alertList.push.apply($scope.alertList, _list);
+				}
+
+				$scope.alertList.ready = true;
+			});
+		}
+
+		_loadAlerts();
+		var _loadInterval = $interval(_loadAlerts, app.time.refreshInterval);
+		$scope.$on('$destroy',function(){
+			$interval.cancel(_loadInterval);
+		});
+	});
+
+	// ========================= Alert List =========================
+	feature.controller('alertDetail', function(PageConfig, Site, $scope, $wrapState, Entities) {
+		PageConfig.pageTitle = "Alert Detail";
+		PageConfig.lockSite = true;
+		PageConfig
+			.addNavPath("Alert List", "/common/alertList")
+			.addNavPath("Alert Detail");
+
+		$scope.common = common;
+
+		// Query policy
+		$scope.alertList = Entities.queryEntity("AlertService", $wrapState.param.filter);
+		$scope.alertList._promise.then(function() {
+			if($scope.alertList.length === 0) {
+				$.dialog({
+					title: "OPS!",
+					content: "Alert not found!"
+				}, function() {
+					location.href = "#/common/alertList";
+				});
+			} else {
+				$scope.alert = $scope.alertList[0];
+				Site.current(Site.find($scope.alert.tags.site));
+				console.log($scope.alert);
+			}
+		});
+
+		// UI
+		$scope.getMessageTime = function(alert) {
+			var _time = common.getValueByPath(alert, "alertContext.properties.timestamp");
+			return Number(_time);
+		};
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertDetail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertDetail.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertDetail.html
new file mode 100644
index 0000000..fee5ab1
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertDetail.html
@@ -0,0 +1,61 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<div class="box box-info">
+	<div class="box-header with-border">
+		<h3 class="box-title" id="policyId">
+			{{alert.tags.policyId}}
+			<small>{{common.format.date(alert.timestamp)}}</small>
+		</h3>
+	</div><!-- /.box-header -->
+
+	<div class="box-body">
+		<a class="btn btn-primary pull-right" href="#/common/policyDetail/?policy={{alert.tags.policyId}}&site={{alert.tags.site}}&executor={{alert.tags.alertExecutorId}}">View Policy</a>
+
+		<div class="inline-group">
+			<dl><dt>Site</dt><dd>{{alert.tags.site}}</dd></dl>
+			<dl><dt>Data Source</dt><dd>{{alert.tags.dataSource}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>Alert Time</dt><dd>{{common.format.date(alert.timestamp)}}</dd></dl>
+			<dl><dt>Message Time</dt><dd>{{common.format.date(alert.alertContext.properties.timestamp)}}</dd></dl>
+		</div>
+		<!--div class="inline-group">
+			<dl><dt>Severity</dt><dd>{{alert.alertContext.properties.severity}}</dd></dl>
+		</div-->
+		<div class="inline-group">
+			<dl><dt>Stream Name</dt><dd>{{alert.tags.sourceStreams}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>Alert Source</dt><dd>{{alert.tags.alertSource}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>User</dt><dd>{{alert.alertContext.properties.user}}</dd></dl>
+			<dl><dt>Host</dt><dd>{{alert.alertContext.properties.host}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>Event</dt><dd>{{alert.alertContext.properties.alertEvent}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>Message</dt><dd>{{alert.alertContext.properties.alertMessage}}</dd></dl>
+		</div>
+	</div><!-- /.box-body -->
+
+	<div class="overlay" ng-hide="alertList._promise.$$state.status === 1;">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertList.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertList.html
new file mode 100644
index 0000000..44f5a32
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertList.html
@@ -0,0 +1,69 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-list-alt"> </i>
+		<h3 class="box-title">
+			{{application.displayName}}
+		</h3>
+	</div>
+	<div class="box-body">
+		<p ng-show="!alertList.ready">
+			<span class="fa fa-refresh fa-spin"> </span>
+			Loading...
+		</p>
+
+		<div sorttable source="alertList" sort="-timestamp">
+			<table class="table table-bordered" ng-non-bindable>
+				<thead>
+					<tr>
+						<th width="170" sortpath="timestamp">Alert Time</th>
+						<th width="170" sortpath="alertContext.properties.timestamp">Message Time</th>
+						<th width="105" sortpath="tags.dataSource">Data Source</th>
+						<!--th width="70" sortpath="severity">Type</th-->
+						<th width="150" sortpath="tags.policyId">Policy Name</th>
+						<th width="60" sortpath="alertContext.properties.user">User</th>
+						<th width="150" sortpath="alertContext.properties.host">Host</th>
+						<th sortpath="alertContext.properties.emailMessage">Description</th>
+						<th width="50"> </th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr ng-class="{info : item.__new}">
+						<td>{{common.format.date(item.timestamp)}}</td>
+						<td>{{common.format.date(item.alertContext.properties.timestamp)}}</td>
+						<td>{{item.tags.dataSource}}</td>
+						<!--td>{{item.severity}}</td-->
+						<td class="text-nowrap">
+							<a class="fa fa-share-square-o" ng-show="item.tags.policyId"
+							href="#/common/policyDetail/?policy={{item.tags.policyId}}&site={{item.tags.site}}&executor={{item.tags.alertExecutorId}}"> </a>
+							{{item.tags.policyId}}
+						</td>
+						<td>{{item.alertContext.properties.user}}</td>
+						<td>{{item.alertContext.properties.host}}</td>
+						<td>{{item.alertContext.properties.alertMessage}}</td>
+						<td><a href="#/common/alertDetail/{{item.encodedRowkey}}">Detail</a></td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+
+	</div>
+	<!--div class="box-footer clearfix">
+	</div-->
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/common/page/dataSrcConfig.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/dataSrcConfig.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/dataSrcConfig.html
new file mode 100644
index 0000000..ba4f728
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/common/page/dataSrcConfig.html
@@ -0,0 +1,125 @@
+<div class="box box-primary" ng-repeat="_site in Site.list">
+	<div class="box-header">
+		<h3 class="box-title">{{_site.name}}</h3>
+		<div class="box-tools pull-right">
+			<button class="btn btn-box-tool" data-widget="collapse">
+				<i class="fa fa-minus"></i>
+			</button>
+			<button class="btn btn-box-tool" ng-click="showSiteEditor(_site)">
+				<i class="fa fa-wrench"></i>
+			</button>
+		</div>
+	</div><!-- /.box-header -->
+	<div class="box-body">
+		<div class="row">
+			<div class="col-md-4" ng-repeat="dataSrc in _site.dataSrcList">
+				<div class="info-box" ng-class="dataSrc.enabled !== false ? 'bg-aqua' : 'bg-gray'">
+					<span class="info-box-icon"><i class="fa fa-suitcase"></i></span>
+					<div class="info-box-content">
+						<a class="fa fa-cog config pull-right" ng-click="showDataSourceEditor(dataSrc)"></a>
+						<span class="info-box-text">{{dataSrc.desc || dataSrc.tags.dataSource}}</span>
+
+						<span class="info-box-number" ng-show="dataSrc.hide"><small>-</small></span>
+						<span class="info-box-number" ng-show="!dataSrc.hide">
+							<span class="fa fa-refresh fa-spin" ng-hide="policyStatistic._promise.$$state.status === 1"></span>
+							<span ng-show="policyStatistic._promise.$$state.status === 1">{{getPolicyCount(_site.name, dataSrc.tags.dataSource)}}</span>
+							<small>Policies</small>
+						</span>
+						<div class="progress">
+							<div class="progress-bar"></div>
+						</div>
+						<span class="progress-description" ng-show="dataSrc.hide"><small>-</small></span>
+						<span class="progress-description" ng-show="!dataSrc.hide">
+							<span class="fa fa-refresh fa-spin" ng-hide="alertStatistic._promise.$$state.status === 1"></span>
+							<span ng-show="alertStatistic._promise.$$state.status === 1">{{getAlertCount(_site.name, dataSrc.tags.dataSource)}}</span>
+							alerts in 30 Days
+						</span>
+					</div><!-- /.info-box-content -->
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+
+<!-- Modal: Create / Edit site -->
+<div class="modal fade" id="siteMDL" tabindex="-1" role="dialog">
+	<div class="modal-dialog" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">{{!_siteEntity.srcSite ? 'Create' : 'Config'}} Site</h4>
+			</div>
+			<div class="modal-body">
+				<div class="form-group">
+					<label>* Site Name</label>
+					<input type="text" class="form-control" placeholder="Site name..." ng-model="_siteEntity.name" ng-disabled="!!_siteEntity.srcSite" id="siteName">
+				</div>
+
+				<label>
+					* Data Source
+					<small class="text-muted">at least select 1 source</small>
+				</label>
+				<div class="checkbox" ng-repeat="item in _siteEntity.dataSrcList">
+					<label>
+						<input type="checkbox" value="{{item.name}}"
+							   ng-checked="item.enabled"
+							   ng-click="item.enabled = !item.enabled">
+						{{item.name}}
+					</label>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-default" data-dismiss="modal">
+					Close
+				</button>
+				<button type="button" class="btn btn-primary" ng-click="confirmUpateSite()"
+						ng-disabled="!checkUpdateSite()">
+					{{!_siteEntity.srcSite ? 'Create' : 'Update'}}
+				</button>
+			</div>
+		</div>
+	</div>
+</div>
+
+<!-- Modal: Edit data source -->
+<div class="modal fade" id="dataSrcMDL" tabindex="-1" role="dialog">
+	<div class="modal-dialog" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">{{_dataSrcEntity.tags.dataSource}}</h4>
+			</div>
+			<div class="modal-body">
+				<div class="checkbox">
+					<label>
+						<input type="checkbox"
+							   ng-checked="_dataSrcEntity.enabled"
+							   ng-click="_dataSrcEntity.enabled = !_dataSrcEntity.enabled">
+						Enabled
+					</label>
+				</div>
+
+				<div class="form-group">
+					<label>Configuration</label>
+					<textarea type="text" class="form-control" placeholder="Data source configuration..." ng-model="_dataSrcEntity.config" id="dataSrcConfig" rows="10"></textarea>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-danger pull-left" ng-click="confirmDeleteDataSource()">
+					Delete Source
+				</button>
+
+				<button type="button" class="btn btn-default" data-dismiss="modal">
+					Close
+				</button>
+				<button type="button" class="btn btn-primary" ng-click="confirmUpateDataSource()" ng-disabled="_dataSrcEntityLock">
+					Update
+				</button>
+			</div>
+		</div>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyDetail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyDetail.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyDetail.html
new file mode 100644
index 0000000..8670839
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyDetail.html
@@ -0,0 +1,172 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<div class="box box-info">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			{{policy.tags.policyId}}
+			<small>{{policy.tags.site}}</small>
+		</h3>
+	</div>
+
+	<div class="box-body">
+		<div class="row">
+			<div class="col-xs-8">
+				<div class="inline-group">
+					<dl><dt>Data Source</dt><dd>{{policy.tags.dataSource}}</dd></dl>
+					<dl><dt>Status</dt><dd>
+						<span ng-show="policy.enabled" class="text-muted"><i class="fa fa-square text-green"></i> Enabled</span>
+						<span ng-show="!policy.enabled" class="text-muted"><i class="fa fa-square text-muted"></i> Disabled</span>
+					</dd></dl>
+				</div>
+				<div class="inline-group">
+					<dl><dt>Description</dt><dd>{{policy.desc}}</dd></dl>
+				</div>
+				<div class="inline-group">
+					<dl><dt>Alert</dt><dd>
+						<a href="mailto:{{mail}}" ng-repeat="mail in policy.__mailList track by $index" style="margin-right: 10px;">{{mail}}</a>
+					</dd></dl>
+				</div>
+			</div>
+			<div class="col-xs-4 text-right" ng-show="Auth.isRole('ROLE_ADMIN')">
+				<a class="btn btn-primary" href="#/common/policyEdit/{{policy.encodedRowkey}}">Edit</a>
+				<button class="btn btn-warning" ng-show="!policy.enabled" ng-click="updatePolicyStatus(policy, true)">Enable</button>
+				<button class="btn btn-warning" ng-show="policy.enabled" ng-click="updatePolicyStatus(policy, false)">Disable</button>
+				<button class="btn btn-danger" ng-click="deletePolicy(policy)">Delete</button>
+			</div>
+		</div>
+	</div>
+
+	<div class="overlay" ng-hide="policyList._promise.$$state.status === 1;">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+
+	<div class="box-footer clearfix">
+		<a data-toggle="collapse" href="[data-id='query']">
+			View Query
+		</a>
+		<div data-id="query" class="collapse in">
+			<pre>{{policy.__expression}}</pre>
+		</div>
+	</div>
+</div>
+
+<div class="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<li class="active">
+			<a href="[data-id='visualization']" data-toggle="tab">Visualization</a>
+		</li>
+		<li>
+			<a href="[data-id='statistics']" data-toggle="tab">Statistics</a>
+		</li>
+		<li>
+			<a href="[data-id='alerts']" data-toggle="tab">Alerts</a>
+		</li>
+	</ul>
+	<div class="tab-content">
+		<div class="tab-pane active" data-id="visualization">
+			<div class="row">
+				<div class="col-xs-6">
+					<div nvd3="policyEvalSeries" data-title="Policy Eval Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
+				</div>
+				<div class="col-xs-6">
+					<div nvd3="policyEvalFailSeries" data-title="Policy Eval Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
+				</div>
+				<div class="col-xs-6">
+					<div nvd3="alertSeries" data-title="Alert Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
+				</div>
+				<div class="col-xs-6">
+					<div nvd3="alertFailSeries" data-title="Alert Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
+				</div>
+			</div>
+		</div>
+
+		<div class="tab-pane" data-id="statistics">
+			<div class="row">
+				<div class="col-xs-3">
+					<div class="info-box bg-aqua">
+						<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
+						<div class="info-box-content">
+							<span class="info-box-text">Policy Eval Count</span>
+							<span class="info-box-number">{{common.array.sum(policyEvalSeries, "1")}} <small>(Monthly)</small></span>
+							<span class="info-box-number">{{policyEvalSeries[policyEvalSeries.length - 1][1]}} <small>(Daily)</small></span>
+						</div>
+					</div>
+				</div>
+				<div class="col-xs-3">
+					<div class="info-box bg-red">
+						<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
+						<div class="info-box-content">
+							<span class="info-box-text">Policy Eval Fail Count</span>
+							<span class="info-box-number">{{common.array.sum(policyEvalFailSeries, "1")}} <small>(Monthly)</small></span>
+							<span class="info-box-number">{{policyEvalFailSeries[policyEvalFailSeries.length - 1][1]}} <small>(Daily)</small></span>
+						</div>
+					</div>
+				</div>
+				<div class="col-xs-3">
+					<div class="info-box bg-aqua">
+						<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
+						<div class="info-box-content">
+							<span class="info-box-text">Alert Count</span>
+							<span class="info-box-number">{{common.array.sum(alertSeries, "1")}} <small>(Monthly)</small></span>
+							<span class="info-box-number">{{alertSeries[alertSeries.length - 1][1]}} <small>(Daily)</small></span>
+						</div>
+					</div>
+				</div>
+				<div class="col-xs-3">
+					<div class="info-box bg-red">
+						<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
+						<div class="info-box-content">
+							<span class="info-box-text">Alert Fail Count</span>
+							<span class="info-box-number">{{common.array.sum(alertFailSeries, "1")}} <small>(Monthly)</small></span>
+							<span class="info-box-number">{{alertFailSeries[alertFailSeries.length - 1][1]}} <small>(Daily)</small></span>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<div class="tab-pane" data-id="alerts">
+			<div sorttable source="alertList" sort="-timestamp">
+				<table class="table table-bordered" ng-non-bindable>
+					<thead>
+						<tr>
+							<th width="170" sortpath="timestamp">Alert Time</th>
+							<th width="170" sortpath="alertContext.properties.timestamp">Message Time</th>
+							<th width="60" sortpath="alertContext.properties.user">User</th>
+							<th width="150" sortpath="alertContext.properties.host">Host</th>
+							<th sortpath="alertContext.properties.emailMessage">Description</th>
+							<th width="50"> </th>
+						</tr>
+					</thead>
+					<tbody>
+						<tr ng-class="{info : item.__new}">
+							<td>{{common.format.date(item.timestamp)}}</td>
+							<td>{{common.format.date(item.alertContext.properties.timestamp)}}</td>
+							<td>{{item.alertContext.properties.user}}</td>
+							<td>{{item.alertContext.properties.host}}</td>
+							<td>{{item.alertContext.properties.alertMessage}}</td>
+							<td><a href="#/common/alertDetail/{{item.encodedRowkey}}">Detail</a></td>
+						</tr>
+					</tbody>
+				</table>
+			</div>
+		</div>
+
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyEdit.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyEdit.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyEdit.html
new file mode 100644
index 0000000..e07cb0c
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyEdit.html
@@ -0,0 +1,359 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<div class="progress active" ng-show="!streamReady">
+	<div class="progress-bar progress-bar-primary progress-bar-striped" style="width: 100%">
+	</div>
+</div>
+
+<!-- Step navigation -->
+<div ng-show="streamReady">
+	<div class="row step-cntr">
+		<div class="col-sm-4" ng-repeat="step in steps">
+			<div class="step" ng-class="stepSelect($index + 1)">
+				<h1>{{$index + 1}}</h1>
+				<h2>Step {{$index + 1}}</h2>
+				<p title="{{step.title}}">{{step.title}}</p>
+			</div>
+		</div>
+	</div>
+
+	<!-- Step container -->
+	<div class="box box-info">
+		<div class="box-header with-border">
+			<h3 class="box-title">Step {{step}} - {{steps[step - 1].title}}</h3>
+		</div><!-- /.box-header -->
+
+		<div class="box-body">
+			<!-- ---------------------- Step Body Start ---------------------- -->
+
+			<!-- Step 1: Data Source -->
+			<!--div ng-show="step === 1">
+				<div class="form-group">
+					<label>Select Data Source</label>
+					<select class="form-control" ng-model="policy.tags.dataSource">
+						<option ng-repeat="dataSource in Site.current().dataSrcList" ng-hide="dataSource.hide" value="{{dataSource.tags.dataSource}}">{{dataSource.desc || dataSource.tags.dataSource}}</option>
+					</select>
+				</div>
+			</div-->
+
+			<!-- Step 1: Stream -->
+			<div ng-show="step === 1">
+				<div class="pull-right" ng-show="policy.__.advanced === undefined">
+					<span class="text-muted">or</span>
+					<a ng-click="policy.__.advanced = true;">Advanced</a>
+				</div>
+
+				<div class="form-group">
+					<label>Select Stream</label>
+					<select class="form-control" ng-model="policy.__.streamName" ng-show="!policy.__.advanced">
+						<option ng-repeat="stream in dataSources[policy.tags.dataSource]">{{stream.tags.streamName}}</option>
+					</select>
+					<select class="form-control" ng-show="policy.__.advanced" disabled="disabled">
+						<option>[Advanced Mode]</option>
+					</select>
+				</div>
+
+				<div class="checkbox" ng-show="policy.__.advanced !== undefined">
+					<label>
+						<input type="checkbox" ng-model="policy.__.advanced">
+						Advanced Mode
+					</label>
+				</div>
+			</div>
+
+			<!-- Step 2: Define Alert Policy -->
+			<div ng-show="step === 2 && !policy.__.advanced">
+				<!-- Criteria -->
+				<div>
+					<label>Match Criteria</label>
+					<a ng-click="collapse('.panel-group')">expand / collapse all</a>
+
+					<div class="panel-group panel-group-sm" role="tablist">
+						<div class="panel panel-default"
+							ng-repeat="meta in _stream.metas"
+							ng-init="op = '=='; val = null; type = (meta.attrType || '').toLowerCase();">
+							<div class="panel-heading" role="tab">
+								<h4 class="panel-title">
+									<span class="bg-navy disabled color-palette pull-right">
+										{{parseConditionDesc(meta.tags.attrName)}}
+									</span>
+
+									<a role="button" data-toggle="collapse" href="[data-name='{{meta.tags.attrName}}']" class="collapsed">
+										<span class="fa fa-square" ng-class="hasCondition(meta.tags.attrName, type) ? 'text-green' : 'text-muted'"> </span>
+										{{meta.attrDisplayName || meta.tags.attrName}}
+										<span class="fa fa-question-circle" ng-show="meta.attrDescription"
+										uib-tooltip="{{meta.attrDescription}}" tooltip-placement="right" tooltip-animation="false"> </span>
+									</a>
+								</h4>
+							</div>
+							<div data-name="{{meta.tags.attrName}}" data-type="{{meta.attrType}}" role="tabpanel" class="collapse">
+								<div class="panel-body">
+									<ul ng-show="type !== 'bool'">
+										<li ng-repeat="cond in policy.__.conditions[meta.tags.attrName]">
+											[<a ng-click="policy.__.conditions[meta.tags.attrName].splice($index, 1)">X</a>]
+											{{cond.toString()}}
+										</li>
+									</ul>
+
+									<!-- String -->
+									<div ng-if="type == 'string'">
+										<div class="input-group" style="max-width: 450px;">
+											<div class="input-group-btn">
+												<select class="form-control" ng-model="op">
+													<option ng-repeat="mark in ['==','!=','contains','regex']">{{mark}}</option>
+												</select>
+											</div>
+
+											<!-- With resolver -->
+											<input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="meta.attrValueResolver"
+												ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)"
+												uib-typeahead="item for item in resolverTypeahead($viewValue, meta.attrValueResolver)">
+											<!-- Without resolver -->
+											<input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="!meta.attrValueResolver"
+												ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)">
+
+											<span class="input-group-btn">
+												<button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type);val=null;">Add</button>
+											</span>
+										</div>
+									</div>
+
+									<!-- Number -->
+									<div ng-if="type == 'long' || type == 'integer' || type == 'number' || type == 'double' || type == 'float'">
+										<div class="input-group" style="max-width: 450px;">
+											<div class="input-group-btn">
+												<select class="form-control" ng-model="op">
+													<option ng-repeat="mark in ['==','!=','>','>=','<','<=']">{{mark}}</option>
+												</select>
+											</div>
+
+											<input type="number" class="form-control" autocomplete="off" placeholder="Number Only..." ng-model="val" ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)">
+											<span class="input-group-btn">
+												<button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type) ? val=null : void(0);">Add</button>
+											</span>
+										</div>
+									</div>
+
+									<!-- Boolean -->
+									<div ng-if="type == 'bool'" ng-init="policy.__.conditions[meta.tags.attrName] = policy.__.conditions[meta.tags.attrName] || [_CondUnit(meta.tags.attrName, '==', 'none', 'bool')]">
+										<select class="form-control" ng-model="policy.__.conditions[meta.tags.attrName][0].val" style="max-width: 100px;">
+											<option ng-repeat="bool in ['none','true','false']">{{bool}}</option>
+										</select>
+									</div>
+								</div>
+							</div>
+						</div>
+					</div>
+				</div>
+
+				<!-- Window -->
+				<div class="checkbox">
+					<label>
+						<input type="checkbox" ng-checked="policy.__.windowConfig" ng-click="policy.__.windowConfig = !policy.__.windowConfig"> Slide Window
+					</label>
+				</div>
+				<div ng-show="policy.__.windowConfig">
+					<div class="row">
+						<div class="col-md-4">
+							<div class="form-group">
+								<label>Window</label>
+								<select class="form-control" ng-model="policy.__.window"
+								uib-tooltip="{{getWindow().description}}" tooltip-animation="false">
+									<option ng-repeat="item in config.window" value="{{item.type}}">{{item.title}}</option>
+								</select>
+							</div>
+						</div>
+
+						<!-- fields -->
+						<div class="col-md-4" ng-repeat="field in getWindow().fields" ng-init="field.val = field.val || (field.defaultValue || '');" ng-hide="field.hide">
+							<div class="form-group" ng-class="{'has-warning' : !field.val || !field.val.match(field.regex)}">
+								<label>Window - {{field.title}}</label>
+								<input type="text" class="form-control" autocomplete="off" placeholder="{{field.description}}" ng-model="field.val" title="{{field.description}}">
+							</div>
+						</div>
+					</div>
+
+					<!-- Aggregation -->
+					<div class="row">
+						<div class="col-md-4">
+							<div class="form-group" ng-class="{'text-yellow' : (policy.__.groupAgg && !policy.__.groupAggPath)}">
+								<label>Aggregation</label>
+								<div class="input-group">
+									<div class="input-group-btn">
+										<select class="form-control" ng-model="policy.__.groupAgg" ng-change="updateGroupAgg()">
+											<option ng-repeat="op in ['max','min','avg','count', 'sum']">{{op}}</option>
+										</select>
+									</div>
+									<select class="form-control" ng-model="policy.__.groupAggPath" ng-class="{'has-warning' : !policy.__.groupAggPath}" id="groupAggPath"
+											ng-show="policy.__.groupAgg" ng-disabled="policy.__.groupAgg === 'count'">
+										<option ng-repeat="meta in groupAggPathList()">{{meta.tags.attrName}}</option>
+									</select>
+								</div>
+							</div>
+						</div>
+
+						<div class="col-md-4">
+							<div class="form-group" ng-class="{'text-yellow' : (!policy.__.groupCondOp || !policy.__.groupCondVal)}">
+								<label>Condition</label>
+								<div class="input-group">
+									<div class="input-group-btn">
+										<select class="form-control" ng-model="policy.__.groupCondOp" ng-class="{'has-warning' : !policy.__.groupCondOp}">
+											<option ng-repeat="op in ['>','<','>=','<=','==']">{{op}}</option>
+										</select>
+									</div>
+									<input type="text" class="form-control" ng-model="policy.__.groupCondVal" ng-class="{'has-warning' : !policy.__.groupCondVal}" />
+								</div>
+							</div>
+						</div>
+
+						<div class="col-md-4">
+							<div class="form-group">
+								<label>Alias (Optional)</label>
+								<input type="text" class="form-control" ng-model="policy.__.groupAggAlias" placeholder="Default: aggValue" />
+							</div>
+						</div>
+					</div>
+
+					<!-- Group -->
+					<div class="row">
+						<div class="col-md-4">
+							<div class="form-group">
+								<label>Group By</label>
+								<select class="form-control" ng-model="policy.__.group">
+									<option value="">None</option>
+									<option ng-repeat="meta in _stream.metas">{{meta.tags.attrName}}</option>
+								</select>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+
+			<!-- Step 2: Define Alert Policy -->
+			<div ng-show="step === 2 && policy.__.advanced">
+				<div class="form-group">
+					<label>Query Expression</label>
+					<textarea class="form-control" ng-model="policy.__._expression"
+					placeholder="Query expression. e.g. from hdfsAuditLogEventStream[(cmd=='open') and (host=='localhost' or host=='127.0.0.1')]#window.time(2 sec) select * insert into outputStream;" rows="5"></textarea>
+				</div>
+			</div>
+
+			<!-- Step 3: Email Notification -->
+			<div ng-show="step === 3">
+				<div class="row">
+					<div class="col-xs-4">
+						<div class="form-group" ng-class="{'has-warning' : !policy.tags.policyId}">
+							<label>Policy Name</label>
+							<input type="text" class="form-control" placeholder="" ng-model="policy.tags.policyId" ng-disabled="!create">
+						</div>
+					</div>
+					<div class="col-xs-3">
+						<div class="form-group">
+							<label>
+								Alert De-Dup Interval(min)
+								<span class="fa fa-question-circle" uib-tooltip="Same type alert will be De-dup in configured interval"> </span>
+							</label>
+							<input type="number" class="form-control" ng-model="policy.__.dedupe.alertDedupIntervalMin" placeholder="[Minute] Number only. Suggestion: 720">
+						</div>
+					</div>
+					<div class="col-xs-3">
+						<div class="form-group">
+							<label>
+								Email De-Dup Interval(min)
+								<span class="fa fa-question-circle" uib-tooltip="The minimun time interval of email"> </span>
+							</label>
+							<input type="number" class="form-control" ng-model="policy.__.dedupe.emailDedupIntervalMin" placeholder="[Minute] Number only. Suggestion: 1440">
+						</div>
+					</div>
+					<div class="col-xs-2">
+						<div class="form-group">
+							<label>
+								Enabled
+							</label>
+							<p>
+								<input type="checkbox" checked="checked" ng-model="policy.enabled">
+							</p>
+						</div>
+					</div>
+				</div>
+
+				<div>
+					<a data-toggle="collapse" href="[data-id='advancedDeDup']">Advanced De-Dup</a>
+					<div data-id='advancedDeDup' class="collapse">
+						<label>
+							De-Dup Key
+							<span class="fa fa-question-circle" uib-tooltip="Type of grouping alerts. If you don't know how to config, leave it default."> </span>
+						</label>
+						<div class="form-group">
+							<div class="checkbox" ng-repeat="meta in _stream.metas" ng-init="create ? policy.__._dedupTags[meta.tags.attrName] = !!meta.usedAsTag : void(0);">
+								<label>
+									<input type="checkbox" ng-model="policy.__._dedupTags[meta.tags.attrName]">
+									{{meta.tags.attrName}}
+								</label>
+							</div>
+						</div>
+					</div>
+				</div>
+
+				<hr/>
+
+				<div class="form-group">
+					<label>Sender</label>
+					<input type="text" class="form-control" value="noreply-hadoop-eagle@company1.com" placeholder="Enter Sender. e.g. sender@eaxmple.com" ng-model="policy.__.notification[0].sender">
+				</div>
+				<div class="form-group">
+					<label>Recipients</label>
+					<input type="text" class="form-control" placeholder="Enter Recipients. Split with ','. e.g. usera@example.com, userb@example.com" ng-model="policy.__.notification[0].recipients">
+				</div>
+				<div class="form-group">
+					<label>Subject</label>
+					<input type="text" class="form-control" placeholder="Enter Subject" ng-model="policy.__.notification[0].subject">
+				</div>
+				<div class="form-group">
+					<label>Description</label>
+					<textarea class="form-control" placeholder="Policy description" ng-model="policy.desc"></textarea>
+				</div>
+
+				<a data-toggle="collapse" href="[data-id='policyQuery']">
+					View Query
+				</a>
+				<div class="collapse in" data-id="policyQuery">
+					<pre>{{toQuery()}}</pre>
+				</div>
+			</div>
+
+			<!-- ----------------------- Step Body End ----------------------- -->
+		</div><!-- /.box-body -->
+
+		<div class="overlay" ng-hide="stepReady(step)">
+			<span class="fa fa-refresh fa-spin"> </span>
+		</div>
+
+		<div class="box-footer text-right">
+			<button class="btn btn-info" ng-show="step > 1" ng-click="changeStep(step, step - 1, false)" ng-disabled="lock">
+				Prev <span class="fa fa-arrow-circle-o-left"> </span>
+			</button>
+			<button class="btn btn-info" ng-show="step < steps.length" ng-click="changeStep(step, step + 1)" ng-disabled="checkNextable(step) || lock">
+				Next <span class="fa fa-arrow-circle-o-right"> </span>
+			</button>
+			<button class="btn btn-info" ng-show="step === steps.length" ng-click="finishPolicy()" ng-disabled="checkNextable(step) || lock">
+				Done <span class="fa fa-check-circle-o"> </span>
+			</button>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyList.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyList.html
new file mode 100644
index 0000000..d7141a6
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyList.html
@@ -0,0 +1,84 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-list-alt"> </i>
+		<h3 class="box-title">
+			{{application.displayName}}
+		</h3>
+	</div>
+	<div class="box-body">
+		<div class="row">
+			<div class="col-xs-3">
+				<div class="search-box">
+					<input type="search" class="form-control input-sm" placeholder="Search" ng-model="search" />
+					<span class="fa fa-search"> </span>
+				</div>
+			</div>
+			<div class="col-xs-6">
+				<div class="inline-group form-inline text-muted">
+					<dl><dt><i class="fa fa-square text-green"> </i></dt><dd>Enabled</dd></dl>
+					<dl><dt><i class="fa fa-square text-muted"> </i></dt><dd>Disabled</dd></dl>
+				</div>
+			</div>
+			<div class="col-xs-3 text-right">
+				<a class="btn btn-primary" href="#/common/policyCreate/{{!dataSource ? '' : '?dataSrc=' + dataSource.tags.dataSource}}" ng-show="Auth.isRole('ROLE_ADMIN')">
+					New Policy
+					<i class="fa fa-plus-circle"> </i>
+				</a>
+			</div>
+		</div>
+
+		<p ng-show="policyList._promise.$$state.status !== 1">
+			<span class="fa fa-refresh fa-spin"> </span>
+			Loading...
+		</p>
+
+		<div sorttable source="policyList" ng-show="policyList._promise.$$state.status === 1" search="false" searchfunc="searchFunc">
+			<table class="table table-bordered" ng-non-bindable>
+				<thead>
+					<tr>
+						<th width="30" sortpath="enabled"> </th>
+						<th width="200" sortpath="tags.policyId">Policy Name</th>
+						<th sortpath="desc">Description</th>
+						<th width="150" sortpath="owner">Owner</th>
+						<th width="170" sortpath="lastModifiedDate">Last Modified</th>
+						<th width="95" ng-show="_parent.Auth.isRole('ROLE_ADMIN')">Action</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr>
+						<td><span class='fa fa-square' ng-class="item.enabled ? 'text-green' : 'text-muted'"> </span></td>
+						<td><a href="#/common/policyDetail/{{item.encodedRowkey}}">{{item.tags.policyId}}</a></td>
+						<td>{{item.desc}}</td>
+						<td>{{item.owner}}</td>
+						<td>{{common.format.date(item.lastModifiedDate) || "-"}}</td>
+						<td ng-show="_parent.Auth.isRole('ROLE_ADMIN')">
+							<a class="fa fa-pencil btn btn-default btn-xs" uib-tooltip="Edit" tooltip-animation="false" href="#/common/policyEdit/{{item.encodedRowkey}}"> </a>
+							<button class="fa fa-play sm btn btn-default btn-xs" uib-tooltip="Enable" tooltip-animation="false" ng-show="!item.enabled" ng-click="_parent.updatePolicyStatus(item, true)"> </button>
+							<button class="fa fa-pause sm btn btn-default btn-xs" uib-tooltip="Disable" tooltip-animation="false" ng-show="item.enabled" ng-click="_parent.updatePolicyStatus(item, false)"> </button>
+							<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false" ng-click="_parent.deletePolicy(item)"> </button>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</div>
+	<!--div class="box-footer clearfix">
+	</div-->
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/metadata/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/metadata/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/metadata/controller.js
new file mode 100644
index 0000000..2797223
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/metadata/controller.js
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var featureControllers = angular.module('featureControllers');
+	var feature = featureControllers.register("metadata");
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+
+	// ==============================================================
+	// =                          Metadata                          =
+	// ==============================================================
+
+	// ======================= Policy Summary =======================
+	feature.navItem("streamList", "Metadata", "bullseye");
+	feature.controller('streamList', function(PageConfig, Site, $scope, $q, Application, Entities) {
+		PageConfig.hideSite = true;
+
+		$scope.streams = {};
+		$scope._streamEntity = null;
+		$scope._streamEntityLock = false;
+
+		// =========================================== List ===========================================
+		var _streamList = Entities.queryEntities("AlertStreamService", {dataSource: Application.current().tags.application});
+		var _streamSchemaList = Entities.queryEntities("AlertStreamSchemaService", {dataSource: Application.current().tags.application});
+		$scope.streamList = _streamList;
+		$scope.streamSchemaList = _streamSchemaList;
+
+		_streamList._promise.then(function() {
+			$.each(_streamList, function(i, stream) {
+				stream.metaList = [];
+				$scope.streams[stream.tags.dataSource + "_" + stream.tags.streamName] = stream;
+			});
+		});
+
+		$q.all([_streamList._promise, _streamSchemaList._promise]).then(function() {
+			$.each(_streamSchemaList, function(i, meta) {
+				var _stream = $scope.streams[meta.tags.dataSource + "_" + meta.tags.streamName];
+				if(_stream) {
+					_stream.metaList.push(meta);
+				} else {
+					console.warn("[Meta] Stream not match:", meta.tags.dataSource + "_" + meta.tags.streamName);
+				}
+			});
+		});
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/metadata/page/streamList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/metadata/page/streamList.html b/eagle-webservice/src/main/webapp/app/public/feature/metadata/page/streamList.html
new file mode 100644
index 0000000..9218d8d
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/metadata/page/streamList.html
@@ -0,0 +1,84 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<p ng-show="streamList._promise.$$state.status !== 1">
+	<span class="fa fa-refresh fa-spin"> </span>
+	Loading...
+</p>
+
+<div class="box box-primary" ng-repeat="stream in streams">
+	<div class="box-header with-border">
+		<h3 class="box-title">{{stream.tags.streamName}}</h3>
+	</div>
+	<div class="box-body">
+		<div class="inline-group">
+			<dl>
+				<dt>
+					Data Source
+				</dt>
+				<dd>
+					{{stream.tags.dataSource}}
+				</dd>
+			</dl>
+			<dl>
+				<dt>
+					Stream Name
+				</dt>
+				<dd>
+					{{stream.tags.streamName}}
+				</dd>
+			</dl>
+		</div>
+		<div>
+			<dl>
+				<dt>
+					Description
+				</dt>
+				<dd>
+					{{stream.desc}}
+				</dd>
+			</dl>
+		</div>
+
+		<p ng-show="streamSchemaList._promise.$$state.status !== 1">
+			<span class="fa fa-refresh fa-spin"> </span>
+			Loading...
+		</p>
+
+		<div class="list" ng-show="streamSchemaList._promise.$$state.status === 1">
+			<table class="table table-bordered">
+				<thead>
+					<tr>
+						<th width="10%">Attribute Name</th>
+						<th width="12%">Display Name</th>
+						<th width="8%">Type</th>
+						<th>Description</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr ng-repeat="meta in stream.metaList">
+						<td>{{meta.tags.attrName}}</td>
+						<td>{{meta.attrDisplayName}}</td>
+						<td><span class="label label-warning">{{meta.attrType}}</span></td>
+						<td>{{meta.attrDescription}}</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</div><!-- /.box-body -->
+	<!-- Loading (remove the following to stop the loading)-->
+</div>
\ No newline at end of file


[2/9] incubator-eagle git commit: EAGLE-139 EAGLE-163 Eagle UI Modularization and fix bugs in policy extensions

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

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/srv/authorizationSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/authorizationSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/authorizationSrv.js
new file mode 100644
index 0000000..337b567
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/authorizationSrv.js
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	serviceModule.service('Authorization', function ($rootScope, $http, $wrapState, $q) {
+		$http.defaults.withCredentials = true;
+
+		var _promise;
+		var _path = "";
+
+		var content = {
+			isLogin: true,	// Status mark. Work for UI status check, changed when eagle api return 403 authorization failure.
+			needLogin: function () {
+				console.log("[Authorization] Need Login!");
+				if(content.isLogin) {
+					_path = _path || $wrapState.path();
+					content.isLogin = false;
+					console.log("[Authorization] Call need login. Redirect...");
+					$wrapState.go("login", 99);
+				} else {
+					console.log("[Authorization] Already login state...");
+				}
+			},
+			login: function (username, password) {
+				var _hash = btoa(username + ':' + password);
+				return $http({
+					url: app.getURL('userProfile'),
+					method: "GET",
+					headers: {
+						'Authorization': "Basic " + _hash
+					}
+				}).then(function () {
+					content.isLogin = true;
+					return true;
+				}, function () {
+					return false;
+				});
+			},
+			logout: function () {
+				$http({
+					url: app.getURL('logout'),
+					method: "GET"
+				});
+			},
+			path: function (path) {
+				if (typeof path === "string") {
+					_path = path;
+				} else if (path === true) {
+					$wrapState.path(_path || "");
+					_path = "";
+				}
+			}
+		};
+
+		content.userProfile = {};
+		content.isRole = function (role) {
+			if (!content.userProfile.roles) return null;
+
+			return content.userProfile.roles[role] === true;
+		};
+
+		content.reload = function () {
+			_promise = $http({
+				url: app.getURL('userProfile'),
+				method: "GET"
+			}).then(function (data) {
+				content.userProfile = data.data;
+
+				// Role
+				content.userProfile.roles = {};
+				$.each(content.userProfile.authorities, function (i, role) {
+					content.userProfile.roles[role.authority] = true;
+				});
+
+				return content;
+			}, function(data) {
+				if(data.status === 403) {
+					content.needLogin();
+				}
+			});
+			return _promise;
+		};
+
+		content._promise = function () {
+			if (!_promise) {
+				content.reload();
+			}
+			return _promise;
+		};
+
+		content.rolePromise = function(role, rejectState) {
+			var _deferred = $q.defer();
+			var _oriPromise = content._promise();
+			_oriPromise.then(function() {
+				if(content.isRole(role)) {
+					_deferred.resolve(content);
+				} else if(content.isLogin) {
+					_deferred.resolve(content);
+					console.log("[Authorization] go landing...");
+					$wrapState.go(rejectState || "landing");
+				} else {
+					_deferred.reject(content);
+				}
+
+				return content;
+			});
+
+			return _deferred.promise;
+		};
+
+		return content;
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
new file mode 100644
index 0000000..b580f92
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	serviceModule.service('Entities', function($http, $q, $rootScope, $location, Authorization) {
+		var pkg;
+
+		// Query
+		function _query(name, kvs) {
+			kvs = kvs || {};
+			var _list = [];
+			var _condition = kvs._condition || {};
+			var _addtionalCondition = _condition.additionalCondition || {};
+			var _startTime, _endTime;
+			var _startTimeStr, _endTimeStr;
+
+			// Initial
+			// > Condition
+			delete kvs._condition;
+			if(_condition) {
+				kvs.condition = _condition.condition;
+			}
+
+			// > Values
+			if(!kvs.values) {
+				kvs.values = "*";
+			} else if($.isArray(kvs.values)) {
+				kvs.values = $.map(kvs.values, function(field) {
+					return (field[0] === "@" ? '' : '@') + field;
+				}).join(",");
+			}
+
+			var _url = app.getURL(name, kvs);
+
+			// Fill special parameters
+			// > Query by time duration
+			if(_addtionalCondition._duration) {
+				_endTime = app.time.now();
+				_startTime = _endTime.clone().subtract(_addtionalCondition._duration, "ms");
+
+				// Debug usage. Extend more time duration for end time
+				if(_addtionalCondition.__ETD) {
+					_endTime.add(_addtionalCondition.__ETD, "ms");
+				}
+
+				_addtionalCondition._startTime = _startTime;
+				_addtionalCondition._endTime = _endTime;
+
+				_startTimeStr = _startTime.format("YYYY-MM-DD HH:mm:ss");
+				_endTimeStr = _endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
+
+				_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
+			} else if(_addtionalCondition._startTime && _addtionalCondition._endTime) {
+				_startTimeStr = _addtionalCondition._startTime.format("YYYY-MM-DD HH:mm:ss");
+				_endTimeStr = _addtionalCondition._endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
+
+				_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
+			}
+
+			// > Query contains metric name
+			if(_addtionalCondition._metricName) {
+				_url += "&metricName=" + _addtionalCondition._metricName;
+			}
+
+			// > Customize page size
+			if(_addtionalCondition._pageSize) {
+				_url = _url.replace(/pageSize=\d+/, "pageSize=" + _addtionalCondition._pageSize);
+			}
+
+			// AJAX
+			var canceler = $q.defer();
+			_list._promise = $http.get(_url, {timeout: canceler.promise}).then(function(status) {
+				_list.push.apply(_list, status.data.obj);
+				return _list;
+			});
+			_list._promise.abort = function() {
+				canceler.resolve();
+			};
+
+			_list._promise.then(function() {}, function(data) {
+				if(data.status === 403) {
+					Authorization.needLogin();
+				}
+			});
+
+			return _list;
+		}
+		function _post(url, entities) {
+			var _list = [];
+			_list._promise = $http({
+				method: 'POST',
+				url: url,
+				headers: {
+					"Content-Type": "application/json"
+				},
+				data: entities
+			}).success(function(data) {
+				_list.push.apply(_list, data.obj);
+			});
+			return _list;
+		}
+		function _delete(url) {
+			var _list = [];
+			_list._promise = $http({
+				method: 'DELETE',
+				url: url,
+				headers: {
+					"Content-Type": "application/json"
+				}
+			}).success(function(data) {
+				_list.push.apply(_list, data.obj);
+			});
+			return _list;
+		}
+		function ParseCondition(condition) {
+			var _this = this;
+			_this.condition = "";
+			_this.additionalCondition = {};
+
+			if(typeof condition === "string") {
+				_this.condition = condition;
+			} else {
+				_this.condition = $.map(condition, function(value, key) {
+					if(!key.match(/^_/)) {
+						if(value === undefined || value === null) {
+							return '@' + key + '=~".*"';
+						} else {
+							return '@' + key + '="' + value + '"';
+						}
+					} else {
+						_this.additionalCondition[key] = value;
+						return null;
+					}
+				}).join(" AND ");
+			}
+			return _this;
+		}
+
+		pkg = {
+			_query: _query,
+			_post: _post,
+
+			updateEntity: function(serviceName, entities, config) {
+				var _url;
+				config = config || {};
+				if(!$.isArray(entities)) entities = [entities];
+
+				// Post clone entities
+				var _entities = $.map(entities, function(entity) {
+					var _entity = {};
+
+					// Clone variables
+					$.each(entity, function(key) {
+						// Skip inner variables
+						if(!key.match(/^__/)) {
+							_entity[key] = entity[key];
+						}
+					});
+
+					// Add timestamp
+					if(config.timestamp !== false) {
+						if(config.createTime !== false && !_entity.createdTime) {
+							_entity.createdTime = new moment().valueOf();
+						}
+						if(config.lastModifiedDate !== false) {
+							_entity.lastModifiedDate = new moment().valueOf();
+						}
+					}
+
+					return _entity;
+				});
+
+				// Check for url hook
+				if(config.hook) {
+					_url = app.getUpdateURL(serviceName);
+				} else {
+					_url = app.getURL("updateEntity", {serviceName: serviceName});
+				}
+
+				return _post(_url, _entities);
+			},
+
+			deleteEntity: function(serviceName, entities) {
+				if (!$.isArray(entities)) entities = [entities];
+
+				var _entities = $.map(entities, function (entity) {
+					return typeof entity === "object" ? entity.encodedRowkey : entity;
+				});
+				return _post(app.getURL("deleteEntity", {serviceName: serviceName}), _entities);
+			},
+			deleteEntities: function(serviceName, condition) {
+				return _delete(app.getURL("deleteEntities", {serviceName: serviceName, condition: new ParseCondition(condition).condition}));
+			},
+			delete: function(serviceName, kvs) {
+				var _deleteURL = app.getDeleteURL(serviceName);
+				return _delete(common.template(_deleteURL, kvs));
+			},
+
+			queryEntity: function(serviceName, encodedRowkey) {
+				return _query("queryEntity", {serviceName: serviceName, encodedRowkey: encodedRowkey});
+			},
+			queryEntities: function(serviceName, condition, fields) {
+				return _query("queryEntities", {serviceName: serviceName, _condition: new ParseCondition(condition), values: fields});
+			},
+			queryGroup: function(serviceName, condition, groupBy, fields) {
+				return _query("queryGroup", {serviceName: serviceName, _condition: new ParseCondition(condition), groupBy: groupBy, values: fields});
+			},
+			querySeries: function(serviceName, condition, groupBy, fields, intervalmin) {
+				var _cond = new ParseCondition(condition);
+				var _list = _query("querySeries", {serviceName: serviceName, _condition: _cond, groupBy: groupBy, values: fields, intervalmin: intervalmin});
+				_list._promise.then(function() {
+					if(_list.length === 0) {
+						_list._empty = true;
+						_list._convert = true;
+
+						for(var i = 0; i <= (_cond.additionalCondition._endTime.valueOf() - _cond.additionalCondition._startTime.valueOf()) / (1000 * 60 * intervalmin); i += 1) {
+							_list.push(0);
+						}
+					} else if(_list.length === 1) {
+						_list._convert = true;
+						var _unit = _list.pop();
+						_list.push.apply(_list, _unit.value[0]);
+					}
+
+					if(_list._convert) {
+						var _current = _cond.additionalCondition._startTime.clone();
+						$.each(_list, function(i, value) {
+							_list[i] = {
+								x: _current.valueOf(),
+								y: value
+							};
+							_current.add(intervalmin, "m");
+						});
+					}
+				});
+				return _list;
+			},
+
+			query: function(path, params) {
+				var _list = [];
+				_list._promise = $http({
+					method: 'GET',
+					url: app.getURL("query") + path,
+					params: params
+				}).success(function(data) {
+					_list.push.apply(_list, data.obj);
+				});
+				return _list;
+			},
+
+			dialog: function(data, callback) {
+				if(data.success === false || (data.exception || "").trim()) {
+					return $.dialog({
+						title: "OPS",
+						content: $("<pre>").html(data.exception)
+					}, callback);
+				}
+				return false;
+			}
+		};
+		return pkg;
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/srv/main.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/main.js b/eagle-webservice/src/main/webapp/app/public/js/srv/main.js
new file mode 100644
index 0000000..82765b8
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/main.js
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+angular.module('eagle.service', []);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/srv/pageSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/pageSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/pageSrv.js
new file mode 100644
index 0000000..e59d8a3
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/pageSrv.js
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+
+	// ===========================================================
+	// =                         Service                         =
+	// ===========================================================
+	// Feature page
+	serviceModule.service('PageConfig', function() {
+		var _tmplConfig = {
+			pageTitle: "",
+			pageSubTitle: "",
+
+			hideSite: false,
+			lockSite: false,
+			hideApplication: false,
+			hideSidebar: false,
+			hideUser: false,
+
+			// Current page navigation path
+			navPath: [],
+
+			navConfig: {}
+		};
+
+		var PageConfig = {};
+
+		// Reset
+		PageConfig.reset = function() {
+			$.extend(PageConfig, _tmplConfig);
+			PageConfig.navPath = [];
+		};
+		PageConfig.reset();
+
+		// Create navigation path
+		PageConfig.addNavPath = function(title, path) {
+			PageConfig.navPath.push({
+				title: title,
+				path: path
+			});
+			return PageConfig;
+		};
+
+		return PageConfig;
+	});
+
+	// Feature page
+	serviceModule.service('FeaturePageConfig', function(Application) {
+		var config = {
+			// Feature mapping pages
+			_navItemMapping: {}
+		};
+
+		// Register feature controller
+		config.addNavItem = function(feature, item) {
+			var _navItemList = config._navItemMapping[feature] = config._navItemMapping[feature] || [];
+			_navItemList.push(item);
+		};
+
+		// Page list
+		Object.defineProperty(config, "pageList", {
+			get: function() {
+				var _app = Application.current();
+				var _list = [];
+
+				if(_app && _app.features) {
+					$.each(_app.features, function(i, featureName) {
+						_list = _list.concat(config._navItemMapping[featureName] || []);
+					});
+				}
+
+				return _list;
+			}
+		});
+
+		return config;
+	});
+
+	// Configuration page
+	serviceModule.service('ConfigPageConfig', function(Application) {
+		var _originPageList = [
+			{icon: "server", title: "Sites", url: "#/config/site"},
+			{icon: "cubes", title: "Applications", url: "#/config/application"},
+			{icon: "leaf", title: "Features", url: "#/config/feature"}
+		];
+
+		var config = {
+			_navItemMapping: {}
+		};
+
+		// Register feature controller
+		config.addNavItem = function(feature, item) {
+			var _navItemList = config._navItemMapping[feature] = config._navItemMapping[feature] || [];
+			_navItemList.push(item);
+		};
+
+		// Page list
+		Object.defineProperty(config, "pageList", {
+			get: function() {
+				var _list = _originPageList;
+
+				$.each(Application.featureList, function(i, feature) {
+					_list = _list.concat(config._navItemMapping[feature.tags.feature] || []);
+				});
+
+				return _list;
+			}
+		});
+
+		return config;
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
new file mode 100644
index 0000000..907915d
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	serviceModule.service('Site', function($rootScope, $wrapState, $location, $q, Entities, Application) {
+		var _currentSite;
+		var Site = {};
+		var _promise;
+
+		Site.list = [];
+		Site.list.set = {};
+
+		Site.current = function(site) {
+			if(site) {
+				var _prev = _currentSite;
+				_currentSite = site;
+
+				// Keep current site and reload page
+				if(!_prev || _prev.tags.site !== _currentSite.tags.site) {
+					if(sessionStorage) {
+						sessionStorage.setItem("site", _currentSite.tags.site);
+					}
+
+					if(!$wrapState.current.abstract && $wrapState.current.name !== "login") {
+						console.log("[Site]", "Switch. Reload.");
+						$wrapState.reload();
+					}
+				}
+			}
+			return _currentSite;
+		};
+		Site.find = function(siteName) {
+			return common.array.find(siteName, Site.list, "tags.site");
+		};
+		Site.url = function(site, url) {
+			console.warn("[Site] Site.url is a deprecated function.");
+			if(arguments.length == 1) {
+				url = site;
+			} else {
+				Site.current(site);
+			}
+			$wrapState.url(url);
+
+			if ($rootScope.$$phase != '$apply' && $rootScope.$$phase != '$digest') {
+				$rootScope.$apply();
+			}
+		};
+
+		Site.currentSiteApplication = function() {
+			var _app = Application.current();
+			if(!_app) return null;
+
+			return _currentSite.applicationList.set[_app.tags.application];
+		};
+
+		Site.reload = function() {
+			var _applicationList;
+
+			if(Site.list && Site.list._promise) Site.list._promise.abort();
+
+			Site.list = Entities.queryEntities("SiteDescService", '');
+			Site.list.set = {};
+			_applicationList = Entities.queryEntities("SiteApplicationService", '');
+
+			_promise = $q.all([Site.list._promise, _applicationList._promise, Application._promise()]).then(function() {
+				// Fill site set
+				$.each(Site.list, function(i, site) {
+					var _list = [];
+					var _appGrp = {};
+					var _appGrpList = [];
+					_list.set = {};
+					Site.list.set[site.tags.site] = site;
+
+					// Find application
+					_list.find = function(applicationName) {
+						return common.array.find(applicationName, _list, "tags.application");
+					};
+
+					// Define properties
+					Object.defineProperties(site, {
+						applicationList: {
+							get: function() {
+								return _list;
+							}
+						},
+						applicationGroup: {
+							get: function() {
+								return _appGrp;
+							}
+						},
+						applicationGroupList: {
+							get: function() {
+								return _appGrpList;
+							}
+						}
+					});
+				});
+
+				// Fill site application mapping
+				$.each(_applicationList, function(i, siteApplication) {
+					var _site = Site.list.set[siteApplication.tags.site];
+					var _application = Application.find(siteApplication.tags.application);
+					var _appGroup, _configObj;
+
+					if(!_site) {
+						console.warn("[Site] Application not match site:", siteApplication.tags.site, "-", siteApplication.tags.application);
+					} else if(!_application) {
+						console.warn("[Site] Application not found:", siteApplication.tags.site, "-", siteApplication.tags.application);
+					} else {
+						_configObj = common.parseJSON(siteApplication.config, {});
+						Object.defineProperties(siteApplication, {
+							application: {
+								get: function () {
+									return _application;
+								}
+							},
+							configObj: {
+								get: function () {
+									return _configObj;
+								}
+							}
+						});
+
+						_site.applicationList.push(siteApplication);
+						_site.applicationList.set[siteApplication.tags.application] = siteApplication;
+
+						_appGroup = _site.applicationGroup[_application.groupName] = _site.applicationGroup[_application.groupName] || [];
+						_appGroup.push(_application);
+					}
+				});
+
+				// Fill site application group attributes
+				$.each(Site.list, function(i, site) {
+					$.each(site.applicationGroup, function(grpName, grpList) {
+						var grp = {
+							name: grpName,
+							list: grpList,
+							enabledList: $.grep(grpList, function(application) {return site.applicationList.set[application.tags.application].enabled;}),
+							disabledList: $.grep(grpList, function(application) {return !site.applicationList.set[application.tags.application].enabled;})
+						};
+
+						site.applicationGroupList.push(grp);
+					});
+
+					site.applicationGroupList.sort(function(a, b) {
+						if(a.name === b.name) return 0;
+						if(a.name === "Others") return 1;
+						if(b.name === "Others") return -1;
+						return a.name < b.name ? -1 : 1;
+					});
+				});
+
+				// Set current site
+				if(sessionStorage && Site.find(sessionStorage.getItem("site"))) {
+					Site.current(Site.find(sessionStorage.getItem("site")));
+				} else {
+					Site.current(Site.list[0]);
+				}
+
+				return Site;
+			});
+
+			return _promise;
+		};
+
+		Site._promise = function() {
+			if(!_promise) {
+				Site.reload();
+			}
+			return _promise;
+		};
+
+		return Site;
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
new file mode 100644
index 0000000..f82c838
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+
+	// ===========================================================
+	// =                         Service                         =
+	// ===========================================================
+	// Feature page
+	serviceModule.service('UI', function($rootScope, $q, $compile) {
+		var UI = {};
+
+		function _fieldDialog(create, name, entity, fieldList, checkFunc) {
+			var _deferred, $mdl, $scope;
+
+			_deferred = $q.defer();
+			$scope = $rootScope.$new(true);
+			$scope.name = name;
+			$scope.entity = entity;
+			$scope.fieldList = fieldList;
+			$scope.checkFunc = checkFunc;
+			$scope.lock = false;
+			$scope.create = create;
+
+			$scope.config = typeof name === "object" ? name : {};
+
+			// Modal
+			$mdl = $(TMPL_FIELDS).appendTo('body');
+			$compile($mdl)($scope);
+			$mdl.modal();
+
+			$mdl.on("hide.bs.modal", function() {
+				_deferred.reject();
+			});
+			$mdl.on("hidden.bs.modal", function() {
+				_deferred.resolve({
+					entity: entity
+				});
+				$mdl.remove();
+			});
+
+			// Function
+			$scope.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;
+					}
+				});
+			};
+
+			return _deferred.promise;
+		}
+
+		/***
+		 * Create a creation confirm modal.
+		 * @param name			Name title
+		 * @param entity		bind entity
+		 * @param fieldList	Array. Format: {name, field, type(optional: blob), rows(optional: number), description(optional), optional(optional), readonly(optional)}
+		 * @param checkFunc	Check logic function. Return string will prevent access
+		 */
+		UI.createConfirm = function(name, entity, fieldList, checkFunc) {
+			return _fieldDialog(true, name, entity, fieldList, checkFunc);
+		};
+
+		/***
+		 * Create a update confirm modal.
+		 * @param name			Name title
+		 * @param entity		bind entity
+		 * @param fieldList	Array. Format: {name, field, type(optional: blob), rows(optional: number), description(optional), optional(optional), readonly(optional)}
+		 * @param checkFunc	Check logic function. Return string will prevent access
+		 */
+		UI.updateConfirm = function(name, entity, fieldList, checkFunc) {
+			return _fieldDialog(false, name, entity, fieldList, checkFunc);
+		};
+
+		/***
+		 * Create a customize field confirm modal.
+		 * @param config		Configuration object
+		 * 			@param config.title			Title of dialog box
+		 * 			@param config.size				"large". Set dialog size
+		 * 			@param config.confirm			Boolean. Display or not confirm button
+		 * 			@param config.confirmDesc		Confirm button display description
+		 * @param entity		bind entity
+		 * @param fieldList	Array. Format: {name, field, type(optional: blob), rows(optional: number), description(optional), optional(optional), readonly(optional)}
+		 * @param checkFunc	Check logic function. Return string will prevent access
+		 */
+		UI.fieldConfirm = function(config, entity, fieldList, checkFunc) {
+			return _fieldDialog("field", config, entity, fieldList, checkFunc);
+		};
+
+		UI.deleteConfirm = function(name) {
+			var _deferred, $mdl, $scope;
+
+			_deferred = $q.defer();
+			$scope = $rootScope.$new(true);
+			$scope.name = name;
+			$scope.lock = false;
+
+			// Modal
+			$mdl = $(TMPL_DELETE).appendTo('body');
+			$compile($mdl)($scope);
+			$mdl.modal();
+
+			$mdl.on("hide.bs.modal", function() {
+				_deferred.reject();
+			});
+			$mdl.on("hidden.bs.modal", function() {
+				_deferred.resolve({
+					name: name
+				});
+				$mdl.remove();
+			});
+
+			// Function
+			$scope.delete = function() {
+				$scope.lock = true;
+				_deferred.notify({
+					name: name,
+					closeFunc: function() {
+						$mdl.modal('hide');
+					},
+					unlock: function() {
+						$scope.lock = false;
+					}
+				});
+			};
+
+			return _deferred.promise;
+		};
+
+		return UI;
+	});
+
+	// ===========================================================
+	// =                         Template                        =
+	// ===========================================================
+	var TMPL_FIELDS =
+		'<div class="modal fade" tabindex="-1" role="dialog">' +
+			'<div class="modal-dialog" ng-class="{\'modal-lg\': config.size === \'large\'}" role="document">' +
+				'<div class="modal-content">' +
+					'<div class="modal-header">' +
+						'<button type="button" class="close" data-dismiss="modal" aria-label="Close">' +
+							'<span aria-hidden="true">&times;</span>' +
+						'</button>' +
+						'<h4 class="modal-title">{{config.title || (create ? "New" : "Update") + " " + name}}</h4>' +
+					'</div>' +
+					'<div class="modal-body">' +
+						'<div class="form-group" ng-repeat="field in fieldList" ng-switch="field.type">' +
+							'<label for="featureName">' +
+								'<span ng-if="!field.optional">*</span> ' +
+								'{{field.name || field.field}}' +
+							'</label>' +
+							'<textarea class="form-control" placeholder="{{field.description || field.name || field.field + \'...\'}}" ng-model="entity[field.field]" rows="{{ field.rows || 10 }}" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-when="blob"></textarea>' +
+							'<input type="text" class="form-control" placeholder="{{field.description || field.name || field.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" 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/d37643b0/eagle-webservice/src/main/webapp/app/public/js/srv/wrapStateSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/wrapStateSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/wrapStateSrv.js
new file mode 100644
index 0000000..57872b2
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/wrapStateSrv.js
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	serviceModule.service('$wrapState', function($state, $location, $stateParams) {
+		var $wrapState = {};
+		var _targetState = null;
+		var _targetPriority = 0;
+
+		// Go
+		$wrapState.go = function(state, param, priority) {
+			setTimeout(function() {
+				_targetState = null;
+				_targetPriority = 0;
+			});
+
+			if(typeof param !== "object") {
+				param = {};
+				priority = param;
+			}
+
+			priority = priority === true ? 1 : (priority || 0);
+			if(_targetPriority > priority) {
+				console.log("[Wrap State] Go - low priority:", state, "(Skip)");
+				return false;
+			}
+
+			if(_targetState !== state || priority) {
+				if($state.current && $state.current.name === state && angular.equals($state.params, param)) {
+					console.log($state);
+					console.log("[Wrap State] Go reload.");
+					$state.reload();
+				} else {
+					console.log("[Wrap State] Go:", state, param, priority);
+					$state.go(state, param);
+				}
+				_targetState = state;
+				_targetPriority = priority;
+				return true;
+			} else {
+				console.log("[Wrap State] Go:", state, "(Ignored)");
+			}
+			return false;
+		};
+
+		// Reload
+		$wrapState.reload = function() {
+			console.log("[Wrap State] Do reload.");
+			$state.reload();
+		};
+
+		// Path
+		$wrapState.path = function(path) {
+			if(path !== undefined) {
+				console.log("[Wrap State][Deprecated] Switch path:", path);
+			}
+			return $location.path(path);
+		};
+
+		// URL
+		$wrapState.url = function(url) {
+			if(url !== undefined) console.log("[Wrap State] Switch url:", url);
+			return $location.url(url);
+		};
+
+		Object.defineProperties($wrapState, {
+			// Origin $state
+			origin: {
+				get: function() {
+					return $state;
+				}
+			},
+
+			// Current
+			current: {
+				get: function() {
+					return $state.current;
+				}
+			},
+
+			// Parameter
+			param: {
+				get: function() {
+					return $.extend({}, $location.search(), $stateParams);
+				}
+			}
+		});
+
+		return $wrapState;
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/grunt.json
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/grunt.json b/eagle-webservice/src/main/webapp/grunt.json
index 0b8ad5a..c921862 100644
--- a/eagle-webservice/src/main/webapp/grunt.json
+++ b/eagle-webservice/src/main/webapp/grunt.json
@@ -15,8 +15,9 @@
 				"node_modules/angular/angular.min.js",
 				"node_modules/angular-resource/angular-resource.min.js",
 				"node_modules/angular-route/angular-route.min.js",
-				"node_modules/angular-cookies/angular-cookies.min.js",
+				"node_modules/angular-animate/angular-animate.min.js",
 				"node_modules/angular-ui-bootstrap/ui-bootstrap-tpls.min.js",
+				"node_modules/angular-ui-router/release/angular-ui-router.min.js",
 				"node_modules/d3/d3.min.js",
 				"node_modules/zombiej-nvd3/build/nv.d3.min.js",
 
@@ -28,8 +29,8 @@
 			"src": [
 				"node_modules/bootstrap/dist/css/bootstrap.min.css",
 				"node_modules/zombiej-bootstrap-components/bootstrap-components/css/bootstrap-components.min.css",
-				"node_modules/font-awesome/css/font-awesome.min.css",
 				"node_modules/zombiej-nvd3/build/nv.d3.min.css",
+				"node_modules/font-awesome/css/font-awesome.min.css",
 				"node_modules/admin-lte/dist/css/AdminLTE.min.css",
 				"node_modules/admin-lte/dist/css/skins/skin-blue.min.css",
 

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/package.json
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/package.json b/eagle-webservice/src/main/webapp/package.json
index 713b93c..1e6f8d4 100644
--- a/eagle-webservice/src/main/webapp/package.json
+++ b/eagle-webservice/src/main/webapp/package.json
@@ -20,10 +20,11 @@
 		"angular-cookies"		: "1.4.7",
 		"angular-animate"		: "1.4.7",
 		"angular-ui-bootstrap"	: "0.14.3",
-		"d3"					: "~3.5.10",
-		"zombiej-nvd3"			: "~1.8.1-1",
-		"jquery-slimscroll": "1.3.6",
-		"zombiej-bootstrap-components"		: "~1.1.1"
+		"angular-ui-router"		: "~0.2.17",
+		"d3"					: "3.5.14",
+		"zombiej-nvd3"			: "1.8.1-1",
+		"jquery-slimscroll"		:"1.3.6",
+		"zombiej-bootstrap-components"		: "1.1.1"
 	},
 
 	"devDependencies": {



[3/9] incubator-eagle git commit: EAGLE-139 EAGLE-163 Eagle UI Modularization and fix bugs in policy extensions

Posted by hd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
new file mode 100644
index 0000000..c17cdf3
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
@@ -0,0 +1,332 @@
+/*
+ * 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');
+	// =============================================================
+	// =                       Configuration                       =
+	// =============================================================
+	// ========================== Feature ==========================
+	eagleControllers.controller('configFeatureCtrl', function ($scope, PageConfig, Application, Entities, UI) {
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		$scope._pageLock = false;
+
+		// ================== Feature ==================
+		// Current feature
+		$scope.feature = Application.featureList[0];
+		$scope.setFeature = function (feature) {
+			$scope.feature = feature;
+		};
+
+		// Feature list
+		$scope.features = {};
+		$.each(Application.featureList, function(i, feature) {
+			$scope.features[feature.tags.feature] = $.extend({}, feature, true);
+		});
+
+		// Create feature
+		$scope.newFeature = function() {
+			UI.createConfirm("Feature", {}, [
+				{name: "Feature Name", field: "name"}
+			], function(entity) {
+				if(entity.name && $.map($scope.features, function(feature, name) {
+						return name.toUpperCase() === entity.name.toUpperCase() ? true : null;
+					}).length) {
+					return "Feature name conflict!";
+				}
+			}).then(null, null, function(holder) {
+				Entities.updateEntity(
+					"FeatureDescService",
+					{tags: {feature: holder.entity.name}},
+					{timestamp: false}
+				)._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// Delete feature
+		$scope.deleteFeature = function(feature) {
+			UI.deleteConfirm(feature.tags.feature).then(null, null, function(holder) {
+				Entities.delete("FeatureDescService", {feature: feature.tags.feature})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// Save feature
+		$scope.saveAll = function() {
+			$scope._pageLock = true;
+			var _list = $.map($scope.features, function(feature) {
+				return feature;
+			});
+			Entities.updateEntity("FeatureDescService", _list, {timestamp: false})._promise.success(function() {
+				location.reload();
+			}).finally(function() {
+				$scope._pageLock = false;
+			});
+		};
+	});
+
+	// ======================== Application ========================
+	eagleControllers.controller('configApplicationCtrl', function ($scope, $timeout, PageConfig, Application, Entities, UI) {
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		$scope._pageLock = false;
+
+		// ================ Application ================
+		// Current application
+		$scope.application = Application.list[0];
+		$scope.setApplication = function (application) {
+			$scope.application = application;
+		};
+
+		// Application list
+		$scope.applications = {};
+		$.each(Application.list, function(i, application) {
+			var _application = $scope.applications[application.tags.application] = $.extend({}, application, {features: application.features.slice()}, true);
+			_application.optionalFeatures = $.map(Application.featureList, function(feature) {
+				if(!common.array.find(feature.tags.feature, _application.features)) {
+					return feature.tags.feature;
+				}
+			});
+		});
+
+		// Create application
+		$scope.newApplication = function() {
+			UI.createConfirm("Application", {}, [
+				{name: "Application Name", field: "name"}
+			], function(entity) {
+				if(entity.name && $.map($scope.applications, function(application, name) {
+						return name.toUpperCase() === entity.name.toUpperCase() ? true : null;
+					}).length) {
+					return "Application name conflict!";
+				}
+			}).then(null, null, function(holder) {
+				Entities.updateEntity(
+					"ApplicationDescService",
+					{tags: {application: holder.entity.name}},
+					{timestamp: false}
+				)._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// Delete application
+		$scope.deleteApplication = function(application) {
+			UI.deleteConfirm(application.tags.application).then(null, null, function(holder) {
+				Entities.delete("ApplicationDescService", {application: application.tags.application})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// ================= Function ==================
+		// Configuration check
+		$scope.configCheck = function(config) {
+			if(config && !common.parseJSON(config, false)) {
+				return "Invalid JSON format";
+			}
+		};
+
+		// Feature
+		$scope._feature = "";
+		function highlightFeature(feature) {
+			$scope._feature = feature;
+
+			$timeout(function() {
+				$scope._feature = "";
+			}, 100);
+		}
+
+		$scope.addFeature = function(feature, application) {
+			application.features.push(feature);
+			common.array.remove(feature, application.optionalFeatures);
+			highlightFeature(feature);
+		};
+
+		$scope.removeFeature = function(feature, application) {
+			application.optionalFeatures.push(feature);
+			common.array.remove(feature, application.features);
+		};
+
+		$scope.moveFeature = function(feature, list, offset) {
+			common.array.moveOffset(feature, list, offset);
+			highlightFeature(feature);
+		};
+
+		// Save feature
+		$scope.saveAll = function() {
+			$scope._pageLock = true;
+
+			var _list = $.map($scope.applications, function(application) {
+				return application;
+			});
+			Entities.updateEntity("ApplicationDescService", _list, {timestamp: false})._promise.success(function() {
+				location.reload();
+			}).finally(function() {
+				$scope._pageLock = false;
+			});
+		};
+	});
+
+	// ============================ Site ===========================
+	eagleControllers.controller('configSiteCtrl', function ($scope, $timeout, PageConfig, Site, Application, Entities, UI) {
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		$scope._pageLock = false;
+
+		// =================== Site ====================
+		// Current site
+		$scope.site = Site.list[0];
+		$scope.setSite = function (site) {
+			$scope.site = site;
+		};
+
+
+		// Site list
+		$scope.sites = {};
+		$.each(Site.list, function(i, site) {
+			var _site = $scope.sites[site.tags.site] = $.extend({}, site, true);
+			var _applications = [];
+			var _optionalApplications = [];
+
+			Object.defineProperties(_site, {
+				applications: {
+					get: function() {return _applications;}
+				},
+				optionalApplications: {
+					get: function() {return _optionalApplications;}
+				}
+			});
+
+			$.each(Application.list, function(i, application) {
+				var _application = site.applicationList.set[application.tags.application];
+				if(_application && _application.enabled) {
+					_site.applications.push(_application);
+				} else {
+					if(_application) {
+						_site.optionalApplications.push(_application);
+					} else {
+						_site.optionalApplications.push({
+							prefix: "eagleSiteApplication",
+							config: "",
+							enabled: false,
+							tags: {
+								application: application.tags.application,
+								site: site.tags.site
+							}
+						});
+					}
+				}
+			});
+		});
+
+		// Create site
+		$scope.newSite = function() {
+			UI.createConfirm("Site", {}, [
+				{name: "Site Name", field: "name"}
+			], function(entity) {
+				if(entity.name && $.map($scope.sites, function(site, name) {
+						return name.toUpperCase() === entity.name.toUpperCase() ? true : null;
+					}).length) {
+					return "Site name conflict!";
+				}
+			}).then(null, null, function(holder) {
+				Entities.updateEntity(
+					"SiteDescService",
+					{enabled: true, tags: {site: holder.entity.name}},
+					{timestamp: false}
+				)._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// Delete site
+		$scope.deleteSite = function(site) {
+			UI.deleteConfirm(site.tags.site).then(null, null, function(holder) {
+				Entities.delete("SiteDescService", {site: site.tags.site})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// ================= Function ==================
+		$scope._application = "";
+		function highlightApplication(application) {
+			$scope._application = application;
+
+			$timeout(function() {
+				$scope._application = "";
+			}, 100);
+		}
+
+		$scope.addApplication = function(application, site) {
+			site.applications.push(application);
+			common.array.remove(application, site.optionalApplications);
+			application.enabled = true;
+			highlightApplication(application);
+		};
+
+		$scope.removeApplication = function(application, site) {
+			site.optionalApplications.push(application);
+			common.array.remove(application, site.applications);
+			application.enabled = false;
+		};
+
+		$scope.setApplication = function(application) {
+			UI.updateConfirm("Application", {config: application.config}, [
+				{name: "Configuration", field: "config", type: "blob"}
+			], function(entity) {
+				if(entity.config !== "" && !common.parseJSON(entity.config, false)) {
+					return "Invalid JSON format";
+				}
+			}).then(null, null, function(holder) {
+				application.config = holder.entity.config;
+				holder.closeFunc();
+			});
+		};
+
+		// Save feature
+		$scope.saveAll = function() {
+			$scope._pageLock = true;
+
+			var _list = $.map($scope.sites, function(site) {
+				var _clone = $.extend({applications: site.applications.concat(site.optionalApplications)}, site);
+				return _clone;
+			});
+
+			Entities.updateEntity("SiteDescService", _list, {timestamp: false, hook: true})._promise.success(function() {
+				location.reload();
+			}).finally(function() {
+				$scope._pageLock = false;
+			});
+		};
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/ctrl/damController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/damController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/damController.js
deleted file mode 100644
index f630fb2..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/damController.js
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* Controllers */
-var damControllers = angular.module('damControllers', ['ui.bootstrap', 'eagle.components']);
-
-damControllers.service('damContent', function(Entities) {
-	'use strict';
-
-	var content = {
-		config: {
-			pageList: [
-				{icon: "list", title: "Policies", url: "#/dam/summary"},
-				{icon: "exclamation-triangle", title: "Alerts", url: "#/dam/alertList"},
-				{icon: "user-secret", title: "Classification", url: "#/dam/sensitivitySummary"},
-				{icon: "graduation-cap", title: "User Profiles", url: "#/dam/userProfileList"},
-				{icon: "bullseye", title: "Metadata", url: "#/dam/streamList"},
-				{icon: "server", title: "Setup", url: "#/dam/siteList", roles: ["ROLE_ADMIN"]},
-			],
-			navMapping: {
-				"Policy View": "#/dam/summary",
-				"Polict List": "#/dam/policyList",
-				"Alert List": "#/dam/alertList",
-				"User Profile": "#/dam/userProfileList",
-			},
-		},
-		updatePolicyStatus: function(policy, status) {
-			$.dialog({
-				title: "Confirm",
-				content: "Do you want to " + (status ? "enable" : "disable") + " policy[" + policy.tags.policyId + "]?",
-				confirm: true,
-			}, function(ret) {
-				if(ret) {
-					policy.enabled = status;
-					Entities.updateEntity("AlertDefinitionService", policy);
-				}
-			});
-		},
-		deletePolicy: function(policy, callback) {
-			$.dialog({
-				title: "Confirm",
-				content: "Do you want to delete policy[" + policy.tags.policyId + "]?",
-				confirm: true,
-			}, function(ret) {
-				if(ret) {
-					policy.enabled = status;
-					Entities.deleteEntity("AlertDefinitionService", policy)._promise.finally(function() {
-						if(callback) {
-							callback(policy);
-						}
-					});
-				}
-			});
-		},
-	};
-	return content;
-});
-
-// =============================================================
-// =                          Summary                          =
-// =============================================================
-damControllers.controller('summaryCtrl', function(globalContent, Site, damContent, $scope, $q, Entities, $route) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.pageSubTitle = Site.current().name;
-
-	$scope.dataSources = {};
-	$scope.dataReady = false;
-
-	var _policyList = Entities.queryGroup("AlertDefinitionService", {dataSource:null, site: Site.current().name}, "@dataSource", "count");
-
-	_policyList._promise.then(function() {
-		// List programs
-		$.each(_policyList, function(i, unit) {
-			var _dataSrc = Site.current().dataSrcList.find(unit.key[0]);
-			if(_dataSrc) {
-				_dataSrc.count = unit.value[0];
-			} else {
-				var _siteHref = $("<a href='#/dam/siteList'>").text("Setup");
-				var _dlg = $.dialog({
-					title: "Data Source Not Found",
-					content: $("<div>")
-						.append("Data Source [" + unit.key[0] + "] not found. Please check your configuration in ")
-						.append(_siteHref)
-						.append(" page.")
-				});
-				_siteHref.click(function() {
-					_dlg.modal('hide');
-				});
-			}
-		});
-
-		$scope.dataReady = true;
-	});
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/ctrl/main.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/main.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/main.js
new file mode 100644
index 0000000..5064a1d
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/ctrl/main.js
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var eagleControllers = angular.module('eagleControllers', ['ui.bootstrap', 'eagle.components', 'eagle.service']);
+
+	// ===========================================================
+	// =                        Controller                       =
+	// ===========================================================
+	eagleControllers.controller('landingCtrl', function($scope, $wrapState, Site, Application, PageConfig, FeaturePageConfig, Feature) {
+		var _app = Application.current();
+
+		PageConfig.pageTitle = _app ? _app.displayName : 'OPS';
+		PageConfig.pageSubTitle = Site.current().tags.site;
+
+		$scope.Application = Application;
+
+		var _navItemList = FeaturePageConfig.pageList;
+		if(_navItemList.length) {
+			console.log("[Landing] Auto redirect.", FeaturePageConfig);
+			var _match = _navItemList[0].url.match(/#\/([^\/]+)\/([^\/]+)/);
+			Feature.go(_match[1], _match[2]);
+		}
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/ctrl/policyController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/policyController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/policyController.js
deleted file mode 100644
index 1ca741c..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/policyController.js
+++ /dev/null
@@ -1,898 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// =============================================================
-// =                        Policy List                        =
-// =============================================================
-damControllers.controller('policyListCtrl', function(globalContent, Site, damContent, $scope, $routeParams, Entities) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.pageTitle = "Policy List";
-	globalContent.pageSubTitle = Site.current().name;
-	globalContent.navPath = ["Policy View", "Polict List"];
-
-	// Initial load
-	$scope.policyList = [];
-	if($routeParams.dataSource) {
-		$scope.dataSource = Site.current().dataSrcList.find($routeParams.dataSource);
-	}
-
-	// List policies
-	var _policyList = Entities.queryEntities("AlertDefinitionService", {site: Site.current().name, dataSource: $routeParams.dataSource});
-	_policyList._promise.then(function() {
-		$.each(_policyList, function(i, policy) {
-			if($.inArray(policy.tags.dataSource, app.config.dataSource.uiInvisibleList) === -1) {
-				policy.__mailStr = common.getValueByPath(common.parseJSON(policy.notificationDef, {}), "0.recipients", "");
-				policy.__mailList = policy.__mailStr.trim() === "" ? [] : policy.__mailStr.split(/[\,\;]/);
-				policy.__expression = common.parseJSON(policy.policyDef, {}).expression;
-
-				$scope.policyList.push(policy);
-			}
-		});
-	});
-	$scope.policyList._promise = _policyList._promise;
-
-	// Function
-	$scope.searchFunc = function(item) {
-		var key = $scope.search;
-		if(!key) return true;
-
-		var _key = key.toLowerCase();
-		function _hasKey(item, path) {
-			var _value = common.getValueByPath(item, path, "").toLowerCase();
-			return _value.indexOf(_key) !== -1;
-		}
-		return _hasKey(item, "tags.policyId") || _hasKey(item, "__expression") || _hasKey(item, "desc") || _hasKey(item, "owner") || _hasKey(item, "__mailStr");
-	};
-
-	$scope.updatePolicyStatus = damContent.updatePolicyStatus;
-	$scope.deletePolicy = function(policy) {
-		damContent.deletePolicy(policy, function(policy) {
-			var _index = $scope.policyList.indexOf(policy);
-			$scope.policyList.splice(_index, 1);
-		});
-	};
-});
-
-// =============================================================
-// =                       Policy Detail                       =
-// =============================================================
-damControllers.controller('policyDetailCtrl', function(globalContent, Site, damContent, $scope, $routeParams, Entities, nvd3) {
-	'use strict';
-
-	var MAX_PAGESIZE = 10000;
-
-	globalContent.setConfig(damContent.config);
-	globalContent.pageTitle = "Policy Detail";
-	globalContent.navPath = ["Policy View", "Polict List", "Polict Detail"];
-	globalContent.lockSite = true;
-
-	$scope.common = common;
-	$scope.chartConfig = {
-		chart: "line",
-		xType: "time"
-	};
-
-	// Query policy
-	if($routeParams.encodedRowkey) {
-		$scope.policyList = Entities.queryEntity("AlertDefinitionService", $routeParams.encodedRowkey);
-	} else {
-		$scope.policyList = Entities.queryEntities("AlertDefinitionService", {
-			policyId: $routeParams.policy,
-			site: $routeParams.site,
-			alertExecutorId: $routeParams.executor
-		});
-	}
-	$scope.policyList._promise.then(function() {
-		var policy = null;
-
-		if($scope.policyList.length === 0) {
-			$.dialog({
-				title: "OPS!",
-				content: "Policy not found!",
-			}, function() {
-				location.href = "#/dam/policyList";
-			});
-			return;
-		} else {
-			policy = $scope.policyList[0];
-
-			policy.__mailStr = common.getValueByPath(common.parseJSON(policy.notificationDef, {}), "0.recipients", "");
-			policy.__mailList = policy.__mailStr.trim() === "" ? [] : policy.__mailStr.split(/[\,\;]/);
-			policy.__expression = common.parseJSON(policy.policyDef, {}).expression;
-
-			$scope.policy = policy;
-			Site.current(Site.find($scope.policy.tags.site));
-			console.log($scope.policy);
-		}
-
-		// Visualization
-		var _endTime = app.time.now().hour(23).minute(59).second(59).millisecond(0);
-		var _startTime = _endTime.clone().subtract(1, "month").hour(0).minute(0).second(0).millisecond(0);
-		var _cond = {
-			dataSource: policy.tags.dataSource,
-			policyId: policy.tags.policyId,
-			_startTime: _startTime,
-			_endTime: _endTime
-		};
-
-		// > eagle.policy.eval.count
-		$scope.policyEvalSeries = nvd3.convert.eagle([Entities.querySeries("GenericMetricService", $.extend({_metricName: "eagle.policy.eval.count"}, _cond), "@cluster", "sum(value)", 60 * 24)]);
-
-		// > eagle.policy.eval.fail.count
-		$scope.policyEvalFailSeries = nvd3.convert.eagle([Entities.querySeries("GenericMetricService", $.extend({_metricName: "eagle.policy.eval.fail.count"}, _cond), "@cluster", "sum(value)", 60 * 24)]);
-
-		// > eagle.alert.count
-		$scope.alertSeries = nvd3.convert.eagle([Entities.querySeries("GenericMetricService", $.extend({_metricName: "eagle.alert.count"}, _cond), "@cluster", "sum(value)", 60 * 24)]);
-
-		// > eagle.alert.fail.count
-		$scope.alertFailSeries = nvd3.convert.eagle([Entities.querySeries("GenericMetricService", $.extend({_metricName: "eagle.alert.fail.count"}, _cond), "@cluster", "sum(value)", 60 * 24)]);
-
-		// Alert list
-		$scope.alertList = Entities.queryEntities("AlertService", {
-			site: Site.current().name,
-			dataSource: policy.tags.dataSource,
-			policyId: policy.tags.policyId,
-			_pageSize: MAX_PAGESIZE,
-			_duration: 1000 * 60 * 60 * 24 * 30,
-			__ETD: 1000 * 60 * 60 * 24
-		});
-	});
-
-	// Function
-	$scope.updatePolicyStatus = damContent.updatePolicyStatus;
-	$scope.deletePolicy = function(policy) {
-		damContent.deletePolicy(policy, function(policy) {
-			location.href = "#/dam/policyList";
-		});
-	};
-});
-
-// =============================================================
-// =                        Policy Edit                        =
-// =============================================================
-(function() {
-	'use strict';
-
-	function policyCtrl(create, globalContent, Site, damContent, $scope, $routeParams, $location, $q, Entities) {
-		globalContent.setConfig(damContent.config);
-		globalContent.pageTitle = "Policy Edit";
-		globalContent.pageSubTitle = Site.current().name;
-		globalContent.navPath = ["Policy View", "Polict List", "Polict Edit"];
-
-		var _winTimeDesc = "Number unit[millisecond, sec, min, hour, day, month, year]. e.g. 23 sec";
-		var _winTimeRegex = /^\d+\s+(millisecond|milliseconds|second|seconds|sec|minute|minutes|min|hour|hours|day|days|week|weeks|month|months|year|years)$/;
-		var _winTimeDefaultValue = '10 min';
-		$scope.config = {
-			window: [
-				// Display name, window type, required columns[Title, Description || "LONG_FIELD", Regex check, default value]
-				{
-					title: "Message Time Slide",
-					description: "Using timestamp filed from input is used as event's timestamp",
-					type: "externalTime",
-					fields:[
-						{title: "Field", defaultValue: "timestamp", hide: true},
-						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue},
-					]
-				},
-				{
-					title: "System Time Slide",
-					description: "Using System time is used as timestamp for event's timestamp",
-					type: "time",
-					fields:[
-						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue},
-					]
-				},
-				{
-					title: "System Time Batch",
-					description: "Same as System Time Window except the policy is evaluated at fixed duration",
-					type: "timeBatch",
-					fields:[
-						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue},
-					]
-				},
-				{
-					title: "Length Slide",
-					description: "The slide window has a fixed length",
-					type: "length",
-					fields:[
-						{title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/},
-					]
-				},
-				{
-					title: "Length Batch",
-					description: "Same as Length window except the policy is evaluated in batch mode when fixed event count reached",
-					type: "lengthBatch",
-					fields:[
-						{title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/},
-					]
-				},
-			],
-		};
-
-		$scope.create = create;
-		$scope.encodedRowkey = $routeParams.encodedRowkey;
-
-		$scope.step = 0;
-		$scope.dataSources = {};
-		$scope.policy = {};
-
-		// ==========================================
-		// =            Data Preparation            =
-		// ==========================================
-		// Steam list
-		var _streamList = Entities.queryEntities("AlertStreamService", '@streamName=~".*"');
-		var _executorList = Entities.queryEntities("AlertExecutorService", '@streamName=~".*"');
-		$scope.streamList = _streamList;
-		$scope.executorList = _executorList;
-		$scope.streamReady = false;
-
-		$q.all([_streamList._promise, _executorList._promise]).then(function() {
-			// Map executor with stream
-			$.each(_executorList, function(i, executor) {
-				$.each(_streamList, function(j, stream) {
-					if(stream.tags.dataSource === executor.tags.dataSource && stream.tags.streamName === executor.tags.streamName) {
-						stream.alertExecutor = executor;
-						return false;
-					}
-				});
-			});
-
-			// Fill stream list
-			$.each(_streamList, function(i, unit) {
-				var _srcStreamList = $scope.dataSources[unit.tags.dataSource] = $scope.dataSources[unit.tags.dataSource] || [];
-				_srcStreamList.push(unit);
-			});
-
-			$scope.streamReady = true;
-
-			// ==========================================
-			// =                Function                =
-			// ==========================================
-			function _findStream(dataSource, streamName) {
-				var _streamList = $scope.dataSources[dataSource];
-				if(!_streamList) return null;
-
-				for(var i = 0 ; i < _streamList.length ; i += 1) {
-					if(_streamList[i].tags.streamName === streamName) {
-						return _streamList[i];
-					}
-				}
-				return null;
-			}
-
-			// ==========================================
-			// =              Step control              =
-			// ==========================================
-			$scope.steps = [
-				// >> Select data source
-				{
-					title: "Select Data Source",
-					ready: function() {
-						return $scope.streamReady;
-					},
-					init: function() {
-						if(create) $scope.policy.tags.dataSource = $scope.policy.tags.dataSource || Site.current().dataSrcList[0].tags.dataSource;
-					},
-					nextable: function() {
-						return common.getValueByPath($scope.policy, "tags.dataSource");
-					},
-				},
-
-				// >> Select stream
-				{
-					title: "Select Stream",
-					ready: function() {
-						return $scope.streamReady;
-					},
-					init: function() {
-						$scope.policy.__.streamName = $scope.policy.__.streamName ||
-							common.array.find($scope.policy.tags.dataSource, _streamList, "tags.dataSource").tags.streamName;
-					},
-					nextable: function() {
-						var _streamName = common.getValueByPath($scope.policy, "__.streamName");
-						if(!_streamName) return false;
-
-						// Detect stream in current data source list
-						return !!common.array.find(_streamName, $scope.dataSources[$scope.policy.tags.dataSource], "tags.streamName");
-					},
-				},
-
-				// >> Define Alert Policy
-				{
-					title: "Define Alert Policy",
-					init: function() {
-						// Normal mode will fetch meta list
-						if(!$scope.policy.__.advanced) {
-							var _stream = _findStream($scope.policy.tags.dataSource, $scope.policy.__.streamName);
-							$scope._stream = _stream;
-
-							if(!_stream.metas) {
-								_stream.metas = Entities.queryEntities("AlertStreamSchemaService", {dataSource: $scope.policy.tags.dataSource, streamName: $scope.policy.__.streamName});
-								_stream.metas._promise.then(function() {
-									_stream.metas.sort(function(a, b) {
-										if(a.tags.attrName < b.tags.attrName) {
-											return -1;
-										} else if(a.tags.attrName > b.tags.attrName) {
-											return 1;
-										}
-										return 0;
-									});
-								});
-							}
-						}
-					},
-					ready: function() {
-						if(!$scope.policy.__.advanced) {
-							return $scope._stream.metas._promise.$$state.status === 1;
-						}
-						return true;
-					},
-					nextable: function() {
-						if($scope.policy.__.advanced) {
-							// Check stream source
-							$scope._stream = null;
-							$.each($scope.dataSources[$scope.policy.tags.dataSource], function(i, stream) {
-								if(($scope.policy.__._expression || "").indexOf(stream.tags.streamName) !== -1) {
-									$scope._stream = stream;
-									return false;
-								}
-							});
-							return $scope._stream;
-						} else {
-							// Window
-							if($scope.policy.__.windowConfig) {
-								var _winMatch = true;
-								var _winConds = $scope.getWindow().fields;
-								$.each(_winConds, function(i, cond) {
-									if(!(cond.val || "").match(cond.regex)) {
-										_winMatch = false;
-										return false;
-									}
-								});
-								if(!_winMatch) return false;
-
-								// Aggregation
-								if($scope.policy.__.groupAgg) {
-									if(!$scope.policy.__.groupAggPath ||
-										!$scope.policy.__.groupCondOp ||
-										!$scope.policy.__.groupCondVal) {
-										return false;
-									}
-								}
-							}
-						}
-						return true;
-					},
-				},
-
-				// >> Configuration & Notification
-				{
-					title: "Configuration & Notification",
-					nextable: function() {
-						return !!$scope.policy.tags.policyId;
-					},
-				}
-			];
-
-			// ==========================================
-			// =              Policy Logic              =
-			// ==========================================
-			_streamList._promise.then(function() {
-				// Initial policy entity
-				if(create) {
-					$scope.policy = {
-						__: {
-							toJSON: jQuery.noop,
-							conditions: {},
-							notification: [],
-							dedupe: {
-								alertDedupIntervalMin: 10,
-								emailDedupIntervalMin: 10,
-							},
-							policy: {},
-							window: "externalTime",
-							group: "",
-							groupAgg: "count",
-							groupAggPath: "timestamp",
-							groupCondOp: ">=",
-							groupCondVal: "2",
-						},
-						desc: "",
-						enabled: true,
-						prefix: "alertdef",
-						remediationDef: "",
-						tags: {
-							policyType: "siddhiCEPEngine",
-						}
-					};
-
-					// If configured data source
-					if($routeParams.dataSrc) {
-						$scope.policy.tags.dataSource = $routeParams.dataSrc;
-						if(common.array.find($routeParams.dataSrc, Site.current().dataSrcList, "tags.dataSource")) {
-							setTimeout(function() {
-								$scope.changeStep(0, 2, false);
-								$scope.$apply();
-							}, 1);
-						}
-					}
-				} else {
-					var _policy = Entities.queryEntity("AlertDefinitionService", $scope.encodedRowkey);
-					_policy._promise.then(function() {
-						if(_policy.length) {
-							$scope.policy = _policy[0];
-							$scope.policy.__ = {
-								toJSON: jQuery.noop,
-							};
-
-							Site.current(Site.find($scope.policy.tags.site));
-						} else {
-							$.dialog({
-								title: "OPS",
-								content: "Policy not found!",
-							}, function() {
-								$location.path("/dam/policyList");
-								$scope.$apply();
-							});
-							return;
-						}
-
-						// === Revert inner data ===
-						// >> De-dupe
-						$scope.policy.__.dedupe = common.parseJSON($scope.policy.dedupeDef, {});
-
-						// >> Notification
-						$scope.policy.__.notification = common.parseJSON($scope.policy.notificationDef, []);
-
-						// >> Policy
-						var _policyUnit = $scope.policy.__.policy = common.parseJSON($scope.policy.policyDef);
-
-						// >> Parse expression
-						$scope.policy.__.conditions = {};
-						var _condition = _policyUnit.expression.match(/from\s+(\w+)(\[(.*)\])?(#window[^\)]*\))?\s+(select (\w+\, )?(\w+)\((\w+)\) as [\w\d_]+ (group by (\w+) )?having ([\w\d_]+) ([<>=]+) ([^\s]+))?/);
-						var _cond_stream = _condition[1];
-						var _cond_query = _condition[3] || "";
-						var _cond_window = _condition[4];
-						var _cond_group = _condition[5];
-						var _cond_groupUnit = _condition.slice(7,14);
-
-						if(!_condition) {
-							$scope.policy.__.advanced = true;
-						} else {
-							// > StreamName
-							var _streamName = _cond_stream;
-							var _cond = _cond_query;
-
-							$scope.policy.__.streamName = _streamName;
-
-							//  > Conditions
-							// Loop condition groups
-							if(_cond.trim() !== "" && /^\(.*\)$/.test(_cond)) {
-								var _condGrps = _cond.substring(1, _cond.length - 1).split(/\)\s+and\s+\(/);
-								$.each(_condGrps, function(i, line) {
-									// Loop condition cells
-									var _condCells = line.split(/\s+or\s+/);
-									$.each(_condCells, function(i, cell) {
-										var _opMatch = cell.match(/(\S*)\s*(==|!=|>|<|>=|<=|contains)\s*('(?:[^'\\]|\\.)*'|[\w\d]+)/);
-										if(!common.isEmpty(_opMatch)) {
-											var _key = _opMatch[1];
-											var _op = _opMatch[2];
-											var _val = _opMatch[3];
-											var _conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || [];
-											var _type = "";
-											if(_val.match(/\'.*\'/)) {
-												_val = _val.slice(1, -1);
-												_type = "string";
-											} else if(_val === "true" || _val === "false") {
-												var _regexMatch = _key.match(/^str:regexp\((\w+),'(.*)'\)/);
-												var _containsMatch = _key.match(/^str:contains\((\w+),'(.*)'\)/);
-												var _mathes = _regexMatch || _containsMatch;
-												if(_mathes) {
-													_key = _mathes[1];
-													_val = _mathes[2];
-													_type = "string";
-													_op = _regexMatch ? "regex" : "contains";
-													_conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || [];
-												} else {
-													_type = "bool";
-												}
-											} else {
-												_type = "number";
-											}
-											_conds.push($scope._CondUnit(_key, _op, _val, _type));
-										}
-									});
-								});
-							} else if(_cond_query !== "") {
-								$scope.policy.__.advanced = true;
-							}
-						}
-
-						if($scope.policy.__.advanced) {
-							$scope.policy.__._expression = _policyUnit.expression;
-						} else {
-							// > window
-							if(!_cond_window) {
-								$scope.policy.__.window = "externalTime";
-								$scope.policy.__.group = "";
-								$scope.policy.__.groupAgg = "count";
-								$scope.policy.__.groupAggPath = "timestamp";
-								$scope.policy.__.groupCondOp = ">=";
-								$scope.policy.__.groupCondVal = "2";
-							} else {
-								try {
-									$scope.policy.__.windowConfig = true;
-
-									var _winCells = _cond_window.match(/\.(\w+)\((.*)\)/);
-									$scope.policy.__.window = _winCells[1];
-									var _winConds = $scope.getWindow().fields;
-									$.each(_winCells[2].split(","), function(i, val) {
-										_winConds[i].val = val;
-									});
-
-									// Group
-									if(_cond_group) {
-										$scope.policy.__.group = _cond_groupUnit[3];
-										$scope.policy.__.groupAgg = _cond_groupUnit[0];
-										$scope.policy.__.groupAggPath = _cond_groupUnit[1];
-										$scope.policy.__.groupAggAlias = _cond_groupUnit[4] === "aggValue" ? "" : _cond_groupUnit[4];
-										$scope.policy.__.groupCondOp = _cond_groupUnit[5];
-										$scope.policy.__.groupCondVal = _cond_groupUnit[6];
-									} else {
-										$scope.policy.__.group = "";
-										$scope.policy.__.groupAgg = "count";
-										$scope.policy.__.groupAggPath = "timestamp";
-										$scope.policy.__.groupCondOp = ">=";
-										$scope.policy.__.groupCondVal = "2";
-									}
-								} catch(err) {
-									$scope.policy.__.window = "externalTime";
-								}
-							}
-						}
-
-						$scope.changeStep(0, 3, false);
-					});
-				}
-
-				// Start step
-				$scope.changeStep(0, 1, false);
-
-				console.log($scope.policy, $scope);
-			});
-
-			// ==========================================
-			// =                Function                =
-			// ==========================================
-			// UI: Highlight select step
-			$scope.stepSelect = function(step) {
-				return step === $scope.step ? "active" : "";
-			};
-
-			// UI: Collapse all
-			$scope.collapse = function(cntr) {
-				var _list = $(cntr).find(".collapse").css("height", "auto");
-				if(_list.hasClass("in")) {
-					_list.removeClass("in");
-				} else {
-					_list.addClass("in");
-				}
-			};
-
-			// Step process. Will fetch target step attribute and return boolean
-			function _check(key, step) {
-				var _step = $scope.steps[step - 1];
-				if(!_step) return;
-
-				var _value = _step[key];
-				if(typeof _value === "function") {
-					return _value();
-				} else if(typeof _value === "boolean") {
-					return _value;
-				}
-				return true;
-			}
-			// Check step is ready. Otherwise will display load animation
-			$scope.stepReady = function(step) {
-				return _check("ready", step);
-			};
-			// Check whether process next step. Otherwise will disable next button
-			$scope.checkNextable = function(step) {
-				return !_check("nextable", step);
-			};
-			// Switch step
-			$scope.changeStep = function(step, targetStep, check) {
-				if(check === false || _check("checkStep", step)) {
-					$scope.step =  targetStep;
-
-					_check("init", targetStep);
-				}
-			};
-
-			// Window
-			$scope.getWindow = function() {
-				if(!$scope.policy || !$scope.policy.__) return null;
-				var _window = common.array.find($scope.policy.__.window, $scope.config.window, "type");
-				return _window;
-			};
-
-			// Aggregation
-			$scope.groupAggPathList = function() {
-				return $.grep(common.getValueByPath($scope, "_stream.metas", []), function(meta) {
-					return $.inArray(meta.attrType, ['long','integer','number', 'double', 'float']) !== -1;
-				});
-			};
-
-			$scope.updateGroupAgg = function() {
-				$scope.policy.__.groupAggPath = $scope.policy.__.groupAggPath || common.getValueByPath($scope.groupAggPathList()[0], "tags.attrName");
-
-				if($scope.policy.__.groupAgg === 'count') {
-					$scope.policy.__.groupAggPath = 'timestamp';
-				}
-			};
-
-			// Resolver
-			$scope.resolverTypeahead = function(value, resolver) {
-				var _resolverList = Entities.query("stream/attributeresolve", {
-					site: Site.current().name,
-					resolver: resolver,
-					query: value
-				});
-				return _resolverList._promise.then(function() {
-					return _resolverList;
-				});
-			};
-
-			// Used for input box when pressing enter
-			$scope.conditionPress = function(event, key, operation, value, type) {
-				if(event.which == 13) {
-					setTimeout(function() {
-						$(event.currentTarget).closest(".input-group").find("button").click();
-					}, 1);
-				}
-			};
-			// Check whether has condition
-			$scope.hasCondition = function(key, type) {
-				var _list = common.getValueByPath($scope.policy.__.conditions, key, []);
-				if(_list.length === 0) return false;
-
-				if(type === "bool") {
-					return !_list[0].ignored();
-				}
-				return true;
-			};
-			// Condition unit definition
-			$scope._CondUnit = function(key, op, value, type) {
-				var _obj = {
-					key: key,
-					op: op,
-					val: value,
-					type: type,
-					ignored: function() {
-						if(this.type === "bool" && this.val === "none") {
-							return true;
-						}
-						return false;
-					},
-					getVal: function() {
-						return this.type === "string" ? "'" + this.val + "'" : this.val;
-					},
-					toString: function() {
-						return this.op + " " + this.getVal();
-					},
-					toCondString: function() {
-						var _op = this.op === "=" ? "==" : this.op;
-						if(_op === "regex") {
-							return "str:regexp(" + this.key + "," + this.getVal() + ")==true";
-						} else if(_op === "contains") {
-							return "str:contains(" + this.key + "," + this.getVal() + ")==true";
-						} else {
-							return this.key + " " + _op + " " + this.getVal();
-						}
-					},
-				};
-				return _obj;
-			};
-			// Add condition for policy
-			$scope.addCondition = function(key, op, value, type) {
-				if(value === "" || value === undefined) return false;
-
-				var _condList = $scope.policy.__.conditions[key] = $scope.policy.__.conditions[key] || [];
-				_condList.push($scope._CondUnit(key, op, value, type));
-				return true;
-			};
-			// Convert condition list to description string
-			$scope.parseConditionDesc = function(key) {
-				return $.map($scope.policy.__.conditions[key] || [], function(cond) {
-					if(!cond.ignored()) return "[" + cond.toString() + "]";
-				}).join(" or ");
-			};
-
-			// To query
-			$scope.toQuery = function() {
-				if(!$scope.policy.__) return "";
-
-				if($scope.policy.__.advanced) return $scope.policy.__._expression;
-
-				// > Query
-				var _query = $.map(common.getValueByPath($scope.policy, "__.conditions", {}), function(list, key) {
-					var _conds = $.map(list, function(cond) {
-						if(!cond.ignored()) return cond.toCondString();
-					});
-					if(_conds.length) {
-						return "(" + _conds.join(" or ") + ")";
-					}
-				}).join(" and ");
-				if(_query) {
-					_query = "[" + _query + "]";
-				}
-
-				// > Window
-				var _window = $scope.getWindow();
-				var _windowStr = "";
-				if($scope.policy.__.windowConfig) {
-					_windowStr = $.map(_window.fields, function(field) {
-						return field.val;
-					}).join(",");
-					_windowStr = "#window." + _window.type + "(" + _windowStr + ")";
-
-					// > Group
-					if($scope.policy.__.group) {
-						_windowStr += common.template(" select ${group}, ${groupAgg}(${groupAggPath}) as ${groupAggAlias} group by ${group} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", {
-							group: $scope.policy.__.group,
-							groupAgg: $scope.policy.__.groupAgg,
-							groupAggPath: $scope.policy.__.groupAggPath,
-							groupCondOp: $scope.policy.__.groupCondOp,
-							groupCondVal: $scope.policy.__.groupCondVal,
-							groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue"
-						});
-					} else {
-						_windowStr += common.template(" select ${groupAgg}(${groupAggPath}) as ${groupAggAlias} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", {
-							groupAgg: $scope.policy.__.groupAgg,
-							groupAggPath: $scope.policy.__.groupAggPath,
-							groupCondOp: $scope.policy.__.groupCondOp,
-							groupCondVal: $scope.policy.__.groupCondVal,
-							groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue"
-						});
-					}
-				} else {
-					_windowStr = " select *";
-				}
-
-				return common.template("from ${stream}${query}${window} insert into outputStream;", {
-					stream: $scope.policy.__.streamName,
-					query: _query,
-					window: _windowStr,
-				});
-			};
-
-			// ==========================================
-			// =             Update Policy              =
-			// ==========================================
-			// dedupeDef notificationDef policyDef
-			$scope.finishPolicy = function() {
-				$scope.lock = true;
-
-				// dedupeDef
-				$scope.policy.dedupeDef = JSON.stringify($scope.policy.__.dedupe);
-
-				// notificationDef
-				$scope.policy.__.notification = $scope.policy.__.notification || [];
-				var _notificationUnit = $scope.policy.__.notification[0];
-				if(_notificationUnit) {
-					_notificationUnit.flavor = "email";
-					_notificationUnit.id = "email_1";
-					_notificationUnit.tplFileName = "";
-				}
-				$scope.policy.notificationDef = JSON.stringify($scope.policy.__.notification);
-
-				// policyDef
-				$scope.policy.__._dedupTags = $scope.policy.__._dedupTags || {};
-				$scope.policy.__.policy = {
-					expression: $scope.toQuery(),
-					type: "siddhiCEPEngine"
-				};
-				$scope.policy.policyDef = JSON.stringify($scope.policy.__.policy);
-
-				// alertExecutorId
-				if($scope._stream.alertExecutor) {
-					$scope.policy.tags.alertExecutorId = $scope._stream.alertExecutor.tags.alertExecutorId;
-				} else {
-					$scope.lock = false;
-					$.dialog({
-						title: "OPS!",
-						content: "Alert Executor not defined! Please check 'AlertExecutorService'!"
-					});
-					return;
-				}
-
-				// site
-				$scope.policy.tags.site = $scope.policy.tags.site || Site.current().name;
-
-				// Update function
-				function _updatePolicy() {
-					Entities.updateEntity("AlertDefinitionService", $scope.policy)._promise.success(function(data) {
-						$scope.create = create = false;
-						$scope.encodedRowkey = data.obj[0];
-
-						$.dialog({
-							title: "Success",
-							content: (create ? "Create" : "Update") + " success!",
-						}, function() {
-							if(data.success) {
-								Site.url(Site.find($scope.policy.tags.site), "/dam/policyList");
-							} else {
-								$.dialog({
-									title: "OPS",
-									content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data),
-								});
-							}
-						});
-					}).error(function(data) {
-						$.dialog({
-							title: "OPS",
-							content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data),
-						});
-					}).then(function() {
-						$scope.lock = false;
-					});
-				}
-
-				// Check if already exist
-				if($scope.create) {
-					var _checkList = Entities.queryEntities("AlertDefinitionService", {
-						alertExecutorId: $scope.policy.tags.alertExecutorId,
-						policyId: $scope.policy.tags.policyId,
-						policyType: "siddhiCEPEngine",
-						dataSource: $scope.policy.tags.dataSource,
-					});
-					_checkList._promise.then(function() {
-						if(_checkList.length) {
-							$.dialog({
-								title: "Override Confirm",
-								content: "Already exists PolicyID '" + $scope.policy.tags.policyId + "'. Do you want to override?",
-								confirm: true
-							}, function(ret) {
-								if(ret) {
-									_updatePolicy();
-								} else {
-									$scope.lock = false;
-									$scope.$apply();
-								}
-							});
-						} else {
-							_updatePolicy();
-						}
-					});
-				} else {
-					_updatePolicy();
-				}
-			};
-		});
-	}
-
-	damControllers.controller('policyCreateCtrl', function(globalContent, Site, damContent, $scope, $routeParams, $location, $q, Entities) {
-		policyCtrl(true, globalContent, Site, damContent, $scope, $routeParams, $location, $q, Entities);
-	});
-	damControllers.controller('policyEditCtrl', function(globalContent, Site, damContent, $scope, $routeParams, $location, $q, Entities) {
-		globalContent.lockSite = true;
-		policyCtrl(false, globalContent, Site, damContent, $scope, $routeParams, $location, $q, Entities);
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/ctrl/sensitivityController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/sensitivityController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/sensitivityController.js
deleted file mode 100644
index 3a68f10..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/sensitivityController.js
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-damControllers.service('SensitivityConfig', function(Site) {
-	'use strict';
-
-		var config = {
-		list: [
-			{
-				name: "HDFS",
-				dataSrc: common.array.find("hdfsAuditLog", Site.current().dataSrcList, "tags.dataSource"),
-				service: "FileSensitivityService",
-				keys: ["filedir", "sensitivityType"],
-
-				type: "folder",
-				prefix: "fileSensitivity",
-				api: "hdfsResource",
-			},
-			{
-				name: "Hive",
-				dataSrc: common.array.find("hiveQueryLog", Site.current().dataSrcList, "tags.dataSource"),
-				service: "HiveResourceSensitivityService",
-				keys: ["hiveResource", "sensitivityType"],
-
-				type: "table",
-				prefix: "hiveResourceSensitivity",
-				api: {
-					database: "hiveResource/databases",
-					table: "hiveResource/tables",
-					column: "hiveResource/columns",
-				},
-				mapping: {
-					database: "database",
-					table: "table",
-					column: "column",
-				},
-			},
-			{
-				name: "HBase",
-				dataSrc: common.array.find("hbaseSecurityLog", Site.current().dataSrcList, "tags.dataSource"),
-				service: "HbaseResourceSensitivityService",
-				keys: ["hbaseResource", "sensitivityType"],
-				type: "table",
-				prefix: "hbaseResourceSensitivity",
-				api: {
-					database: "hbaseResource/namespaces",
-					table: "hbaseResource/tables",
-					column: "hbaseResource/columns",
-				},
-				mapping: {
-					database: "namespace",
-					table: "table",
-					column: "columnFamily",
-				},
-			},
-		],
-	};
-
-	return config;
-});
-
-// =============================================================
-// =                    Sensitivity Summary                    =
-// =============================================================
-damControllers.controller('sensitivitySummaryCtrl', function(globalContent, Site, damContent, $scope, Entities, SensitivityConfig) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.pageTitle = "Data Classification";
-	globalContent.pageSubTitle = Site.current().name;
-	$scope.config = SensitivityConfig;
-
-	$scope._sensitivitySource = null;
-	$scope._sensitivityLock = false;
-	$scope._sensitivityImportType = "By Text";
-	$scope._sensitivityFile = "";
-	$scope._sensitivityData = "";
-	$scope._sensitivityError = "";
-
-	// ==================== Statistic ====================
-	var _refreshStatistic = function(entity) {
-		if(entity) {
-				entity.statisitc = Entities.queryGroup(entity.service, {site: Site.current().name}, "@site", "count");
-		} else {
-			$.each($scope.config.list, function(i, entity) {
-				entity.statisitc = Entities.queryGroup(entity.service, {site: Site.current().name}, "@site", "count");
-			});
-		}
-	};
-	_refreshStatistic();
-
-	// ================== Configuration ==================
-	// Import sensitivity data
-	$scope.showImportEditor = function(entity) {
-		$scope._sensitivitySource = entity;
-
-		$("#sensitivityMDL").modal('show');
-		setTimeout(function() {
-			$("#sensitivityData").focus();
-		}, 500);
-	};
-
-	$scope.confirmImport = function() {
-		if(!$scope._sensitivitySource) return;
-
-		$scope._sensitivityLock = true;
-
-		// Post data
-		switch($scope._sensitivityImportType) {
-		case "By Text":
-		// > By Text
-			Entities.updateEntity($scope._sensitivitySource.service, 
-				common.parseJSON($scope._sensitivityData, null),
-				 {timestamp: false})._promise.success(function(data) {
-				if(!Entities.dialog(data)) {
-					$("#sensitivityMDL").modal('hide');
-					$scope._sensitivityData = "";
-
-					_refreshStatistic($scope._sensitivitySource);
-				}
-			}).finally(function() {
-				$scope._sensitivityLock = false;
-			});
-			break;
-
-		case "By File":
-		// > By File
-			var formData = new FormData();
-			formData.append("file", $("#sensitivityFile")[0].files[0]);
-			formData.append("site", Site.current().name);
-
-			$.ajax({
-				url : app.getURL("updateEntity", {serviceName: $scope._sensitivitySource.service}),
-				data : formData,
-				cache : false,
-				contentType : false,
-				processData : false,
-				type : 'POST',
-			}).done(function(data) {
-				if(!Entities.dialog(data)) {
-					$("#sensitivityMDL").modal('hide');
-					$scope._sensitivityFile = "";
-
-					_refreshStatistic($scope._sensitivitySource);
-				}
-			}).always(function() {
-				$scope._sensitivityLock = false;
-				$scope.$apply();
-			});
-		}
-	};
-
-	$scope.importCheck = function() {
-		if($scope._sensitivityLock) return false;
-		$scope._sensitivityError = "";
-
-		switch($scope._sensitivityImportType) {
-		case "By Text":
-			if(!$scope._sensitivityData) return false;
-
-			var _list = common.parseJSON($scope._sensitivityData, null);
-			if(!_list) {
-				$scope._sensitivityError = "Can't parse json";
-			} else if(!$.isArray(_list)) {
-				$scope._sensitivityError = "Must be array";
-			} else if(_list.length === 0) {
-				$scope._sensitivityError = "Please provide at least one sensitivity item";
-			}
-			break;
-		case "By File":
-			if(!$scope._sensitivityFile) return false;
-			break;
-		}
-		return !$scope._sensitivityError;
-	};
-
-	// Manage sensitivity data
-	$scope.showManagementEditor = function(entity) {
-		$scope._sensitivitySource = entity;
-		$("#sensitivityListMDL").modal('show');
-
-		entity.list = Entities.queryEntities(entity.service, {site: Site.current().name});
-	};
-
-	$scope.deleteItem = function(item) {
-		$.dialog({
-			title: "Delete Confirm",
-			content: "Do you want to delete '" + item.tags[$scope._sensitivitySource.keys[0]] + "'?",
-			buttons: [
-				{name: "Delete", class: "btn btn-danger", value: true},
-				{name: "Cancel", class: "btn btn-default", value: false},
-			]
-		}, function(ret) {
-			if(!ret) return;
-
-			common.array.remove(item, $scope._sensitivitySource.list);
-			Entities.deleteEntity($scope._sensitivitySource.service, item)._promise.then(function() {
-				_refreshStatistic($scope._sensitivitySource);
-			});
-
-
-			$scope.$apply();
-		});
-	};
-
-	$scope.deleteAll = function(entity) {
-		$.dialog({
-			title: "Delete Confirm",
-			content: "<span class='text-red fa fa-exclamation-triangle pull-left' style='font-size: 50px;'></span>" +
-					"<p>You are <strong class='text-red'>DELETING</strong> all the sensitivity data from '" + entity.name + "'!</p>" +
-					"<p>Proceed to delete?</p>",
-			buttons: [
-				{name: "Delete", class: "btn btn-danger", value: true},
-				{name: "Cancel", class: "btn btn-default", value: false},
-			]
-		}, function(ret) {
-			if(!ret) return;
-
-			Entities.deleteEntities(entity.service, {
-				site: Site.current().name
-			})._promise.then(function() {
-				_refreshStatistic(entity);
-			});
-
-			entity.list.splice(0);
-			$scope.$apply();
-		});
-	};
-});
-
-// =============================================================
-// =                        Sensitivity                        =
-// =============================================================
-damControllers.controller('sensitivityCtrl', function(globalContent, Site, damContent, $scope, $routeParams, SensitivityConfig) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.pageTitle = "Data Classification";
-	globalContent.pageSubTitle = Site.current().name;
-	$scope.config = SensitivityConfig;
-	$scope.ajaxId = Math.random();
-
-	// Check data source
-	$scope.dataSrc = common.array.find($routeParams.dataSrc, $scope.config.list, "name");
-	if(!$scope.dataSrc) {
-		$.dialog({
-			title: "OPS",
-			content: "Data Source '" + $routeParams.dataSrc + "' not found!"
-		}, function() {
-			Site.url('/dam/sensitivitySummary');
-		});
-	}
-});
-
-// ==============================================================
-// =                    Sensitivity - Folder                    =
-// ==============================================================
-damControllers.controller('sensitivityViewFolderCtrl', function(globalContent, Site, damContent, $scope, $location, Entities, SensitivityConfig) {
-	'use strict';
-
-	$scope.path = $location.search().path || "/";
-	$scope.pathUnitList = [];
-	$scope.items = [];
-
-	// Mark sensitivity
-	$scope._oriItem = {};
-	$scope._markItem = {};
-
-	// ======================= View =======================
-	// Path
-	function _refreshPathUnitList(_path) {
-		var _start,_current, _unitList = [];
-		_path = _path + (_path.match(/\/$/) ? "" : "/");
-		for(_current = _start = 0 ; _current < _path.length ; _current += 1) {
-			if(_path[_current] === "/") {
-				_unitList.push({
-					name: _path.substring(_start, _current + (_current === 0 ? 1 : 0)),
-					path: _path.substring(0, _current === 0 ? 1 : _current),
-				});
-				_start = _current + 1;
-			}
-		}
-		$scope.pathUnitList = _unitList;
-	}
-
-	// Item
-	$scope.updateItems = function(path) {
-		if(path) $scope.path = path;
-
-		$scope.items = Entities.query($scope.dataSrc.api, {site: Site.current().name, path: $scope.path});
-		$scope.items._promise.success(function(data) {
-			var $dlg = Entities.dialog(data, function() {
-				if($scope.path !== "/") $scope.updateItems("/");
-			});
-		});
-		_refreshPathUnitList($scope.path);
-	};
-
-	$scope.getFileName = function(item) {
-		return (item.resource + "").replace(/^.*\//, "");
-	};
-
-	$scope.updateItems($scope.path);
-
-	// =================== Sensitivity ===================
-	$scope.markSensitivity = function(item) {
-		$scope._oriItem = item;
-		$scope._markItem = {
-			prefix: $scope.dataSrc.prefix,
-			tags: {
-				site: Site.current().name,
-			},
-			sensitivityType: ""
-		};
-		$scope._markItem.tags[$scope.dataSrc.keys[0]] = item.resource;
-		$("#sensitivityMDL").modal();
-		setTimeout(function() {
-			$("#sensitiveType").focus();
-		}, 500);
-	};
-	$scope.confirmUpateSensitivity = function() {
-		$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
-		var _promise = Entities.updateEntity($scope.dataSrc.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
-			Entities.dialog(data);
-		});
-		$("#sensitivityMDL").modal('hide');
-	};
-	$scope.unmarkSensitivity = function(item) {
-		$.dialog({
-			title: "Unmark Confirm",
-			content: "Do you want to remove the sensitivity mark on '" + item.resource + "'?",
-			confirm: true
-		}, function(ret) {
-			if(!ret) return;
-
-			var _cond = {site: Site.current().name};
-			_cond[$scope.dataSrc.keys[0]] = item.resource;
-			Entities.deleteEntities($scope.dataSrc.service, _cond);
-
-			item.sensitiveType = null;
-			$scope.$apply();
-		});
-	};
-});
-
-// =============================================================
-// =                    Sensitivity - Table                    =
-// =============================================================
-damControllers.controller('sensitivityViewTableCtrl', function(globalContent, Site, damContent, $scope, $location, Entities, SensitivityConfig) {
-	'use strict';
-
-	$scope.table = null;
-
-	// Mark sensitivity
-	$scope._oriItem = {};
-	$scope._markItem = {};
-
-	// ======================= View =======================
-	var _fillAttr = function(list, key, target) {
-		list._promise.then(function() {
-			$.each(list, function(i, unit) {
-				unit[key] = unit[target];
-			});
-		});
-	};
-
-	$scope.databases = Entities.query($scope.dataSrc.api.database, {site: Site.current().name});
-	_fillAttr($scope.databases, "database", $scope.dataSrc.mapping.database);
-
-	$scope.loadTables = function(database) {
-		if(database.tables) return;
-		var _qry = {
-			site: Site.current().name
-		};
-		_qry[$scope.dataSrc.mapping.database] = database[$scope.dataSrc.mapping.database];
-		database.tables = Entities.query($scope.dataSrc.api.table, _qry);
-		_fillAttr(database.tables, "table", $scope.dataSrc.mapping.table);
-		_fillAttr(database.tables, "database", $scope.dataSrc.mapping.database);
-	};
-
-	$scope.loadColumns = function(database, table) {
-		$scope.table = table;
-
-		if(table.columns) return;
-		var _qry = {
-			site: Site.current().name
-		};
-		_qry[$scope.dataSrc.mapping.database] = database[$scope.dataSrc.mapping.database];
-		_qry[$scope.dataSrc.mapping.table] = table[$scope.dataSrc.mapping.table];
-		table.columns = Entities.query($scope.dataSrc.api.column, _qry);
-		_fillAttr(table.columns, "column", $scope.dataSrc.mapping.column);
-	};
-
-	// =================== Sensitivity ===================
-	$scope.markSensitivity = function(item, event) {
-		if(event) event.stopPropagation();
-
-		$scope._oriItem = item;
-		$scope._markItem = {
-			prefix: $scope.dataSrc.prefix,
-			tags: {
-				site: Site.current().name,
-			},
-			sensitivityType: ""
-		};
-		$scope._markItem.tags[$scope.dataSrc.keys[0]] = item.resource;
-		$("#sensitivityMDL").modal();
-		setTimeout(function() {
-			$("#sensitiveType").focus();
-		}, 500);
-	};
-	$scope.confirmUpateSensitivity = function() {
-		$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
-		var _promise = Entities.updateEntity($scope.dataSrc.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
-			Entities.dialog(data);
-		});
-		$("#sensitivityMDL").modal('hide');
-	};
-	$scope.unmarkSensitivity = function(item, event) {
-		if(event) event.stopPropagation();
-
-		$.dialog({
-			title: "Unmark Confirm",
-			content: "Do you want to remove the sensitivity mark on '" + item.resource + "'?",
-			confirm: true
-		}, function(ret) {
-			if(!ret) return;
-
-			var _qry = {
-				site: Site.current().name,
-			};
-			_qry[$scope.dataSrc.keys[0]] = item.resource;
-			Entities.deleteEntities($scope.dataSrc.service, _qry);
-
-			item.sensitiveType = null;
-			$scope.$apply();
-		});
-	};
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/ctrl/siteController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/siteController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/siteController.js
deleted file mode 100644
index c54e99d..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/siteController.js
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// =============================================================
-// =                         Site List                         =
-// =============================================================
-damControllers.controller('siteListCtrl', function(globalContent, Site, damContent, $scope, $q, Entities) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.hideSite = true;
-
-	$scope._siteEntity = null;
-	$scope._siteEntityLock = false;
-
-	$scope._dataSrcEntity = null;
-	$scope._dataSrcEntityLock = false;
-
-	$scope.dataSrcList = Entities.queryGroup("AlertStreamService", '@dataSource=~".*"', "@dataSource", "count");
-
-	// Policy statistic
-	$scope.policyStatistic = Entities.queryGroup("AlertDefinitionService", '@dataSource=~".*"', "@site,@dataSource", "count");
-	$scope.getPolicyCount = function(site, dataSource) {
-		for(var i = 0 ; i < $scope.policyStatistic.length ; i += 1) {
-			var _cur = $scope.policyStatistic[i];
-			if(_cur.key[0] === site && _cur.key[1] === dataSource) {
-				return _cur.value[0];
-			}
-		}
-		return 0;
-	};
-
-	// Alert statistic
-	$scope.alertStatistic = Entities.queryGroup("AlertService", {_duration: 1000 * 60 * 60 * 24 * 30}, "@site,@dataSource", "count");
-	$scope.getAlertCount = function(site, dataSource) {
-		for(var i = 0 ; i < $scope.alertStatistic.length ; i += 1) {
-			var _cur = $scope.alertStatistic[i];
-			if(_cur.key[0] === site && _cur.key[1] === dataSource) {
-				return _cur.value[0];
-			}
-		}
-		return 0;
-	};
-
-	// =========================================== Site ===========================================
-	$scope.showSiteEditor = function(site) {
-		$("#siteMDL").modal("show");
-		setTimeout(function() {
-			$("#siteName").focus();
-		}, 500);
-
-		$scope._siteEntity = {
-			dataSrcList: {}
-		};
-		$.each($scope.dataSrcList, function(i, item) {
-			$scope._siteEntity.dataSrcList[item.key[0]] = {
-				name: item.key[0],
-				enabled: false
-			};
-		});
-
-		if(site) {
-			$scope._siteEntity.srcSite = site;
-			$scope._siteEntity.name = site.name;
-
-			$.each(site.dataSrcList, function(i, dataSrc) {
-				$scope._siteEntity.dataSrcList[dataSrc.tags.dataSource].enabled = dataSrc.enabled === undefined ? true : dataSrc.enabled;
-			});
-		}
-	};
-	$scope.checkUpdateSite = function() {
-		if(!$scope._siteEntity || !$scope._siteEntity.dataSrcList) return false;
-
-		var _hasDataSrc = !!common.array.find(true, common.map.toArray($scope._siteEntity.dataSrcList), "enabled");
-		return $scope._siteEntity.name && _hasDataSrc && !$scope._siteEntityLock;
-	};
-	$scope.confirmUpateSite = function() {
-		var promiseList = [];
-		$scope._siteEntityLock = true;
-
-		if($scope._siteEntity.srcSite) {
-			promiseList = [];
-			$.each($scope._siteEntity.dataSrcList, function(name, dataSrc) {
-				var _entity = {
-					enabled: dataSrc.enabled,
-					tags: {
-						site: $scope._siteEntity.name,
-						dataSource: name,
-					},
-				};
-
-				if(dataSrc.enabled) {
-					promiseList.push(Entities.updateEntity("AlertDataSourceService", _entity)._promise);
-				} else {
-					var _dataSrc = common.array.find(name, $scope._siteEntity.srcSite.dataSrcList, "tags.dataSource");
-					if(_dataSrc) {
-						_dataSrc.enabled = false;
-						promiseList.push(Entities.updateEntity("AlertDataSourceService", _entity)._promise);
-					}
-				}
-			});
-		} else {
-			$.each($scope._siteEntity.dataSrcList, function(name, dataSrc) {
-				if(!dataSrc.enabled) return;
-
-				var _entity = {
-					enabled: true,
-					tags: {
-						site: $scope._siteEntity.name,
-						dataSource: name,
-					},
-				};
-				promiseList.push(Entities.updateEntity("AlertDataSourceService", _entity)._promise);
-			});
-		}
-		
-		$q.all(promiseList).then(function() {
-			$("#siteMDL").modal("hide")
-			.on("hidden.bs.modal", function() {
-				$("#siteMDL").off("hidden.bs.modal");
-				Site.refresh();
-			});
-		}).finally(function() {
-			$scope._siteEntityLock = false;
-		});
-	};
-
-	$scope.deleteSite = function(site) {
-		$.dialog({
-			title: "Delete Confirm",
-			content: "<span class='text-red fa fa-exclamation-triangle pull-left' style='font-size: 50px;'></span>" +
-					"<p>You are <strong class='text-red'>DELETING</strong> the site '<strong>" + site.name + "</strong>'!</p>" +
-					"<p>Proceed to delete?</p>",
-			buttons: [
-				{name: "Delete", class: "btn btn-danger", value: true},
-				{name: "Cancel", class: "btn btn-default", value: false},
-			]
-		}, function(ret) {
-			if(!ret) return;
-
-			Entities.deleteEntities("AlertDataSourceService", {
-				site: site.name
-			})._promise.then(function() {
-				Site.refresh();
-			});
-		});
-	};
-
-	// ======================================= Data Source ========================================
-	$scope.showDataSourceEditor = function(dataSrc) {
-		$("#dataSrcMDL").modal("show");
-		setTimeout(function() {
-			$("#dataSrcConfig").focus();
-		}, 500);
-
-		$scope._dataSrcEntity = dataSrc;
-	};
-
-	$scope.confirmUpateDataSource = function() {
-		$scope._dataSrcEntityLock = true;
-		Entities.updateEntity("AlertDataSourceService", $scope._dataSrcEntity)._promise.then(function() {
-			$("#dataSrcMDL").modal("hide");
-		}).finally(function() {
-			$scope._dataSrcEntityLock = false;
-		});
-	};
-
-	$scope.confirmDeleteDataSource = function() {
-		console.log($scope._dataSrcEntity);
-		$("#dataSrcMDL").modal("hide")
-		.on('hidden.bs.modal', function (e) {
-			$("#dataSrcMDL").off('hidden.bs.modal');
-
-			var _additionalContent = Site.find($scope._dataSrcEntity.tags.site).dataSrcList.length > 1 ? "" : "<p class='text-muted' style='margin-left: 60px;'>(This site has only one source. Delete will remove site either.)</p>";
-
-			$.dialog({
-				title: "Delete Confirm",
-				content: "<span class='text-red fa fa-exclamation-triangle pull-left' style='font-size: 50px;'></span>" +
-						"<p>You are <strong class='text-red'>DELETING</strong> the data source '<strong>" + $scope._dataSrcEntity.tags.dataSource + "</strong>' of '" + $scope._dataSrcEntity.tags.site + "'!</p>" +
-						"<p>Proceed to delete?</p>" + _additionalContent,
-				buttons: [
-					{name: "Delete", class: "btn btn-danger", value: true},
-					{name: "Cancel", class: "btn btn-default", value: false},
-				]
-			}, function(ret) {
-				if(!ret) return;
-
-				Entities.deleteEntity("AlertDataSourceService", $scope._dataSrcEntity)._promise.then(function() {
-					Site.refresh();
-				});
-			});
-		});
-	};
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/ctrl/streamController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/streamController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/streamController.js
deleted file mode 100644
index 8d7347d..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/streamController.js
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// =============================================================
-// =                        Stream List                        =
-// =============================================================
-damControllers.controller('streamListCtrl', function(globalContent, damContent, $scope, $route, $routeParams, $q, Entities) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.hideSite = true;
-
-	$scope.streams = {};
-	$scope._streamEntity = null;
-	$scope._streamEntityLock = false;
-
-	// =========================================== List ===========================================
-	var _streamList = Entities.queryEntities("AlertStreamService", '@streamName=~".*"');
-	var _streamSchemaList = Entities.queryEntities("AlertStreamSchemaService", '@streamName=~".*"');
-	$scope.streamList = _streamList;
-	$scope.streamSchemaList = _streamSchemaList;
-
-	_streamList._promise.then(function() {
-		$.each(_streamList, function(i, stream) {
-			stream.metaList = [];
-			$scope.streams[stream.tags.dataSource + "_" + stream.tags.streamName] = stream;
-		});
-	});
-
-	$q.all([_streamList._promise, _streamSchemaList._promise]).then(function(res) {
-		$.each(_streamSchemaList, function(i, meta) {
-			var _stream = $scope.streams[meta.tags.dataSource + "_" + meta.tags.streamName];
-			if(_stream) {
-				_stream.metaList.push(meta);
-			} else {
-				console.warn("[Meta] Stream not match:", meta.tags.dataSource + "_" + meta.tags.streamName);
-			}
-		});
-	});
-
-	// =========================================== Edit ===========================================
-	$scope.showStreamEditor = function(stream) {
-		$("#streamMDL").modal("show");
-		setTimeout(function() {
-			$("#dataSource").focus();
-		}, 500);
-
-		$scope._streamEntity = {
-			dataSource: "",
-			streamName: "",
-			description: "",
-			metaList: [],
-		};
-		if(stream) {
-			$scope._streamEntity.srcStream = stream;
-			$scope._streamEntity.dataSource = stream.tags.dataSource;
-			$scope._streamEntity.streamName = stream.tags.streamName;
-			$scope._streamEntity.desc = stream.desc;
-
-			$scope._streamEntity.metaList = $.map(stream.metaList, function(meta) {
-				return {
-					srcMeta: meta,
-					attrName: meta.tags.attrName,
-					attrDisplayName: meta.attrDisplayName,
-					attrType: meta.attrType,
-					attrDescription: meta.attrDescription,
-				};
-			});
-		}
-	};
-
-	$scope.deleteMeta = function(meta) {
-		$.dialog({
-			title: "Delete confirm",
-			content: "<p>You are <strong class='text-red'>DELETING</strong> the meta '<strong>" + meta.attrName + "</strong>'!</p>" +
-					"<p>Proceed to delete?</p>",
-			buttons: [
-				{name: "Delete", class: "btn btn-danger", value: true},
-				{name: "Cancel", class: "btn btn-default", value: false},
-			]
-		}, function(ret) {
-			if(!ret) return;
-
-			common.array.remove(meta, $scope._streamEntity.metaList);
-			$scope.$apply();
-		});
-	};
-
-	$scope.checkUpdateStream = function() {
-		if(!$scope._streamEntity || $scope._streamEntityLock) return false;
-
-		var _pass = true;
-
-		if(!$scope._streamEntity.dataSource) _pass = false;
-		if(!$scope._streamEntity.streamName) _pass = false;
-
-		$.each($scope._streamEntity.metaList, function(i, meta) {
-			if(!meta.attrName || !meta.attrType) {
-				_pass = false;
-				return false;
-			}
-		});
-
-		return _pass;
-	};
-
-	$scope.confirmDeleteStream = function() {
-		$.dialog({
-			title: "Delete Confirm",
-			content: "<span class='text-red fa fa-exclamation-triangle pull-left' style='font-size: 50px;'></span>" +
-					"<p>You are <strong class='text-red'>DELETING</strong> the stream '<strong>" + $scope._streamEntity.streamName + "</strong>'!</p>" +
-					"<p>Proceed to delete?</p>",
-			buttons: [
-				{name: "Delete", class: "btn btn-danger", value: true},
-				{name: "Cancel", class: "btn btn-default", value: false},
-			]
-		}, function(ret) {
-			if(!ret) return;
-
-			var _promiseStream = Entities.deleteEntities("AlertStreamService", {
-				dataSource: $scope._streamEntity.dataSource,
-				streamName: $scope._streamEntity.streamName,
-			})._promise;
-			var _promiseStreamSchema = Entities.deleteEntities("AlertStreamSchemaService", {
-				dataSource: $scope._streamEntity.dataSource,
-				streamName: $scope._streamEntity.streamName,
-			})._promise;
-
-			$q.all(_promiseStream, _promiseStreamSchema).then(function() {
-				$("#streamMDL").modal("hide");
-
-				setTimeout(function() {
-					$route.reload();
-				}, 500);
-			});
-		});
-	};
-
-	$scope.confirmUpateStream = function() {
-		$scope._streamEntityLock = true;
-
-		// Stream entity
-		var _entity = {
-			prefix: "alertStream",
-			tags: {
-				dataSource: $scope._streamEntity.dataSource,
-				streamName: $scope._streamEntity.streamName,
-			},
-			desc: $scope._streamEntity.desc
-		};
-
-		// Merge original stream
-		if($scope._streamEntity.srcStream) {
-			$.extend(_entity, $scope._streamEntity.srcStream);
-		}
-
-		// Meta entities
-		var _metaList = $.map($scope._streamEntity.metaList, function(meta) {
-			return {
-				prefix: "alertStreamSchema",
-				attrType: meta.attrType,
-				attrDisplayName: meta.attrDisplayName,
-				attrDescription: meta.attrDescription,
-				tags: {
-					dataSource: _entity.tags.dataSource,
-					streamName: _entity.tags.streamName,
-					attrName: meta.attrName,
-				},
-			};
-		});
-
-		Entities.updateEntity("AlertStreamService", _entity)._promise.then(function() {
-			Entities.deleteEntities("AlertStreamSchemaService", {
-				dataSource: _entity.tags.dataSource,
-				streamName: _entity.tags.streamName,
-			})._promise.then(function() {
-				Entities.updateEntity("AlertStreamSchemaService", _metaList)._promise.finally(function() {
-					Site.refresh();
-				});
-			}).finally(function() {
-				$("#streamMDL").modal("hide");
-				$scope._streamEntityLock = false;
-
-				setTimeout(function() {
-					$route.reload();
-				}, 500);
-			});
-		}, function() {
-			$scope._streamEntityLock = false;
-		});
-	};
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/ctrl/userProfileController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/userProfileController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/userProfileController.js
deleted file mode 100644
index 2d182b0..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/userProfileController.js
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// =============================================================
-// =                     User Profile List                     =
-// =============================================================
-damControllers.controller('userProfileListCtrl', function(globalContent, Site, damContent, $scope, $interval, Entities) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.pageSubTitle = Site.current().name;
-
-	$scope.common = common;
-	$scope.algorithms = [];
-
-	// ======================================== Algorithms ========================================
-	$scope.algorithmEntity = {};
-	Entities.queryEntities("AlertDefinitionService", {site: Site.current().name, dataSource: "userProfile"})._promise.then(function(data) {
-		$scope.algorithmEntity = common.getValueByPath(data, "data.obj[0]");
-		$scope.algorithmEntity.policy = common.parseJSON($scope.algorithmEntity.policyDef);
-	});
-
-	// ======================================= User profile =======================================
-	$scope.profileList = Entities.queryEntities("MLModelService", {site: Site.current().name}, ["user", "algorithm", "content", "version"]);
-	$scope.profileList._promise.then(function() {
-		var _algorithms = {};
-		var _users = {};
-
-		// Map user
-		$.each($scope.profileList, function(i, unit) {
-			_algorithms[unit.tags.algorithm] = unit.tags.algorithm;
-			var _user = _users[unit.tags.user] = _users[unit.tags.user] || {user: unit.tags.user};
-			_user[unit.tags.algorithm] = {
-				version: unit.version
-			};
-
-			// DE
-			if(unit.tags.algorithm === "DE") {
-				var _statistics = common.parseJSON(unit.content);
-				_statistics = common.getValueByPath(_statistics, "statistics", []);
-				_user[unit.tags.algorithm].topCommands = $.map(common.array.top(_statistics, "mean"), function(command) {
-					return command.commandName;
-				});
-			}
-		});
-
-		// Map algorithms
-		$scope.algorithms = $.map(_algorithms, function(algorithm) {
-			return algorithm;
-		}).sort();
-
-		$scope.profileList.splice(0);
-		$scope.profileList.push.apply($scope.profileList, common.map.toArray(_users));
-	});
-
-	// =========================================== Task ===========================================
-	$scope.tasks = [];
-	function _loadTasks() {
-		var _tasks = Entities.queryEntities("ScheduleTaskService", {
-			site: Site.current().name,
-			_pageSize: 100,
-			_duration: 1000 * 60 * 60 * 24 * 14,
-			__ETD: 1000 * 60 * 60 * 24
-		});
-		_tasks._promise.then(function() {
-			$scope.tasks.splice(0);
-			$scope.tasks.push.apply($scope.tasks, _tasks);
-
-			// Duration
-			$.each($scope.tasks, function(i, data) {
-				if(data.timestamp && data.updateTime) {
-					var _ms = (new moment(data.updateTime)).diff(new moment(data.timestamp));
-					var _d = moment.duration(_ms);
-					data._duration = Math.floor(_d.asHours()) + moment.utc(_ms).format(":mm:ss");
-					data.duration = _ms;
-				} else {
-					data._duration = "--";
-				}
-			});
-		});
-	}
-
-	$scope.runningTaskCount = function () {
-		return common.array.count($scope.tasks, "INITIALIZED", "status") + 
-				common.array.count($scope.tasks, "PENDING", "status") +
-				common.array.count($scope.tasks, "EXECUTING", "status");
-	};
-
-	// Create task
-	$scope.updateTask = function() {
-		$.dialog({
-			title: "Confirm",
-			content: "Do you want to update now?",
-			confirm: true
-		}, function(ret) {
-			if(!ret) return;
-
-			var _entity = {
-				status: "INITIALIZED",
-				detail: "Newly created command",
-				tags: {
-					site: Site.current().name,
-					type: "USER_PROFILE_TRAINING"
-				},
-				timestamp: +new Date()
-			};
-			Entities.updateEntity("ScheduleTaskService", _entity, {timestamp: false})._promise.success(function(data) {
-				if(!Entities.dialog(data)) {
-					_loadTasks();
-				}
-			});
-		});
-	};
-
-	// Show detail
-	$scope.showTaskDetail = function(task) {
-		var _content = $("<pre>").text(task.detail);
-
-		var $mdl = $.dialog({
-			title: "Detail",
-			content: _content
-		});
-
-		// TODO: Remove task
-		_content.click(function(e) {
-			if(!e.ctrlKey) return;
-
-			$.dialog({
-				title: "Confirm",
-				content: "Remove this task?",
-				confirm: true
-			}, function(ret) {
-				if(!ret) return;
-
-				$mdl.modal('hide');
-				Entities.deleteEntity("ScheduleTaskService", task)._promise.then(function(data) {
-					_loadTasks();
-				});
-			});
-		});
-	};
-
-	_loadTasks();
-	var _loadInterval = $interval(_loadTasks, app.time.refreshInterval);
-	$scope.$on('$destroy',function(){
-		$interval.cancel(_loadInterval);
-	});
-});
-
-// =============================================================
-// =                    User Profile Detail                    =
-// =============================================================
-damControllers.controller('userProfileDetailCtrl', function(globalContent, Site, damContent, $scope, $routeParams, Entities) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.pageTitle = "User Profile";
-	globalContent.pageSubTitle = Site.current().name;
-	globalContent.navPath = ["User Profile", "Detail"];
-
-	$scope.user = $routeParams.user;
-
-	// User profile
-	$scope.profiles = {};
-	$scope.profileList = Entities.queryEntities("MLModelService", {site: Site.current().name, user: $scope.user});
-	$scope.profileList._promise.then(function() {
-		$.each($scope.profileList, function(i, unit) {
-			unit._content = common.parseJSON(unit.content);
-			$scope.profiles[unit.tags.algorithm] = unit;
-		});
-
-		// DE
-		if($scope.profiles.DE) {
-			console.log($scope.profiles.DE);
-
-			$scope.profiles.DE._chart = {};
-
-			$scope.profiles.DE.estimates = {};
-			$.each($scope.profiles.DE._content, function(key, value) {
-				if(key !== "statistics") {
-					$scope.profiles.DE.estimates[key] = value;
-				}
-			});
-
-			var _meanList = [];
-			var _stddevList = [];
-
-			$.each($scope.profiles.DE._content.statistics, function(i, unit) {
-				_meanList[i] = {
-					x: unit.commandName,
-					y: unit.mean
-				};
-				_stddevList[i] = {
-					x: unit.commandName,
-					y: unit.stddev
-				};
-			});
-			$scope.profiles.DE._chart.series = [
-				{
-					key: "mean",
-					values: _meanList
-				},
-				{
-					key: "stddev",
-					values: _stddevList
-				}
-			];
-
-			// Percentage table list
-			$scope.profiles.DE.meanList = [];
-			var _total = common.array.sum($scope.profiles.DE._content.statistics, "mean");
-			$.each($scope.profiles.DE._content.statistics, function(i, unit) {
-				$scope.profiles.DE.meanList.push({
-					command: unit.commandName,
-					percentage: unit.mean / _total
-				});
-			});
-		}
-
-		// EigenDecomposition
-		if($scope.profiles.EigenDecomposition && $scope.profiles.EigenDecomposition._content.principalComponents) {
-			$scope.profiles.EigenDecomposition._chart = {
-				series: [],
-			};
-
-			$.each($scope.profiles.EigenDecomposition._content.principalComponents, function(z, grp) {
-				var _line = [];
-				$.each(grp, function(x, y) {
-					_line.push([x,y,z]);
-				});
-
-				$scope.profiles.EigenDecomposition._chart.series.push({
-					data: _line
-				});
-			});
-		}
-	});
-
-	// UI
-	$scope.showRawData = function(content) {
-		$.dialog({
-			title: "Raw Data",
-			content: $("<pre>").text(content)
-		});
-	};
-});
\ No newline at end of file



[7/9] incubator-eagle git commit: EAGLE-139 EAGLE-163 Eagle UI Modularization and fix bugs in policy extensions

Posted by hd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/resources/eagleSecurity.xml
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/resources/eagleSecurity.xml b/eagle-webservice/src/main/resources/eagleSecurity.xml
index 2b106b3..b8683dc 100644
--- a/eagle-webservice/src/main/resources/eagleSecurity.xml
+++ b/eagle-webservice/src/main/resources/eagleSecurity.xml
@@ -28,6 +28,8 @@
         <scr:http-basic entry-point-ref="unauthorisedEntryPoint"/>
         <scr:intercept-url pattern="/rest/entities" method="POST" access="hasRole('ROLE_ADMIN')" />
         <scr:intercept-url pattern="/rest/entities/delete" method="POST" access="hasRole('ROLE_ADMIN')" />
+        <scr:intercept-url pattern="/rest/module/*" method="DELETE" access="hasRole('ROLE_ADMIN')" />
+        <scr:intercept-url pattern="/rest/module/*" method="POST" access="hasRole('ROLE_ADMIN')" />
         <scr:intercept-url pattern="/rest/list" method="POST" access="hasRole('ROLE_ADMIN')" />
         <scr:intercept-url pattern="/rest/status" method="GET" access="permitAll" />
         <scr:intercept-url pattern="/rest/*" access="isAuthenticated()" />
@@ -82,7 +84,7 @@
                 <scr:user-service>
                     <!-- user admin has role ADMIN, user eagle has role USER, both have password "secret" -->
                 	<scr:user name="eagle" password="$2a$10$TwALMRHpSetDaeTurg9rj.DnIdOde4fkQGBSPG3fVqtH.G5ZH8sQK" authorities="ROLE_USER" />
-                    <scr:user name="admin" password="$2a$10$TwALMRHpSetDaeTurg9rj.DnIdOde4fkQGBSPG3fVqtH.G5ZH8sQK" authorities="ROLE_ADMIN, ROLE_USER" />
+                    <scr:user name="admin" password="$2a$10$TwALMRHpSetDaeTurg9rj.DnIdOde4fkQGBSPG3fVqtH.G5ZH8sQK" authorities="ROLE_ADMIN" />
                 </scr:user-service>
                 <scr:password-encoder ref="passwordEncoder" />
             </scr:authentication-provider>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/Gruntfile.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/Gruntfile.js b/eagle-webservice/src/main/webapp/Gruntfile.js
index a32b1da..d547ad6 100644
--- a/eagle-webservice/src/main/webapp/Gruntfile.js
+++ b/eagle-webservice/src/main/webapp/Gruntfile.js
@@ -45,6 +45,9 @@ module.exports = function (grunt) {
 		concat: {
 			app: {
 				src: [
+					'app/public/js/srv/main.js',
+					'app/public/js/srv/**.js',
+
 					'app/public/js/app.js',
 					'app/public/js/app.*.js',
 
@@ -54,7 +57,7 @@ module.exports = function (grunt) {
 					'app/public/js/components/**.js',
 					'app/public/js/components/**/**.js',
 
-					'app/public/js/ctrl/damController.js',
+					'app/public/js/ctrl/main.js',
 					'app/public/js/ctrl/*.js',
 				],
 				dest: 'tmp/public/js/scripts.js'
@@ -89,14 +92,24 @@ module.exports = function (grunt) {
 				options: {
 					mangle: false
 				},
-				src: 'tmp/public/js/scripts.js',
-				dest: 'tmp/public/js/scripts.min.js'
-			}
+				files: [
+					{
+						src: 'tmp/public/js/scripts.js',
+						dest: 'tmp/public/js/scripts.min.js'
+					},
+					{
+						expand: true,
+						src: '**/*.js',
+						dest: 'tmp/feature',
+						cwd: 'app/public/feature'
+					}
+				]
+			},
 		},
 		cssmin: {
 			ui: {
 				files: {
-					'tmp/public/css/styles.css': ['app/public/css/main.css']
+					'tmp/public/css/styles.css': ['app/public/css/main.css', 'app/public/css/animation.css']
 				}
 			}
 		},
@@ -107,6 +120,11 @@ module.exports = function (grunt) {
 			}
 		},
 		copy: {
+			feature: {
+				files: [
+					{expand: true, cwd: 'app/', src: ['public/feature/**'], dest: 'tmp'}
+				]
+			},
 			ui: {
 				files: [
 					{expand: true, cwd: 'tmp/', src: ['**'], dest: 'ui'},
@@ -133,6 +151,7 @@ module.exports = function (grunt) {
 		// Clean Env
 		'clean:build',
 		// Compress JS
+		'copy:feature',
 		'concat:app',
 		'regex-replace:strict',
 		'uglify',
@@ -142,7 +161,7 @@ module.exports = function (grunt) {
 		'concat:css',
 		// Pass HTML Resources
 		'htmlrefs',
-		'copy',
+		'copy:ui',
 		// Clean Env
 		'clean:tmp',
 	]);

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/index.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/index.html b/eagle-webservice/src/main/webapp/app/index.html
index 82b543f..f3d61a4 100644
--- a/eagle-webservice/src/main/webapp/app/index.html
+++ b/eagle-webservice/src/main/webapp/app/index.html
@@ -30,72 +30,63 @@
 
 		<!-- ref:css public/css/styles.min.css -->
 		<link href="public/css/main.css" rel="stylesheet" type="text/css" media="screen">
-		<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css" media="screen">
-		<link href="../node_modules/zombiej-bootstrap-components/bootstrap-components/css/bootstrap-components.min.css" rel="stylesheet" type="text/css" media="screen">
-		<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
-		<link href="../node_modules/zombiej-nvd3/build/nv.d3.min.css" rel="stylesheet" type="text/css" />
-		<link href="../node_modules/admin-lte/dist/css/AdminLTE.min.css" rel="stylesheet" type="text/css" />
-		<link href="../node_modules/admin-lte/dist/css/skins/skin-blue.min.css" rel="stylesheet" type="text/css" />
+		<link href="public/css/animation.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="../node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="../node_modules/zombiej-bootstrap-components/bootstrap-components/css/bootstrap-components.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="../node_modules/zombiej-nvd3/build/nv.d3.css" rel="stylesheet" type="text/css" />
+		<link href="../node_modules/font-awesome/css/font-awesome.css" rel="stylesheet" type="text/css" />
+		<link href="../node_modules/admin-lte/dist/css/AdminLTE.css" rel="stylesheet" type="text/css" />
+		<link href="../node_modules/admin-lte/dist/css/skins/skin-blue.css" rel="stylesheet" type="text/css" />
 		<!-- endref -->
 	</head>
-	<body class="skin-blue sidebar-mini" ng-class="{'no-sidebar' : globalContent.hideSidebar}">
+	<body class="skin-blue sidebar-mini" ng-class="{'no-sidebar' : PageConfig.hideSidebar}">
 		<!-- Site wrapper -->
 		<div class="wrapper">
 			<header class="main-header">
 				<a href="#/" class="logo">
-					<span class="logo-mini">DAM</span>
-					<span class="logo-lg">Eagle <small>Data Activity Monitoring</small></span>
+					<span class="logo-mini"><img src="public/images/favicon_white.png" /></span>
+					<span class="logo-lg">Apache Eagle</span>
 				</a>
 				<!-- Header Navbar: style can be found in header.less -->
 				<nav class="navbar navbar-static-top" role="navigation">
 					<!-- Sidebar toggle button-->
-					<a ng-hide="globalContent.hideSidebar" class="sidebar-toggle" data-toggle="offcanvas" role="button">
+					<a ng-hide="PageConfig.hideSidebar" class="sidebar-toggle" data-toggle="offcanvas" role="button">
 						<span class="sr-only">Toggle navigation</span>
 						<span class="icon-bar"></span>
 						<span class="icon-bar"></span>
 						<span class="icon-bar"></span>
 					</a>
+
 					<div class="navbar-custom-menu">
 						<ul class="nav navbar-nav">
 							<!-- Site -->
-							<li class="dropdown" ng-show="!globalContent.hideSite && !globalContent.lockSite">
+							<li class="dropdown" ng-show="!PageConfig.hideSite && !PageConfig.lockSite">
 								<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
 									<i class="fa fa-server"></i>
-									{{site.current().name}}
+									{{Site.current().tags.site}}
 									<i class="fa fa-caret-down"></i>
 								</a>
 								<ul class="dropdown-menu">
-									<li ng-repeat="sites in site.list">
-										<a ng-click="site.current(sites);">
-											<span class="fa fa-database"></span> {{sites.name}}
+									<li ng-repeat="_site in Site.list" ng-if="_site.enabled">
+										<a ng-click="Site.current(_site);">
+											<span class="fa fa-database"></span> {{_site.tags.site}}
 										</a>
 									</li>
-									<!--li role="separator" class="divider"></li>
-									<li>
-										<a href="#/dam/siteList"><span class="fa fa-cog"></span>Config Sites</a>
-									</li-->
 								</ul>
 							</li>
-							<li class="dropdown" ng-show="globalContent.lockSite">
+							<li class="dropdown" ng-show="PageConfig.lockSite">
 								<a>
 									<i class="fa fa-server"></i>
-									{{site.current().name}}
+									{{Site.current().tags.site}}
 								</a>
 							</li>
 
 							<!-- User -->
-							<li class="dropdown user user-menu" ng-hide="globalContent.hideUser">
+							<li class="dropdown user user-menu" ng-hide="PageConfig.hideUser">
 								<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
 									<i class="fa fa-user"></i>
-									{{auth.userProfile.username}}
+									{{Auth.userProfile.username}}
 								</a>
-								<!--ul class="dropdown-menu">
-									<li>
-										<a ng-click="logout();">
-											<span class="fa fa-sign-out"></span> Logout
-										</a>
-									</li>
-								</ul-->
 								<ul class="dropdown-menu">
 									<!-- User image -->
 									<li class="user-header">
@@ -103,14 +94,17 @@
 											<span class="fa fa-user" alt="User Image"></span>
 										</span>
 										<p>
-											{{auth.userProfile.username}}
+											{{Auth.userProfile.username}}
 											<small>
-												<span ng-repeat="role in auth.userProfile.authorities">{{role.authority}} </span>
+												<span ng-repeat="role in Auth.userProfile.authorities">{{role.authority}} </span>
 											</small>
 										</p>
 									</li>
 									<!-- Menu Footer-->
 									<li class="user-footer">
+										<div class="pull-left" ng-if="Auth.isRole('ROLE_ADMIN')">
+											<a href="#/config/site" class="btn btn-default btn-flat">Configuration</a>
+										</div>
 										<div class="pull-right">
 											<a ng-click="logout();" class="btn btn-default btn-flat">Sign out</a>
 										</div>
@@ -119,16 +113,45 @@
 							</li>
 						</ul>
 					</div>
+
+					<!-- Applications -->
+					<div ng-hide="PageConfig.hideApplication">
+						<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#moduleMenu">
+							<span class="sr-only">Toggle navigation</span>
+							<span class="fa fa-map"></span>
+						</button>
+						<div class="collapse navbar-collapse" id="moduleMenu">
+							<ul class="nav navbar-nav">
+								<li ng-repeat="_grp in Site.current().applicationGroupList" ng-if="_grp.enabledList.length"
+									class="dropdown" ng-class="{active: Application.current().groupName === _grp.name}">
+									<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
+										{{_grp.name}}
+									</a>
+									<ul class="dropdown-menu">
+										<li ng-repeat="_app in _grp.enabledList">
+											<a ng-click="Application.current(_app);">
+												<span class="fa fa-cubes"></span> {{_app.displayName}}
+											</a>
+										</li>
+									</ul>
+								</li>
+							</ul>
+						</div>
+					</div>
 				</nav>
 			</header>
 
 			<!-- =============================================== -->
 			<!-- Left side column. contains the side bar -->
-			<aside class="main-sidebar" ng-hide="globalContent.hideSidebar">
+			<aside class="main-sidebar" ng-hide="PageConfig.hideSidebar">
 				<!-- side bar: style can be found in sidebar.less -->
 				<section class="sidebar">
 					<ul class="sidebar-menu">
-						<li ng-repeat="page in globalContent.pageList" ng-class="getNavClass(page)" ng-show="getNavVisible(page)">
+						<li class="header">
+							{{Application.current().groupName || 'Application'}} >
+							{{Application.current().displayName || 'Features'}}
+						</li>
+						<li ng-repeat="page in PageConfig.navConfig.pageList track by $index" ng-class="getNavClass(page)" ng-show="getNavVisible(page)">
 							<a href="{{page.url}}">
 								<i class="fa fa-{{page.icon}}"></i> <span>{{page.title}}</span> 
 							</a>
@@ -142,17 +165,18 @@
 			<!-- Right side column. Contains the navbar and content of the page -->
 			<div class="content-wrapper">
 				<!-- Content Header (Page header) -->
-				<section class="content-header" ng-hide="globalContent.hideSidebar">
+				<section class="content-header" ng-hide="PageConfig.hideSidebar">
 					<h1>
-						<span class="pageTitle">{{globalContent.pageTitle}}</span>
-						<small class="pageSubTitle">{{globalContent.pageSubTitle}}</small>
+						<span class="pageTitle">{{PageConfig.pageTitle}}</span>
+						<small class="pageSubTitle">{{PageConfig.pageSubTitle}}</small>
 					</h1>
 
+
 					<ol class="breadcrumb">
-						<li ng-repeat="nav in globalContent.navPath">
-							<a ng-href="{{$last ? '' : globalContent.navMapping[nav]}}">
-								<span class="fa fa-home" ng-show="$first"></span>
-								{{nav}}
+						<li ng-repeat="navPath in PageConfig.navPath">
+							<a ng-href="#{{navPath.path}}">
+								<span class="fa fa-home" ng-if="$first"></span>
+								{{navPath.title || navPath.path}}
 							</a>
 						</li>
 					</ol>
@@ -160,7 +184,9 @@
 
 				<!-- Main content -->
 				<section class="content">
-					<div id="content" ng-view></div>
+					<div id="content">
+						<div ui-view></div>
+					</div>
 				</section><!-- /.content -->
 			</div><!-- /.content-wrapper -->
 		</div><!-- ./wrapper -->
@@ -176,12 +202,23 @@
 		<script src="../node_modules/angular/angular.js"></script>
 		<script src="../node_modules/angular-resource/angular-resource.js"></script>
 		<script src="../node_modules/angular-route/angular-route.js"></script>
-		<script src="../node_modules/angular-cookies/angular-cookies.js"></script>
+		<script src="../node_modules/angular-animate/angular-animate.js"></script>
 		<script src="../node_modules/angular-ui-bootstrap/ui-bootstrap-tpls.js"></script>
+		<script src="../node_modules/angular-ui-router/release/angular-ui-router.js"></script>
 		<script src="../node_modules/d3/d3.js"></script>
 		<script src="../node_modules/zombiej-nvd3/build/nv.d3.js"></script>
 
-
+		<!-- Service -->
+		<script src="public/js/srv/main.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/applicationSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/authorizationSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/entitiesSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/siteSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/pageSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/wrapStateSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/uiSrv.js" type="text/javascript" charset="utf-8"></script>
+
+		<!-- Application -->
 		<script src="public/js/app.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/app.ui.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/app.time.js" type="text/javascript" charset="utf-8"></script>
@@ -189,6 +226,7 @@
 
 		<script src="public/js/common.js" type="text/javascript" charset="utf-8"></script>
 
+		<!-- Components -->
 		<script src="public/js/components/main.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/components/sortTable.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/components/tabs.js" type="text/javascript" charset="utf-8"></script>
@@ -196,15 +234,10 @@
 		<script src="public/js/components/charts/line3d.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/components/nvd3.js" type="text/javascript" charset="utf-8"></script>
 
-
-		<script src="public/js/ctrl/damController.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/ctrl/policyController.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/ctrl/siteController.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/ctrl/streamController.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/ctrl/alertController.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/ctrl/sensitivityController.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/ctrl/userProfileController.js" type="text/javascript" charset="utf-8"></script>
+		<!-- Controllers -->
+		<script src="public/js/ctrl/main.js" type="text/javascript" charset="utf-8"></script>
 		<script src="public/js/ctrl/authController.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/ctrl/configurationController.js" type="text/javascript" charset="utf-8"></script>
 		<!-- endref -->
 	</body>
 </html>
\ No newline at end of file

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

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

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

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/alertDetail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/alertDetail.html b/eagle-webservice/src/main/webapp/app/partials/dam/alertDetail.html
deleted file mode 100644
index 9b0ec01..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/alertDetail.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="box box-info">
-	<div class="box-header with-border">
-		<h3 class="box-title" id="policyId">
-			{{alert.tags.policyId}}
-			<small>{{common.format.date(alert.timestamp)}}</small>
-		</h3>
-	</div><!-- /.box-header -->
-
-	<div class="box-body">
-		<a class="btn btn-primary pull-right" href="#/dam/policyDetail/?policy={{alert.tags.policyId}}&site={{alert.tags.site}}&executor={{alert.tags.alertExecutorId}}">View Policy</a>
-
-		<div class="inline-group">
-			<dl><dt>Site</dt><dd>{{alert.tags.site}}</dd></dl>
-			<dl><dt>Data Source</dt><dd>{{alert.tags.dataSource}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>Alert Time</dt><dd>{{common.format.date(alert.timestamp)}}</dd></dl>
-			<dl><dt>Message Time</dt><dd>{{common.format.date(alert.alertContext.properties.timestamp)}}</dd></dl>
-		</div>
-		<!--div class="inline-group">
-			<dl><dt>Severity</dt><dd>{{alert.alertContext.properties.severity}}</dd></dl>
-		</div-->
-		<div class="inline-group">
-			<dl><dt>Stream Name</dt><dd>{{alert.tags.sourceStreams}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>Alert Source</dt><dd>{{alert.tags.alertSource}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>User</dt><dd>{{alert.alertContext.properties.user}}</dd></dl>
-			<dl><dt>Host</dt><dd>{{alert.alertContext.properties.host}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>Event</dt><dd>{{alert.alertContext.properties.alertEvent}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>Message</dt><dd>{{alert.alertContext.properties.alertMessage}}</dd></dl>
-		</div>
-	</div><!-- /.box-body -->
-
-	<div class="overlay" ng-hide="alertList._promise.$$state.status === 1;">
-		<i class="fa fa-refresh fa-spin"></i>
-	</div>
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/alertList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/alertList.html b/eagle-webservice/src/main/webapp/app/partials/dam/alertList.html
deleted file mode 100644
index d5492ca..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/alertList.html
+++ /dev/null
@@ -1,83 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-list-alt"> </i>
-		<h3 class="box-title">
-			{{dataSource || "All Alerts"}}
-			<div class="btn-group">
-				<button class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
-					<i class="fa fa-caret-down"></i>
-				</button>
-				<ul id="programList" class="dropdown-menu" role="menu">
-					<li ng-repeat="dataSrc in site.current().dataSrcList">
-						<a href="#/dam/alertList/{{dataSrc.tags.dataSource}}">{{dataSrc.tags.dataSource}}</a>
-					</li>
-					<li class="divider"></li>
-					<li>
-						<a href="#/dam/alertList">All Policy List</a>
-					</li>
-				</ul>
-			</div>
-		</h3>
-	</div>
-	<div class="box-body">
-		<p ng-show="!alertList.ready">
-			<span class="fa fa-refresh fa-spin"> </span>
-			Loading...
-		</p>
-
-		<div sorttable source="alertList" sort="-timestamp">
-			<table class="table table-bordered" ng-non-bindable>
-				<thead>
-					<tr>
-						<th width="170" sortpath="timestamp">Alert Time</th>
-						<th width="170" sortpath="alertContext.properties.timestamp">Message Time</th>
-						<th width="105" sortpath="tags.dataSource">Data Source</th>
-						<!--th width="70" sortpath="severity">Type</th-->
-						<th width="150" sortpath="tags.policyId">Policy Name</th>
-						<th width="60" sortpath="alertContext.properties.user">User</th>
-						<th width="150" sortpath="alertContext.properties.host">Host</th>
-						<th sortpath="alertContext.properties.emailMessage">Description</th>
-						<th width="50"> </th>
-					</tr>
-				</thead>
-				<tbody>
-					<tr ng-class="{info : item.__new}">
-						<td>{{common.format.date(item.timestamp)}}</td>
-						<td>{{common.format.date(item.alertContext.properties.timestamp)}}</td>
-						<td>{{item.tags.dataSource}}</td>
-						<!--td>{{item.severity}}</td-->
-						<td class="text-nowrap">
-							<a class="fa fa-share-square-o" ng-show="item.tags.policyId"
-							href="#/dam/policyDetail/?policy={{item.tags.policyId}}&site={{item.tags.site}}&executor={{item.tags.alertExecutorId}}"> </a>
-							{{item.tags.policyId}}
-						</td>
-						<td>{{item.alertContext.properties.user}}</td>
-						<td>{{item.alertContext.properties.host}}</td>
-						<td>{{item.alertContext.properties.alertMessage}}</td>
-						<td><a href="#/dam/alertDetail/{{item.encodedRowkey}}">Detail</a></td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-
-	</div>
-	<!--div class="box-footer clearfix">
-	</div-->
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/login.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/login.html b/eagle-webservice/src/main/webapp/app/partials/dam/login.html
deleted file mode 100644
index 0e2287c..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/login.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<div class="login-box">
-	<div class="login-logo">
-		<a href="#/"> Data Activity Monitoring </a>
-	</div>
-	<!-- /.login-logo -->
-	<div class="login-box-body">
-		<p class="login-box-msg">Sign in to start your session</p>
-		<div class="form-group has-feedback">
-			<input type="text" class="form-control" placeholder="User Name" ng-model="username" ng-keypress="login($event)" autocomplete="off" id="username">
-			<span class="glyphicon glyphicon-user form-control-feedback"></span>
-		</div>
-		<div class="form-group has-feedback">
-			<input type="password" class="form-control" placeholder="Password" ng-model="password" ng-keypress="login($event)">
-			<span class="glyphicon glyphicon-lock form-control-feedback"></span>
-		</div>
-		<div class="row">
-			<div class="col-xs-8">
-				<!--div class="checkbox">
-					<label> <input type="checkbox" /> Remember Me
-					</label>
-				</div-->
-			</div>
-			<!-- /.col -->
-			<div class="col-xs-4">
-				<button class="btn btn-primary btn-block btn-flat" ng-click="login($event, true)" ng-disabled="lock">Sign In</button>
-			</div>
-			<!-- /.col -->
-		</div>
-	</div>
-	<!-- /.login-box-body -->
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/nvd3.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/nvd3.html b/eagle-webservice/src/main/webapp/app/partials/dam/nvd3.html
deleted file mode 100644
index 1a0196d..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/nvd3.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<style>
-	.nvd3-chart-cntr > h3 {
-		text-align: center;
-	}
-
-	.nvd3-chart-cntr > svg {
-		height: 300px;
-	}
-</style>
-
-<button class="btn btn-primary" ng-click="randomChartData()">Random</button>
-<button class="btn btn-primary" ng-click="chart = 'column'">To Column</button>
-<button class="btn btn-primary" ng-click="chart = 'line'">To Line</button>
-<button class="btn btn-primary" ng-click="chart = 'area'">To Area</button>
-<button class="btn btn-primary" ng-click="chart = 'pie'">To Pie</button>
-<button class="btn btn-warning" ng-click="chartConfig.xType = 'number'">Number</button>
-<button class="btn btn-warning" ng-click="chartConfig.xType = 'time'">Time</button>
-
-<div nvd3="data" data-title="Test" data-chart="{{chart}}" data-watching="true" data-config="chartConfig" class="nvd3-chart-cntr"></div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/policyDetail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/policyDetail.html b/eagle-webservice/src/main/webapp/app/partials/dam/policyDetail.html
deleted file mode 100644
index 9540ac6..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/policyDetail.html
+++ /dev/null
@@ -1,171 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="box box-info">
-	<div class="box-header with-border">
-		<h3 class="box-title">
-			{{policy.tags.policyId}}
-			<small>{{policy.tags.site}}</small>
-		</h3>
-	</div><!-- /.box-header -->
-
-	<div class="box-body">
-		<div class="row">
-			<div class="col-xs-8">
-				<div class="inline-group">
-					<dl><dt>Data Source</dt><dd>{{policy.tags.dataSource}}</dd></dl>
-					<dl><dt>Status</dt><dd>
-						<span ng-show="policy.enabled" class="text-muted"><i class="fa fa-square text-green"></i> Enabled</span>
-						<span ng-show="!policy.enabled" class="text-muted"><i class="fa fa-square text-muted"></i> Disabled</span>
-					</dd></dl>
-				</div>
-				<div class="inline-group">
-					<dl><dt>Description</dt><dd>{{policy.desc}}</dd></dl>
-				</div>
-				<div class="inline-group">
-					<dl><dt>Alert</dt><dd>
-						<a href="mailto:{{mail}}" ng-repeat="mail in policy.__mailList track by $index" style="margin-right: 10px;">{{mail}}</a>
-					</dd></dl>
-				</div>
-			</div>
-			<div class="col-xs-4 text-right" ng-show="auth.isRole('ROLE_ADMIN')">
-				<a class="btn btn-primary" href="#/dam/policyEdit/{{policy.encodedRowkey}}">Edit</a>
-				<button class="btn btn-warning" ng-show="!policy.enabled" ng-click="updatePolicyStatus(policy, true)">Enable</button>
-				<button class="btn btn-warning" ng-show="policy.enabled" ng-click="updatePolicyStatus(policy, false)">Disable</button>
-				<button class="btn btn-danger" ng-click="deletePolicy(policy)">Delete</button>
-			</div>
-		</div>
-	</div><!-- /.box-body -->
-
-	<div class="overlay" ng-hide="policyList._promise.$$state.status === 1;">
-		<i class="fa fa-refresh fa-spin"></i>
-	</div>
-
-	<div class="box-footer clearfix">
-		<a data-toggle="collapse" href="[data-id='query']">
-			View Query
-		</a>
-		<div data-id="query" class="collapse in">
-			<pre>{{policy.__expression}}</pre>
-		</div>
-	</div>
-</div>
-
-<div class="nav-tabs-custom">
-	<ul class="nav nav-tabs">
-		<li class="active">
-			<a href="[data-id='visualization']" data-toggle="tab">Visualization</a>
-		</li>
-		<li>
-			<a href="[data-id='statistics']" data-toggle="tab">Statistics</a>
-		</li>
-		<li>
-			<a href="[data-id='alerts']" data-toggle="tab">Alerts</a>
-		</li>
-	</ul>
-	<div class="tab-content">
-		<div class="tab-pane active" data-id="visualization">
-			<div class="row">
-				<div class="col-xs-6">
-					<div nvd3="policyEvalSeries" data-title="Policy Eval Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
-				</div>
-				<div class="col-xs-6">
-					<div nvd3="policyEvalFailSeries" data-title="Policy Eval Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
-				</div>
-				<div class="col-xs-6">
-					<div nvd3="alertSeries" data-title="Alert Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
-				</div>
-				<div class="col-xs-6">
-					<div nvd3="alertFailSeries" data-title="Alert Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
-				</div>
-			</div>
-		</div><!-- /.tab-pane -->
-
-		<div class="tab-pane" data-id="statistics">
-			<div class="row">
-				<div class="col-xs-3">
-					<div class="info-box bg-aqua">
-						<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
-						<div class="info-box-content">
-							<span class="info-box-text">Policy Eval Count</span>
-							<span class="info-box-number">{{common.array.sum(policyEvalSeries, "1")}} <small>(Monthly)</small></span>
-							<span class="info-box-number">{{policyEvalSeries[policyEvalSeries.length - 1][1]}} <small>(Daily)</small></span>
-						</div><!-- /.info-box-content -->
-					</div>
-				</div>
-				<div class="col-xs-3">
-					<div class="info-box bg-red">
-						<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
-						<div class="info-box-content">
-							<span class="info-box-text">Policy Eval Fail Count</span>
-							<span class="info-box-number">{{common.array.sum(policyEvalFailSeries, "1")}} <small>(Monthly)</small></span>
-							<span class="info-box-number">{{policyEvalFailSeries[policyEvalFailSeries.length - 1][1]}} <small>(Daily)</small></span>
-						</div><!-- /.info-box-content -->
-					</div>
-				</div>
-				<div class="col-xs-3">
-					<div class="info-box bg-aqua">
-						<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
-						<div class="info-box-content">
-							<span class="info-box-text">Alert Count</span>
-							<span class="info-box-number">{{common.array.sum(alertSeries, "1")}} <small>(Monthly)</small></span>
-							<span class="info-box-number">{{alertSeries[alertSeries.length - 1][1]}} <small>(Daily)</small></span>
-						</div><!-- /.info-box-content -->
-					</div>
-				</div>
-				<div class="col-xs-3">
-					<div class="info-box bg-red">
-						<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
-						<div class="info-box-content">
-							<span class="info-box-text">Alert Fail Count</span>
-							<span class="info-box-number">{{common.array.sum(alertFailSeries, "1")}} <small>(Monthly)</small></span>
-							<span class="info-box-number">{{alertFailSeries[alertFailSeries.length - 1][1]}} <small>(Daily)</small></span>
-						</div><!-- /.info-box-content -->
-					</div>
-				</div>
-			</div>
-		</div><!-- /.tab-pane -->
-
-		<div class="tab-pane" data-id="alerts">
-			<div sorttable source="alertList" sort="-timestamp">
-				<table class="table table-bordered" ng-non-bindable>
-					<thead>
-						<tr>
-							<th width="170" sortpath="timestamp">Alert Time</th>
-							<th width="170" sortpath="alertContext.properties.timestamp">Message Time</th>
-							<th width="60" sortpath="alertContext.properties.user">User</th>
-							<th width="150" sortpath="alertContext.properties.host">Host</th>
-							<th sortpath="alertContext.properties.emailMessage">Description</th>
-							<th width="50"> </th>
-						</tr>
-					</thead>
-					<tbody>
-						<tr ng-class="{info : item.__new}">
-							<td>{{common.format.date(item.timestamp)}}</td>
-							<td>{{common.format.date(item.alertContext.properties.timestamp)}}</td>
-							<td>{{item.alertContext.properties.user}}</td>
-							<td>{{item.alertContext.properties.host}}</td>
-							<td>{{item.alertContext.properties.alertMessage}}</td>
-							<td><a href="#/dam/alertDetail/{{item.encodedRowkey}}">Detail</a></td>
-						</tr>
-					</tbody>
-				</table>
-			</div>
-		</div><!-- /.tab-pane -->
-
-	</div><!-- /.tab-content -->
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/policyEdit.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/policyEdit.html b/eagle-webservice/src/main/webapp/app/partials/dam/policyEdit.html
deleted file mode 100644
index 038a590..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/policyEdit.html
+++ /dev/null
@@ -1,359 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="progress active" ng-show="!streamReady">
-	<div class="progress-bar progress-bar-primary progress-bar-striped" style="width: 100%">
-	</div>
-</div>
-
-<!-- Step navigation -->
-<div ng-show="streamReady">
-	<div class="row step-cntr">
-		<div class="col-md-3" ng-repeat="step in steps">
-			<div class="step" ng-class="stepSelect($index + 1)">
-				<h1>{{$index + 1}}</h1>
-				<h2>Step {{$index + 1}}</h2>
-				<p title="{{step.title}}">{{step.title}}</p>
-			</div>
-		</div>
-	</div>
-
-	<!-- Step container -->
-	<div class="box box-info">
-		<div class="box-header with-border">
-			<h3 class="box-title">Step {{step}} - {{steps[step - 1].title}}</h3>
-		</div><!-- /.box-header -->
-
-		<div class="box-body">
-			<!-- ---------------------- Step Body Start ---------------------- -->
-
-			<!-- Step 1: Data Source -->
-			<div ng-show="step === 1">
-				<div class="form-group">
-					<label>Select Data Source</label>
-					<select class="form-control" ng-model="policy.tags.dataSource">
-						<option ng-repeat="dataSource in site.current().dataSrcList" ng-hide="dataSource.hide" value="{{dataSource.tags.dataSource}}">{{dataSource.desc || dataSource.tags.dataSource}}</option>
-					</select>
-				</div>
-			</div>
-
-			<!-- Step 2: Stream -->
-			<div ng-show="step === 2">
-				<div class="pull-right" ng-show="policy.__.advanced === undefined">
-					<span class="text-muted">or</span>
-					<a ng-click="policy.__.advanced = true;">Advanced</a>
-				</div>
-
-				<div class="form-group">
-					<label>Select Stream</label>
-					<select class="form-control" ng-model="policy.__.streamName" ng-show="!policy.__.advanced">
-						<option ng-repeat="stream in dataSources[policy.tags.dataSource]">{{stream.tags.streamName}}</option>
-					</select>
-					<select class="form-control" ng-show="policy.__.advanced" disabled="disabled">
-						<option>[Advanced Mode]</option>
-					</select>
-				</div>
-
-				<div class="checkbox" ng-show="policy.__.advanced !== undefined">
-					<label>
-						<input type="checkbox" ng-model="policy.__.advanced">
-						Advanced Mode
-					</label>
-				</div>
-			</div>
-
-			<!-- Step 3: Define Alert Policy -->
-			<div ng-show="step === 3 && !policy.__.advanced">
-				<!-- Criteria -->
-				<div>
-					<label>Match Criteria</label>
-					<a ng-click="collapse('.panel-group')">expand / collapse all</a>
-
-					<div class="panel-group panel-group-sm" role="tablist">
-						<div class="panel panel-default"
-							ng-repeat="meta in _stream.metas"
-							ng-init="op = '=='; val = null; type = (meta.attrType || '').toLowerCase();">
-							<div class="panel-heading" role="tab">
-								<h4 class="panel-title">
-									<span class="bg-navy disabled color-palette pull-right">
-										{{parseConditionDesc(meta.tags.attrName)}}
-									</span>
-
-									<a role="button" data-toggle="collapse" href="[data-name='{{meta.tags.attrName}}']" class="collapsed">
-										<span class="fa fa-square" ng-class="hasCondition(meta.tags.attrName, type) ? 'text-green' : 'text-muted'"> </span>
-										{{meta.attrDisplayName || meta.tags.attrName}}
-										<span class="fa fa-question-circle" ng-show="meta.attrDescription"
-										uib-tooltip="{{meta.attrDescription}}" tooltip-placement="right" tooltip-animation="false"> </span>
-									</a>
-								</h4>
-							</div>
-							<div data-name="{{meta.tags.attrName}}" data-type="{{meta.attrType}}" role="tabpanel" class="collapse">
-								<div class="panel-body">
-									<ul ng-show="type !== 'bool'">
-										<li ng-repeat="cond in policy.__.conditions[meta.tags.attrName]">
-											[<a ng-click="policy.__.conditions[meta.tags.attrName].splice($index, 1)">X</a>]
-											{{cond.toString()}}
-										</li>
-									</ul>
-
-									<!-- String -->
-									<div ng-if="type == 'string'">
-										<div class="input-group" style="max-width: 450px;">
-											<div class="input-group-btn">
-												<select class="form-control" ng-model="op">
-													<option ng-repeat="mark in ['==','!=','contains','regex']">{{mark}}</option>
-												</select>
-											</div>
-
-											<!-- With resolver -->
-											<input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="meta.attrValueResolver"
-												ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)"
-												uib-typeahead="item for item in resolverTypeahead($viewValue, meta.attrValueResolver)">
-											<!-- Without resolver -->
-											<input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="!meta.attrValueResolver"
-												ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)">
-
-											<span class="input-group-btn">
-												<button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type);val=null;">Add</button>
-											</span>
-										</div>
-									</div>
-
-									<!-- Number -->
-									<div ng-if="type == 'long' || type == 'integer' || type == 'number' || type == 'double' || type == 'float'">
-										<div class="input-group" style="max-width: 450px;">
-											<div class="input-group-btn">
-												<select class="form-control" ng-model="op">
-													<option ng-repeat="mark in ['==','!=','>','>=','<','<=']">{{mark}}</option>
-												</select>
-											</div>
-
-											<input type="number" class="form-control" autocomplete="off" placeholder="Number Only..." ng-model="val" ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)">
-											<span class="input-group-btn">
-												<button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type) ? val=null : void(0);">Add</button>
-											</span>
-										</div>
-									</div>
-
-									<!-- Boolean -->
-									<div ng-if="type == 'bool'" ng-init="policy.__.conditions[meta.tags.attrName] = policy.__.conditions[meta.tags.attrName] || [_CondUnit(meta.tags.attrName, '==', 'none', 'bool')]">
-										<select class="form-control" ng-model="policy.__.conditions[meta.tags.attrName][0].val" style="max-width: 100px;">
-											<option ng-repeat="bool in ['none','true','false']">{{bool}}</option>
-										</select>
-									</div>
-								</div>
-							</div>
-						</div>
-					</div>
-				</div>
-
-				<!-- Window -->
-				<div class="checkbox">
-					<label>
-						<input type="checkbox" ng-checked="policy.__.windowConfig" ng-click="policy.__.windowConfig = !policy.__.windowConfig"> Slide Window
-					</label>
-				</div>
-				<div ng-show="policy.__.windowConfig">
-					<div class="row">
-						<div class="col-md-4">
-							<div class="form-group">
-								<label for="window">Window</label>
-								<select class="form-control" ng-model="policy.__.window"
-								uib-tooltip="{{getWindow().description}}" tooltip-animation="false">
-									<option ng-repeat="item in config.window" value="{{item.type}}">{{item.title}}</option>
-								</select>
-							</div>
-						</div>
-
-						<!-- fields -->
-						<div class="col-md-4" ng-repeat="field in getWindow().fields" ng-init="field.val = field.val || (field.defaultValue || '');" ng-hide="field.hide">
-							<div class="form-group" ng-class="{'has-warning' : !field.val || !field.val.match(field.regex)}">
-								<label for="window">Window - {{field.title}}</label>
-								<input type="text" class="form-control" autocomplete="off" placeholder="{{field.description}}" ng-model="field.val" title="{{field.description}}">
-							</div>
-						</div>
-					</div>
-
-					<!-- Aggregation -->
-					<div class="row">
-						<div class="col-md-4">
-							<div class="form-group" ng-class="{'text-yellow' : (policy.__.groupAgg && !policy.__.groupAggPath)}">
-								<label for="window">Aggregation</label>
-								<div class="input-group">
-									<div class="input-group-btn">
-										<select class="form-control" ng-model="policy.__.groupAgg" ng-change="updateGroupAgg()">
-											<option ng-repeat="op in ['max','min','avg','count', 'sum']">{{op}}</option>
-										</select>
-									</div>
-									<select class="form-control" ng-model="policy.__.groupAggPath" ng-class="{'has-warning' : !policy.__.groupAggPath}" id="groupAggPath"
-											ng-show="policy.__.groupAgg" ng-disabled="policy.__.groupAgg === 'count'">
-										<option ng-repeat="meta in groupAggPathList()">{{meta.tags.attrName}}</option>
-									</select>
-								</div>
-							</div>
-						</div>
-
-						<div class="col-md-4">
-							<div class="form-group" ng-class="{'text-yellow' : (!policy.__.groupCondOp || !policy.__.groupCondVal)}">
-								<label for="window">Condition</label>
-								<div class="input-group">
-									<div class="input-group-btn">
-										<select class="form-control" ng-model="policy.__.groupCondOp" ng-class="{'has-warning' : !policy.__.groupCondOp}">
-											<option ng-repeat="op in ['>','<','>=','<=','==']">{{op}}</option>
-										</select>
-									</div>
-									<input type="text" class="form-control" ng-model="policy.__.groupCondVal" ng-class="{'has-warning' : !policy.__.groupCondVal}" />
-								</div>
-							</div>
-						</div>
-
-						<div class="col-md-4">
-							<div class="form-group">
-								<label for="window">Alias (Optional)</label>
-								<input type="text" class="form-control" ng-model="policy.__.groupAggAlias" placeholder="Default: aggValue" />
-							</div>
-						</div>
-					</div>
-
-					<!-- Group -->
-					<div class="row">
-						<div class="col-md-4">
-							<div class="form-group">
-								<label for="window">Group By</label>
-								<select class="form-control" ng-model="policy.__.group">
-									<option value="">None</option>
-									<option ng-repeat="meta in _stream.metas">{{meta.tags.attrName}}</option>
-								</select>
-							</div>
-						</div>
-					</div>
-				</div>
-			</div>
-
-			<!-- Step 3: Define Alert Policy -->
-			<div ng-show="step === 3 && policy.__.advanced">
-				<div class="form-group">
-					<label>Query Expression</label>
-					<textarea class="form-control" ng-model="policy.__._expression"
-					placeholder="Query expression. e.g. from hdfsAuditLogEventStream[(cmd=='open') and (host=='localhost' or host=='127.0.0.1')]#window.time(2 sec) select * insert into outputStream;" rows="5"></textarea>
-				</div>
-			</div>
-
-			<!-- Step 4: Email Notification -->
-			<div ng-show="step === 4">
-				<div class="row">
-					<div class="col-xs-4">
-						<div class="form-group" ng-class="{'has-warning' : !policy.tags.policyId}">
-							<label>Policy Name</label>
-							<input type="text" class="form-control" placeholder="" ng-model="policy.tags.policyId" ng-disabled="!create">
-						</div>
-					</div>
-					<div class="col-xs-3">
-						<div class="form-group">
-							<label>
-								Alert De-Dup Interval(min)
-								<span class="fa fa-question-circle" uib-tooltip="Same type alert will be De-dup in configured interval"> </span>
-							</label>
-							<input type="number" class="form-control" ng-model="policy.__.dedupe.alertDedupIntervalMin" placeholder="[Minute] Number only. Suggestion: 720">
-						</div>
-					</div>
-					<div class="col-xs-3">
-						<div class="form-group">
-							<label>
-								Email De-Dup Interval(min)
-								<span class="fa fa-question-circle" uib-tooltip="The minimun time interval of email"> </span>
-							</label>
-							<input type="number" class="form-control" ng-model="policy.__.dedupe.emailDedupIntervalMin" placeholder="[Minute] Number only. Suggestion: 1440">
-						</div>
-					</div>
-					<div class="col-xs-2">
-						<div class="form-group">
-							<label>
-								Enabled
-							</label>
-							<p>
-								<input type="checkbox" checked="checked" ng-model="policy.enabled">
-							</p>
-						</div>
-					</div>
-				</div>
-
-				<div>
-					<a data-toggle="collapse" href="[data-id='advancedDeDup']">Advanced De-Dup</a>
-					<div data-id='advancedDeDup' class="collapse">
-						<label>
-							De-Dup Key
-							<span class="fa fa-question-circle" uib-tooltip="Type of grouping alerts. If you don't know how to config, leave it default."> </span>
-						</label>
-						<div class="form-group">
-							<div class="checkbox" ng-repeat="meta in _stream.metas" ng-init="create ? policy.__._dedupTags[meta.tags.attrName] = !!meta.usedAsTag : void(0);">
-								<label>
-									<input type="checkbox" ng-model="policy.__._dedupTags[meta.tags.attrName]">
-									{{meta.tags.attrName}}
-								</label>
-							</div>
-						</div>
-					</div>
-				</div>
-
-				<hr/>
-
-				<div class="form-group">
-					<label>Sender</label>
-					<input type="text" class="form-control" value="noreply-hadoop-eagle@company1.com" placeholder="Enter Sender. e.g. sender@eaxmple.com" ng-model="policy.__.notification[0].sender">
-				</div>
-				<div class="form-group">
-					<label>Recipients</label>
-					<input type="text" class="form-control" placeholder="Enter Recipients. Split with ','. e.g. usera@example.com, userb@example.com" ng-model="policy.__.notification[0].recipients">
-				</div>
-				<div class="form-group">
-					<label>Subject</label>
-					<input type="text" class="form-control" placeholder="Enter Subject" ng-model="policy.__.notification[0].subject">
-				</div>
-				<div class="form-group">
-					<label>Description</label>
-					<textarea class="form-control" placeholder="Policy description" ng-model="policy.desc"></textarea>
-				</div>
-
-				<a data-toggle="collapse" href="[data-id='policyQuery']">
-					View Query
-				</a>
-				<div class="collapse in" data-id="policyQuery">
-					<pre>{{toQuery()}}</pre>
-				</div>
-			</div>
-
-			<!-- ----------------------- Step Body End ----------------------- -->
-		</div><!-- /.box-body -->
-
-		<div class="overlay" ng-hide="stepReady(step)">
-			<span class="fa fa-refresh fa-spin"> </span>
-		</div>
-
-		<div class="box-footer text-right">
-			<button class="btn btn-info" ng-show="step > 1" ng-click="changeStep(step, step - 1, false)" ng-disabled="lock">
-				Prev <span class="fa fa-arrow-circle-o-left"> </span>
-			</button>
-			<button class="btn btn-info" ng-show="step < steps.length" ng-click="changeStep(step, step + 1)" ng-disabled="checkNextable(step) || lock">
-				Next <span class="fa fa-arrow-circle-o-right"> </span>
-			</button>
-			<button class="btn btn-info" ng-show="step === steps.length" ng-click="finishPolicy()" ng-disabled="checkNextable(step) || lock">
-				Done <span class="fa fa-check-circle-o"> </span>
-			</button>
-		</div>
-	</div>
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/policyList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/policyList.html b/eagle-webservice/src/main/webapp/app/partials/dam/policyList.html
deleted file mode 100644
index 376a513..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/policyList.html
+++ /dev/null
@@ -1,98 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-list-alt"> </i>
-		<h3 class="box-title">
-			{{dataSource.desc || dataSource.tags.dataSource || "All Policies"}}
-			<div class="btn-group">
-				<button class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
-					<i class="fa fa-caret-down"></i>
-				</button>
-				<ul id="programList" class="dropdown-menu" role="menu">
-					<li ng-repeat="dataSrc in site.current().dataSrcList" ng-hide="dataSrc.hide">
-						<a href="#/dam/policyList/{{dataSrc.tags.dataSource}}">{{dataSrc.desc || dataSrc.tags.dataSource}}</a>
-					</li>
-					<li class="divider"></li>
-					<li>
-						<a href="#/dam/policyList">All Policy List</a>
-					</li>
-				</ul>
-			</div>
-		</h3>
-	</div>
-	<div class="box-body">
-		<div class="row">
-			<div class="col-xs-3">
-				<div class="search-box">
-					<input type="search" class="form-control input-sm" placeholder="Search" ng-model="search" />
-					<span class="fa fa-search"> </span>
-				</div>
-			</div>
-			<div class="col-xs-6">
-				<div class="inline-group form-inline text-muted">
-					<dl><dt><i class="fa fa-square text-green"> </i></dt><dd>Enabled</dd></dl>
-					<dl><dt><i class="fa fa-square text-muted"> </i></dt><dd>Disabled</dd></dl>
-				</div>
-			</div>
-			<div class="col-xs-3 text-right">
-				<a class="btn btn-primary" href="#/dam/policyCreate/{{!dataSource ? '' : '?dataSrc=' + dataSource.tags.dataSource}}" ng-show="auth.isRole('ROLE_ADMIN')">
-					New Policy
-					<i class="fa fa-plus-circle"> </i>
-				</a>
-			</div>
-		</div>
-
-		<p ng-show="policyList._promise.$$state.status !== 1">
-			<span class="fa fa-refresh fa-spin"> </span>
-			Loading...
-		</p>
-
-		<div sorttable source="policyList" ng-show="policyList._promise.$$state.status === 1" search="false" searchfunc="searchFunc">
-			<table class="table table-bordered" ng-non-bindable>
-				<thead>
-					<tr>
-						<th width="30" sortpath="enabled"> </th>
-						<th width="200" sortpath="tags.policyId">Policy Name</th>
-						<th sortpath="desc">Description</th>
-						<th width="150" sortpath="owner">Owner</th>
-						<th width="170" sortpath="lastModifiedDate">Last Modified</th>
-						<th width="95" ng-show="_parent.auth.isRole('ROLE_ADMIN')">Action</th>
-					</tr>
-				</thead>
-				<tbody>
-					<tr>
-						<td><span class='fa fa-square' ng-class="item.enabled ? 'text-green' : 'text-muted'"> </span></td>
-						<td><a href="#/dam/policyDetail/{{item.encodedRowkey}}">{{item.tags.policyId}}</a></td>
-						<td>{{item.desc}}</td>
-						<td>{{item.owner}}</td>
-						<td>{{common.format.date(item.lastModifiedDate) || "-"}}</td>
-						<td ng-show="_parent.auth.isRole('ROLE_ADMIN')">
-							<a class="fa fa-pencil btn btn-default btn-xs" uib-tooltip="Edit" tooltip-animation="false" href="#/dam/policyEdit/{{item.encodedRowkey}}"> </a>
-							<button class="fa fa-play sm btn btn-default btn-xs" uib-tooltip="Enable" tooltip-animation="false" ng-show="!item.enabled" ng-click="_parent.updatePolicyStatus(item, true)"> </button>
-							<button class="fa fa-pause sm btn btn-default btn-xs" uib-tooltip="Disable" tooltip-animation="false" ng-show="item.enabled" ng-click="_parent.updatePolicyStatus(item, false)"> </button>
-							<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false" ng-click="_parent.deletePolicy(item)"> </button>
-						</td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-	</div>
-	<!--div class="box-footer clearfix">
-	</div-->
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity.html b/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity.html
deleted file mode 100644
index b00bbaf..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="nav-tabs-custom">
-	<!-- Tabs within a box -->
-	<ul class="nav nav-tabs pull-right ui-sortable-handle">
-		<li ng-repeat="entity in config.list" ng-class="{active: dataSrc.name === entity.name}">
-			<a ng-click="site.url('/dam/sensitivity/' + entity.name)">{{entity.name}}</a>
-		</li>
-		<li class="pull-left header"><i class="fa fa-folder-open"></i> Sensitivity Broswer</li>
-	</ul>
-	<div class="tab-content">
-		<!-- Morris chart - Sales -->
-		<div class="tab-pane active">
-			<ng-include src="'partials/dam/sensitivity/' + dataSrc.type + '.html?_=' + ajaxId"></ng-include>
-		</div>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity/folder.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity/folder.html b/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity/folder.html
deleted file mode 100644
index 1827eac..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity/folder.html
+++ /dev/null
@@ -1,110 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div ng-controller="sensitivityViewFolderCtrl">
-	<ul class="list-inline path">
-		<li>Path:</li>
-		<li ng-repeat="unit in pathUnitList">
-			<a ng-click="updateItems(unit.path)" class="label bg-black">{{unit.name}}</a>
-		</li>
-	</ul>
-
-	<table class="table table-bordered">
-		<thead>
-			<tr>
-				<th width="15%">File Name</th>
-				<th width="10%">Owner</th>
-				<th width="10%">Group</th>
-				<th>Sensitivity Type</th>
-				<th width="10" ng-show="auth.isRole('ROLE_ADMIN')"> </th>
-			</tr>
-		</thead>
-		<tbody>
-			<tr ng-show="items._promise.$$state.status !== 1">
-				<td colspan="5">
-					<span class="fa fa-refresh fa-spin"> </span>
-					Loading...
-				</td>
-			</tr>
-			<tr ng-show="items._promise.$$state.status === 1 && !items.length">
-				<td colspan="5">
-					<span class="fa fa-exclamation-triangle"> </span>
-					Empty Folder
-				</td>
-			</tr>
-			<tr ng-repeat="item in items" ng-class="{warning : item.sensitiveType}">
-				<td>
-					<span ng-show="!item.isdir">
-						<span class="fa fa-file"> </span>
-						{{getFileName(item)}}
-					</span>
-					<a ng-show="item.isdir" ng-click="updateItems(item.resource)">
-						<span class="fa fa-folder"> </span>
-						{{getFileName(item)}}
-					</a>
-
-					<span class="pull-right" ng-show="item.childSensitiveTypes.length">
-						<span class="fa fa-dot-circle-o text-muted" uib-tooltip="Contain child sensitivity defination"> </span>
-					</span>
-				</td>
-				<td>{{item.owner}}</td>
-				<td>{{item.group}}</td>
-				<td>{{item.sensitiveType}}</td>
-				<td ng-show="auth.isRole('ROLE_ADMIN')">
-					<button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(item)" ng-show="!item.sensitiveType"
-					uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
-					<button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(item)" ng-show="item.sensitiveType"
-					uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
-				</td>
-			</tr>
-		</tbody>
-	</table>
-
-
-	<!-- Modal: Create / Edit site -->
-	<div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
-		<div class="modal-dialog" role="document">
-			<div class="modal-content">
-				<div class="modal-header">
-					<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-						<span aria-hidden="true">&times;</span>
-					</button>
-					<h4 class="modal-title">Mark Sensitivity Data</h4>
-				</div>
-				<div class="modal-body">
-					<div class="form-group">
-						<label>Resource</label>
-						<input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags.filedir" />
-					</div>
-					<div class="form-group">
-						<label>* Sensitivity Type</label>
-						<input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
-					</div>
-				</div>
-				<div class="modal-footer">
-					<button type="button" class="btn btn-default" data-dismiss="modal">
-						Close
-					</button>
-					<button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
-						Update
-					</button>
-				</div>
-			</div>
-		</div>
-	</div>
-
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity/table.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity/table.html b/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity/table.html
deleted file mode 100644
index baa824c..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/sensitivity/table.html
+++ /dev/null
@@ -1,150 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div ng-controller="sensitivityViewTableCtrl">
-	<p ng-show="databases._promise.$$state.status !== 1">
-		<span class="fa fa-refresh fa-spin"> </span>
-		Loading...
-	</p>
-
-	<div ng-show="databases._promise.$$state.status === 1" class="row">
-		<div class="col-md-4">
-			<label>
-				Databases
-				({{databases.length}})
-			</label>
-			<ul class="tree tree-bordered" style="max-height: 500px; overflow-y: auto;">
-				<li ng-repeat="db in databases">
-					<span class="tree-item box-clickable text-primary" ng-click="db.show = !db.show; loadTables(db);">
-						<span ng-class="{'text-warning' : db.sensitiveType}">
-							<span class="fa fa-database"> </span>
-							{{db.database}}
-							<span ng-show="db.tables._promise.$$state.status === 1">({{db.tables.length}})</span>
-
-							<span ng-show="auth.isRole('ROLE_ADMIN')">
-								<a class="fa fa-eye text-muted hover" ng-click="markSensitivity(db, $event)" ng-show="!db.sensitiveType"
-								uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a>
-								<a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(db, $event)" ng-show="db.sensitiveType"
-								uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a>
-							</span>
-
-							<span class="pull-right" ng-show="db.childSensitiveTypes.length">
-								<span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span>
-							</span>
-							<span ng-show="db.sensitiveType" class="pull-right">[{{db.sensitiveType}}]</span>
-						</span>
-					</span>
-					<ul ng-show="db.show">
-						<li ng-show="db.tables._promise.$$state.status !== 1">
-							<span>
-								<span class="fa fa-refresh fa-spin"> </span>
-								Loading...
-							</span>
-						</li>
-						<li ng-repeat="tb in db.tables" ng-class="{active : tb === table}">
-							<span class="tree-item box-clickable text-primary" ng-click="loadColumns(db, tb)">
-								<span ng-class="{'text-warning' : tb.sensitiveType}">
-									<span class="fa fa-table"> </span>
-									{{tb.table}}
-
-									<span ng-show="auth.isRole('ROLE_ADMIN')">
-										<a class="fa fa-eye text-muted hover" ng-click="markSensitivity(tb, $event)" ng-show="!tb.sensitiveType"
-										uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a>
-										<a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(tb, $event)" ng-show="tb.sensitiveType"
-										uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a>
-									</span>
-
-									<span class="pull-right" ng-show="tb.childSensitiveTypes.length">
-										<span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span>
-									</span>
-									<span ng-show="tb.sensitiveType" class="pull-right">[{{tb.sensitiveType}}]</span>
-								</span>
-							</span>
-						</li>
-					</ul>
-				</li>
-			</ul>
-		</div>
-		<div class="col-md-8">
-			<label ng-show="table">Route: {{table.database}} > {{table.table}}</label>
-			<p ng-show="table && table.columns._promise.$$state.status !== 1">
-				<span class="fa fa-refresh fa-spin"> </span>
-				Loading...
-			</p>
-			<div ng-show="table && table.columns._promise.$$state.status === 1">
-				<table class="table table-bordered">
-					<thead>
-						<tr>
-							<th width="40%">Column Name</th>
-							<th>Sensitivity Type</th>
-							<th width="10" ng-show="auth.isRole('ROLE_ADMIN')"> </th>
-						</tr>
-					</thead>
-					<tbody>
-						<tr ng-repeat="col in table.columns" ng-class="{warning : col.sensitiveType}">
-							<td>{{col.column}}</td>
-							<td>{{col.sensitiveType}}</td>
-							<td ng-show="auth.isRole('ROLE_ADMIN')">
-								<button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(col)" ng-show="!col.sensitiveType"
-								uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
-								<button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(col)" ng-show="col.sensitiveType"
-								uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
-							</td>
-						</tr>
-					</tbody>
-				</table>
-			</div>
-		</div>
-	</div>
-
-
-
-
-
-
-	<!-- Modal: Create / Edit site -->
-	<div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
-		<div class="modal-dialog" role="document">
-			<div class="modal-content">
-				<div class="modal-header">
-					<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-						<span aria-hidden="true">&times;</span>
-					</button>
-					<h4 class="modal-title">Mark Sensitivity Data</h4>
-				</div>
-				<div class="modal-body">
-					<div class="form-group">
-						<label>Resource</label>
-						<input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags[dataSrc.keys[0]]" />
-					</div>
-					<div class="form-group">
-						<label>* Sensitivity Type</label>
-						<input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
-					</div>
-				</div>
-				<div class="modal-footer">
-					<button type="button" class="btn btn-default" data-dismiss="modal">
-						Close
-					</button>
-					<button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
-						Update
-					</button>
-				</div>
-			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/sensitivitySummary.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/sensitivitySummary.html b/eagle-webservice/src/main/webapp/app/partials/dam/sensitivitySummary.html
deleted file mode 100644
index 3dfe103..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/sensitivitySummary.html
+++ /dev/null
@@ -1,143 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="row">
-	<div class="col-md-4" ng-repeat="entity in config.list">
-		<div class="info-box bg-aqua box-clickable">
-			<span class="info-box-icon" ng-click="site.url('/dam/sensitivity/' + entity.name)"><i class="fa fa-suitcase"></i></span>
-			<div class="info-box-content">
-				<div class="pull-right btn-group" ng-show="auth.isRole('ROLE_ADMIN')">
-					<a class="fa fa-cog config" data-toggle="dropdown"></a>
-					<ul class="dropdown-menu" role="menu">
-						<li>
-							<a ng-click="showImportEditor(entity);"><span class="fa fa-cloud-upload"></span>Import</a>
-						</li>
-						<li class="divider"></li>
-						<li>
-							<a ng-click="showManagementEditor(entity);"><span class="fa fa-list"></span>Management</a>
-						</li>
-					</ul>
-				</div>
-
-				<div ng-click="site.url('/dam/sensitivity/' + entity.name)">
-					<span class="info-box-text text-large">{{entity.name}}</span>
-					<!--span class="info-box-number">
-						<small>Connected</small>
-					</span-->
-					<div class="progress">
-						<div class="progress-bar"></div>
-					</div>
-					<span class="progress-description">
-						<span class="fa fa-refresh fa-spin" ng-hide="entity.statisitc._promise.$$state.status === 1"></span>
-						<span ng-show="entity.statisitc._promise.$$state.status === 1">{{entity.statisitc[0].value[0] || 0}}</span>
-						sensitivity items
-					</span>
-				</div>
-			</div><!-- /.info-box-content -->
-		</div>
-	</div>
-</div>
-
-<!-- Modal: Create sensitivity -->
-<div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-disabled="_sensitivityLock">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">Import Sensitivity Data</h4>
-			</div>
-			<div class="modal-body">
-				<div tabs data-inner="true" selected="_sensitivityImportType">
-					<div pane data-title="By Text">
-						<div class="form-group">
-							<label>* Sensitivity Data</label>
-							<textarea class="form-control" placeholder="Json format here..." rows="10" id="sensitivityData" ng-model="_parent._sensitivityData" ng-disabled="_parent._sensitivityLock"></textarea>
-						</div>
-					</div>
-					<div pane data-title="By File">
-						<input type="file" file filepath="_parent._sensitivityFile" id="sensitivityFile" ng-disabled="_parent._sensitivityLock" />
-					</div>
-				</div>
-
-			</div>
-			<div class="modal-footer">
-				<p class="text-danger pull-left">{{_sensitivityError}}</p>
-
-				<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="_sensitivityLock">
-					Close
-				</button>
-				<button type="button" class="btn btn-primary" ng-click="confirmImport()"
-				ng-disabled="!importCheck()">
-					Import
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
-
-<!-- Modal: Manage sensitivity -->
-<div class="modal fade" id="sensitivityListMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-disabled="_sensitivityLock">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">Sensitivity List</h4>
-			</div>
-			<div class="modal-body">
-				<p ng-show="_sensitivitySource.list._promise.$$state.status !== 1">
-					<span class="fa fa-refresh fa-spin"> </span>
-					Loading...
-				</p>
-
-				<div sorttable source="_sensitivitySource.list" ng-show="_sensitivitySource.list._promise.$$state.status === 1">
-					<table class="table table-bordered" ng-non-bindable>
-						<thead>
-							<tr>
-								<!--th width="10"></th-->
-								<th ng-repeat="key in _parent._sensitivitySource.keys">{{key}}</th>
-								<th width="10"> </th>
-							</tr>
-						</thead>
-						<tbody>
-							<tr>
-								<!--td>
-									<input type="checkbox" ng-checked="item.selected" ng-click="item.selected = !item.selected">
-								</td-->
-								<td ng-repeat="key in _parent._sensitivitySource.keys">{{item.tags[key] || item[key]}}</td>
-								<td>
-									<a ng-click="_parent.deleteItem(item)">Delete</a>
-								</td>
-							</tr>
-						</tbody>
-				</table>
-				</div>
-			</div>
-			<div class="modal-footer">
-				<button type="button" class="btn btn-danger pull-left" ng-click="deleteAll(_sensitivitySource)">
-					Delete All
-				</button>
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file



[6/9] incubator-eagle git commit: EAGLE-139 EAGLE-163 Eagle UI Modularization and fix bugs in policy extensions

Posted by hd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/siteList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/siteList.html b/eagle-webservice/src/main/webapp/app/partials/dam/siteList.html
deleted file mode 100644
index 44fad6f..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/siteList.html
+++ /dev/null
@@ -1,162 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="page-fixed">
-	<button class="btn btn-primary"
-		ng-disabled="dataSrcList._promise.$$state.status !== 1"
-		ng-click="showSiteEditor()"
-	>+ New Site</button>
-</div>
-
-<div class="box box-primary" ng-repeat="_site in site.list">
-	<div class="box-header">
-		<h3 class="box-title">{{_site.name}}</h3>
-		<div class="box-tools pull-right">
-			<button class="btn btn-box-tool" data-widget="collapse">
-				<i class="fa fa-minus"></i>
-			</button>
-			<div class="btn-group">
-				<button class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
-					<i class="fa fa-wrench"></i>
-				</button>
-				<ul class="dropdown-menu" role="menu">
-					<li>
-						<a ng-click="showSiteEditor(_site)"><span class="fa fa-cog"></span> Config</a>
-					</li>
-					<li class="divider"></li>
-					<li class="danger">
-						<a ng-click="deleteSite(_site)"><span class="fa fa-trash-o"></span> Delete Site</a>
-					</li>
-				</ul>
-			</div>
-		</div>
-	</div><!-- /.box-header -->
-	<div class="box-body">
-		<div class="row">
-			<div class="col-md-4" ng-repeat="dataSrc in _site.dataSrcList">
-				<div class="info-box" ng-class="dataSrc.enabled !== false ? 'bg-aqua' : 'bg-gray'">
-					<span class="info-box-icon"><i class="fa fa-suitcase"></i></span>
-					<div class="info-box-content">
-						<a class="fa fa-cog config pull-right" ng-click="showDataSourceEditor(dataSrc)"></a>
-						<span class="info-box-text">{{dataSrc.desc || dataSrc.tags.dataSource}}</span>
-
-						<span class="info-box-number" ng-show="dataSrc.hide"><small>-</small></span>
-						<a class="info-box-number" ng-show="!dataSrc.hide" ng-click="site.url(_site, '/dam/policyList/' + dataSrc.tags.dataSource);">
-							<span class="fa fa-refresh fa-spin" ng-hide="policyStatistic._promise.$$state.status === 1"></span>
-							<span ng-show="policyStatistic._promise.$$state.status === 1">{{getPolicyCount(_site.name, dataSrc.tags.dataSource)}}</span>
-							<small>Policies</small>
-						</a>
-						<div class="progress">
-							<div class="progress-bar"></div>
-						</div>
-						<span class="progress-description" ng-show="dataSrc.hide"><small>-</small></span>
-						<a class="progress-description" ng-show="!dataSrc.hide" ng-click="site.url(_site, '/dam/alertList/' + dataSrc.tags.dataSource);">
-							<span class="fa fa-refresh fa-spin" ng-hide="alertStatistic._promise.$$state.status === 1"></span>
-							<span ng-show="alertStatistic._promise.$$state.status === 1">{{getAlertCount(_site.name, dataSrc.tags.dataSource)}}</span>
-							alerts in 30 Days
-						</a>
-					</div><!-- /.info-box-content -->
-				</div>
-			</div>
-		</div>
-	</div>
-</div>
-
-
-
-<!-- Modal: Create / Edit site -->
-<div class="modal fade" id="siteMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">{{!_siteEntity.srcSite ? 'Create' : 'Config'}} Site</h4>
-			</div>
-			<div class="modal-body">
-				<div class="form-group">
-					<label>* Site Name</label>
-					<input type="text" class="form-control" placeholder="Site name..." ng-model="_siteEntity.name" ng-disabled="!!_siteEntity.srcSite" id="siteName">
-				</div>
-
-				<label>
-					* Data Source
-					<small class="text-muted">at least select 1 source</small>
-				</label>
-				<div class="checkbox" ng-repeat="item in _siteEntity.dataSrcList">
-					<label>
-						<input type="checkbox" value="{{item.name}}"
-						ng-checked="item.enabled"
-						ng-click="item.enabled = !item.enabled">
-						{{item.name}}
-					</label>
-				</div>
-			</div>
-			<div class="modal-footer">
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-				<button type="button" class="btn btn-primary" ng-click="confirmUpateSite()"
-				ng-disabled="!checkUpdateSite()">
-					{{!_siteEntity.srcSite ? 'Create' : 'Update'}}
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
-
-<!-- Modal: Edit data source -->
-<div class="modal fade" id="dataSrcMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">{{_dataSrcEntity.tags.dataSource}}</h4>
-			</div>
-			<div class="modal-body">
-				<div class="checkbox">
-					<label>
-						<input type="checkbox"
-						ng-checked="_dataSrcEntity.enabled"
-						ng-click="_dataSrcEntity.enabled = !_dataSrcEntity.enabled">
-						Enabled
-					</label>
-				</div>
-
-				<div class="form-group">
-					<label>Configuration</label>
-					<textarea type="text" class="form-control" placeholder="Data source configuration..." ng-model="_dataSrcEntity.config" id="dataSrcConfig" rows="10"></textarea>
-				</div>
-			</div>
-			<div class="modal-footer">
-				<button type="button" class="btn btn-danger pull-left" ng-click="confirmDeleteDataSource()">
-					Delete Source
-				</button>
-
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-				<button type="button" class="btn btn-primary" ng-click="confirmUpateDataSource()" ng-disabled="_dataSrcEntityLock">
-					Update
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/streamList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/streamList.html b/eagle-webservice/src/main/webapp/app/partials/dam/streamList.html
deleted file mode 100644
index 251bc46..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/streamList.html
+++ /dev/null
@@ -1,179 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="page-fixed">
-	<button class="btn btn-primary"
-		ng-if="auth.isRole('ROLE_ADMIN')"
-		ng-click="showStreamEditor()"
-	>+ New Stream</button>
-</div>
-
-<p ng-show="streamList._promise.$$state.status !== 1">
-	<span class="fa fa-refresh fa-spin"> </span>
-	Loading...
-</p>
-
-<div class="box box-primary" ng-repeat="stream in streams">
-	<div class="box-header with-border">
-		<h3 class="box-title">{{stream.tags.streamName}}</h3>
-		<div class="box-tools pull-right">
-			<button class="btn btn-box-tool" data-widget="collapse">
-				<i class="fa fa-minus"></i>
-			</button>
-			<button class="btn btn-box-tool" ng-click="showStreamEditor(stream)" ng-if="auth.isRole('ROLE_ADMIN')">
-				<i class="fa fa-wrench"></i>
-			</button>
-		</div>
-	</div>
-	<div class="box-body">
-		<div class="inline-group">
-			<dl>
-				<dt>
-					Data Source
-				</dt>
-				<dd>
-					{{stream.tags.dataSource}}
-				</dd>
-			</dl>
-			<dl>
-				<dt>
-					Stream Name
-				</dt>
-				<dd>
-					{{stream.tags.streamName}}
-				</dd>
-			</dl>
-		</div>
-		<div>
-			<dl>
-				<dt>
-					Description
-				</dt>
-				<dd>
-					{{stream.desc}}
-				</dd>
-			</dl>
-		</div>
-
-		<p ng-show="streamSchemaList._promise.$$state.status !== 1">
-			<span class="fa fa-refresh fa-spin"> </span>
-			Loading...
-		</p>
-
-		<div class="list" ng-show="streamSchemaList._promise.$$state.status === 1">
-			<table class="table table-bordered">
-				<thead>
-					<tr>
-						<th width="10%">Attribute Name</th>
-						<th width="12%">Display Name</th>
-						<th width="8%">Type</th>
-						<th>Description</th>
-					</tr>
-				</thead>
-				<tbody>
-					<tr ng-repeat="meta in stream.metaList">
-						<td>{{meta.tags.attrName}}</td>
-						<td>{{meta.attrDisplayName}}</td>
-						<td><span class="label label-warning">{{meta.attrType}}</span></td>
-						<td>{{meta.attrDescription}}</td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-	</div><!-- /.box-body -->
-	<!-- Loading (remove the following to stop the loading)-->
-</div>
-
-
-
-
-
-
-<!-- Modal: Create / Edit stream -->
-<div class="modal fade" id="streamMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog modal-lg" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">{{!_streamEntity.srcStream ? 'Create' : 'Config'}} Stream</h4>
-			</div>
-			<div class="modal-body">
-				<div class="form-group">
-					<label>* Data Source</label>
-					<input type="text" class="form-control" placeholder="Data source..." ng-model="_streamEntity.dataSource"
-							ng-class="{'has-warning': !_streamEntity.dataSource}" ng-disabled="!!_streamEntity.srcStream" id="dataSource">
-				</div>
-
-				<div class="form-group">
-					<label>* Stream Name</label>
-					<input type="text" class="form-control" placeholder="Stream name..." ng-model="_streamEntity.streamName"
-							ng-class="{'has-warning': !_streamEntity.streamName}" ng-disabled="!!_streamEntity.srcStream">
-				</div>
-
-				<div class="form-group">
-					<label>Description</label>
-					<textarea class="form-control" placeholder="Description..." ng-model="_streamEntity.desc"></textarea>
-				</div>
-
-				<label>
-					* Data Source Schema
-				</label>
-				<table class="table table-bordered">
-					<thead>
-						<tr>
-							<th width="26"> </th>
-							<th width="15%">Attribute Name</th>
-							<th width="15%">Display Name</th>
-							<th width="15%">Type</th>
-							<th>Description</th>
-						</tr>
-					</thead>
-					<tbody>
-						<tr ng-repeat="meta in _streamEntity.metaList">
-							<td class="input-field text-center">
-								<a class="fa fa-trash-o text-danger" ng-click="deleteMeta(meta)"></a>
-							</td>
-							<td class="input-field"><input type="text" class="form-control" ng-class="{'has-warning': !meta.attrName}" ng-model="meta.attrName" /></td>
-							<td class="input-field"><input type="text" class="form-control" ng-model="meta.attrDisplayName" /></td>
-							<td class="input-field">
-								<select class="form-control" ng-model="meta.attrType">
-									<option ng-repeat="type in ['string','bool','long','integer','double','float']">{{type}}</option>
-								</select>
-							</td>
-							<td class="input-field"><input type="text" class="form-control" ng-model="meta.attrDescription" /></td>
-						</tr>
-					</tbody>
-				</table>
-				<a ng-click="_streamEntity.metaList.push({attrName: '', attrType: 'string'})">+ Add Schema Item</a>
-			</div>
-			<div class="modal-footer">
-				<button type="button" class="btn btn-danger pull-left" ng-show="_streamEntity.srcStream" ng-click="confirmDeleteStream()" ng-disabled="!checkUpdateStream()">
-					Delete
-				</button>
-
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-				<button type="button" class="btn btn-primary" ng-click="confirmUpateStream()" ng-disabled="!checkUpdateStream()">
-					{{!_streamEntity.srcStream ? 'Create' : 'Update'}}
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/summary.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/summary.html b/eagle-webservice/src/main/webapp/app/partials/dam/summary.html
deleted file mode 100644
index 5a44551..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/summary.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<p ng-show="!dataReady">
-	<span class="fa fa-refresh fa-spin"> </span>
-	Loading...
-</p>
-
-<div class="row">
-	<div class="col-md-4" ng-repeat="dataSrc in site.current().dataSrcList" ng-hide="dataSrc.hide">
-		<div class="small-box bg-aqua box-clickable" ng-click="site.url('/dam/policyList/' + dataSrc.tags.dataSource)">
-			<div class="inner">
-				<h3>
-					{{dataSrc.desc || dataSrc.tags.dataSource}}
-				</h3>
-				<p>
-					{{dataSrc.count || 0}} Policies
-				</p>
-			</div>
-			<div class="icon">
-				<i class="fa fa-arrow-circle-right"></i>
-			</div>
-			<span class="small-box-footer">
-				<i class="fa fa-arrow-circle-right"></i>
-				View Policy List
-			</span>
-		</div>
-	</div>
-</div>
-
-<a class="btn btn-primary" href="#/dam/policyCreate" ng-show="auth.isRole('ROLE_ADMIN')">+ New Policy</a>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/userProfileDetail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/userProfileDetail.html b/eagle-webservice/src/main/webapp/app/partials/dam/userProfileDetail.html
deleted file mode 100644
index 7c6e19a..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/userProfileDetail.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-user"> </i>
-		<h3 class="box-title">
-			{{user}}
-		</h3>
-	</div>
-	<div class="box-body">
-		<div>
-			<div class="inline-group">
-				<dl><dt>User</dt><dd>{{user}}</dd></dl>
-				<dl><dt>Site</dt><dd>{{site.current().name}}</dd></dl>
-			</div>
-			<div class="inline-group">
-				<dl><dt>Other Info</dt><dd class="text-muted">N/A</dd></dl>
-			</div>
-		</div>
-
-		<div class="overlay" ng-hide="profileList._promise.$$state.status === 1;">
-			<span class="fa fa-refresh fa-spin"></span>
-		</div>
-	</div>
-</div>
-
-<!-- Analysis -->
-<div class="nav-tabs-custom">
-	<ul class="nav nav-tabs">
-		<li class="active">
-			<a href="[data-id='DE']" data-toggle="tab" ng-click=" currentTab='DE'">DE</a>
-		</li>
-		<li>
-			<a href="[data-id='EigenDecomposition']" data-toggle="tab" ng-click=" currentTab='EigenDecomposition'">EigenDecomposition</a>
-		</li>
-		<li class="pull-right">
-			<button class="btn btn-primary" ng-click="showRawData(currentTab === 'EigenDecomposition' ? profiles.EigenDecomposition.content : profiles.DE.content)">Raw Data</button>
-		</li>
-	</ul>
-	<div class="tab-content">
-		<div class="tab-pane active" data-id="DE">
-			<div class="row">
-				<div class="col-md-9">
-					<div nvd3="profiles.DE._chart.series" data-config="{chart: 'column', xType: 'text', height: 400}" class="nvd3-chart-cntr" height="400"></div>
-					<div class="inline-group text-center">
-						<dl ng-repeat="(key, value) in profiles.DE.estimates"><dt>{{key}}</dt><dd>{{value}}</dd></dl>
-					</div>
-				</div>
-
-				<div class="col-md-3">
-					<table class="table table-bordered">
-						<thead>
-							<tr>
-								<th>Command</th>
-								<th>Percentage</th>
-							</tr>
-						</thead>
-						<tbody>
-							<tr ng-repeat="unit in profiles.DE.meanList">
-								<td>{{unit.command}}</td>
-								<td class="text-right">{{(unit.percentage*100).toFixed(2)}}%</td>
-							</tr>
-						</tbody>
-					</table>
-				</div>
-			</div>
-		</div><!-- /.tab-pane -->
-		<div class="tab-pane" data-id="EigenDecomposition">
-			<div line3d-chart height="400" data="profiles.EigenDecomposition._chart.series"> </div>
-		</div><!-- /.tab-pane -->
-	</div><!-- /.tab-content -->
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/dam/userProfileList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/dam/userProfileList.html b/eagle-webservice/src/main/webapp/app/partials/dam/userProfileList.html
deleted file mode 100644
index 8c0021a..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/dam/userProfileList.html
+++ /dev/null
@@ -1,138 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-list-alt"> </i>
-		<h3 class="box-title">
-			User Profiles
-			<small><a data-toggle="collapse" href="[data-id='algorithms']">Detail</a></small>
-		</h3>
-		<div class="pull-right">
-			<a class="label label-primary" ng-class="runningTaskCount() ? 'label-primary' : 'label-default'" data-toggle="modal" data-target="#taskMDL">Update</a>
-		</div>
-	</div>
-	<div class="box-body">
-		<!-- Algorithms -->
-		<div data-id="algorithms" class="collapse">
-			<table class="table table-bordered">
-				<thead>
-					<tr>
-						<th>Name</th>
-						<td>Feature</td>
-					</tr>
-				</thead>
-				<tbody>
-					<tr ng-repeat="algorithm in algorithmEntity.policy.algorithms">
-						<td>{{algorithm.name}}</td>
-						<td>{{algorithm.features}}</td>
-					</tr>
-				</tbody>
-			</table>
-			<hr/>
-		</div>
-
-		<!-- User Profile List -->
-		<p ng-show="profileList._promise.$$state.status !== 1">
-			<span class="fa fa-refresh fa-spin"> </span>
-			Loading...
-		</p>
-
-		<div sorttable source="profileList" ng-show="profileList._promise.$$state.status === 1">
-			<table class="table table-bordered" ng-non-bindable>
-				<thead>
-					<tr>
-						<th width="10%" sortpath="user">User</th>
-						<th>Most Predominat Feature</th>
-						<th width="10"></th>
-					</tr>
-				</thead>
-				<tbody>
-					<tr>
-						<td>
-							<a href="#/dam/userProfileDetail/{{item.user}}">{{item.user}}</a>
-						</td>
-						<td>
-							{{item.DE.topCommands.slice(0,3).join(", ")}}
-						</td>
-						<td>
-							<a href="#/dam/userProfileDetail/{{item.user}}">Detail</a>
-						</td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-	</div>
-</div>
-
-<!-- Modal: User profile Schedule Task -->
-<div class="modal fade" id="taskMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog modal-lg" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">Training History</h4>
-			</div>
-			<div class="modal-body">
-				<div sorttable source="tasks">
-					<table class="table table-bordered" ng-non-bindable>
-						<thead>
-							<tr>
-								<th sortpath="tags.type">Command</th>
-								<th sortpath="timestamp">Start Time</th>
-								<th sortpath="updateTime">Update Time</th>
-								<th sortpath="duration">Duration</th>
-								<th sortpath="status">Status</th>
-								<th width="10"> </th>
-							</tr>
-						</thead>
-						<tbody>
-							<tr>
-								<td>{{item.tags.type}}</td>
-								<td>{{common.format.date(item.timestamp) || "--"}}</td>
-								<td>{{common.format.date(item.updateTime) || "--"}}</td>
-								<td>{{item._duration}}</td>
-								<td class="text-nowrap">
-									<span class="fa fa-hourglass-start text-muted" ng-show="item.status === 'INITIALIZED'"></span>
-									<span class="fa fa-hourglass-half text-info" ng-show="item.status === 'PENDING'"></span>
-									<span class="fa fa-circle-o-notch text-primary" ng-show="item.status === 'EXECUTING'"></span>
-									<span class="fa fa-check-circle text-success" ng-show="item.status === 'SUCCEEDED'"></span>
-									<span class="fa fa-exclamation-circle text-danger" ng-show="item.status === 'FAILED'"></span>
-									<span class="fa fa-ban text-muted" ng-show="item.status === 'CANCELED'"></span>
-									{{item.status}}
-								</td>
-								<td>
-									<a ng-click="_parent.showTaskDetail(item)">Detail</a>
-								</td>
-							</tr>
-						</tbody>
-					</table>
-				</div>
-			</div>
-			<div class="modal-footer">
-				<button type="button" class="btn btn-primary pull-left" ng-click="updateTask()" ng-show="auth.isRole('ROLE_ADMIN')">
-					Update Now
-				</button>
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file

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

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/partials/login.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/login.html b/eagle-webservice/src/main/webapp/app/partials/login.html
new file mode 100644
index 0000000..112a76c
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/partials/login.html
@@ -0,0 +1,49 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<div class="login-box">
+	<div class="login-logo">
+		<a href="#/">Apache Eagle</a>
+	</div>
+	<!-- /.login-logo -->
+	<div class="login-box-body">
+		<p class="login-box-msg">Sign in to start your session</p>
+		<div class="form-group has-feedback">
+			<input type="text" class="form-control" placeholder="User Name" ng-model="username" ng-keypress="login($event)" autocomplete="off" id="username">
+			<span class="glyphicon glyphicon-user form-control-feedback"></span>
+		</div>
+		<div class="form-group has-feedback">
+			<input type="password" class="form-control" placeholder="Password" ng-model="password" ng-keypress="login($event)">
+			<span class="glyphicon glyphicon-lock form-control-feedback"></span>
+		</div>
+		<div class="row">
+			<div class="col-xs-8">
+				<!--div class="checkbox">
+					<label> <input type="checkbox" /> Remember Me
+					</label>
+				</div-->
+			</div>
+			<!-- /.col -->
+			<div class="col-xs-4">
+				<button class="btn btn-primary btn-block btn-flat" ng-click="login($event, true)" ng-disabled="lock">Sign In</button>
+			</div>
+			<!-- /.col -->
+		</div>
+	</div>
+	<!-- /.login-box-body -->
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/css/animation.css
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/css/animation.css b/eagle-webservice/src/main/webapp/app/public/css/animation.css
new file mode 100644
index 0000000..954bd29
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/css/animation.css
@@ -0,0 +1,46 @@
+@CHARSET "UTF-8";
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+[ui-view].ng-enter, [ui-view].ng-leave {
+	position: absolute;
+	left: 0;
+	right: 0;
+	-webkit-transition: all .5s ease-in-out;
+	-moz-transition: all .5s ease-in-out;
+	-o-transition: all .5s ease-in-out;
+	transition: all .3s ease-in-out;
+}
+
+[ui-view].ng-enter {
+	opacity: 0;
+}
+
+[ui-view].ng-enter-active {
+	opacity: 1;
+}
+
+[ui-view].ng-leave {
+	opacity: 1;
+	transform:translate3d(0, 0, 0);
+}
+
+[ui-view].ng-leave-active {
+	opacity: 0;
+	transform:translate3d(20%, 0, 0);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/css/main.css
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/css/main.css b/eagle-webservice/src/main/webapp/app/public/css/main.css
index 5e80728..2e951f2 100644
--- a/eagle-webservice/src/main/webapp/app/public/css/main.css
+++ b/eagle-webservice/src/main/webapp/app/public/css/main.css
@@ -43,11 +43,12 @@ body.no-sidebar .content-wrapper {
 	margin-top: 10px;
 }
 
-/* Common */
+	/* Common */
 a {
 	cursor: pointer;
 }
 
+/* Table */
 .table.table-sm>tbody>tr>td,
 .table.table-sm>tbody>tr>th,
 .table.table-sm>tfoot>tr>td,
@@ -148,41 +149,7 @@ a {
 	margin: 5px 0 10px 0;
 }
 
-
-body .tooltip-inner {
-	max-width: 500px;
-}
-
-.text-nowrap {
-	white-space: nowrap;
-}
-
-.text-ellipsis,
-.label.text-ellipsis {
-	overflow:hidden;
-	text-overflow:ellipsis;
-	display: inline-block;
-	white-space: nowrap;
-	max-width: 100%;
-}
-td.text-ellipsis {
-	display: table-cell;
-}
-
-.btn.btn-xs.sm {
-	font-size: 12px;
-	padding: 2px 6px;
-}
-
-.noSelect {
-	-khtml-user-select: none;
-	-moz-user-select: none;
-	-ms-user-select: none;
-	user-select: none;
-	-webkit-touch-callout: none;
-	-webkit-user-select: none;
-}
-
+/* inline group */
 .inline-group dl,
 .inline-group dl dt,
 .inline-group dl dd {
@@ -206,6 +173,7 @@ td.text-ellipsis {
 	margin-right: 5px;
 }
 
+/* Search box */
 .search-box {
 	position: relative;
 	margin-bottom: 15px;
@@ -327,6 +295,13 @@ ul.nav.nav-tabs li .btn {
 	background: #dd4b39;
 }
 
+.dropdown-submenu{position:relative;}
+.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
+.dropdown-submenu:hover>.dropdown-menu{display:block;}
+.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;}
+.dropdown-submenu:hover>a:after{border-left-color:#ffffff;}
+.dropdown-submenu.pull-left{float:none;}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;}
+
 /* Input Group */
 .input-group .input-group-btn select {
 	width: auto;
@@ -405,6 +380,55 @@ ul.tree.tree-bordered > li > ul > li > a {
 	padding: 8px 8px 8px 30px;
 }
 
+.product-list-in-box > .item {
+	-webkit-transition: background .5s linear;
+	-o-transition: background .5s linear;
+	transition: background .5s linear;
+}
+.product-list-in-box > .item.ng-animate {
+	transition: 0s;
+}
+.product-list-in-box > .item.active {
+	background: #F5FAFC;
+	-webkit-transition: none;
+	-o-transition: none;
+	transition: none;
+}
+
+.products-list.fixed-height {
+	height: 402px;
+	overflow-y: auto;
+}
+
+.products-list .product-operation {
+	float: left;
+	border: 1px solid #9EC8E0;
+	border-radius: 5px;
+	overflow: hidden;
+}
+
+.products-list .product-operation .fa {
+	display: block;
+	padding: 4px 16px;
+	color: #3c8dbc;
+}
+.products-list .product-operation a.fa:hover {
+	color: #FFFFFF;
+	background: #337ab7;
+}
+
+.products-list .product-operation.single .fa {
+	padding: 12px 12px;
+	font-size: 20px;
+}
+
+.products-list .item .product-info a.fa.fa-times {
+	display: none;
+}
+.products-list .item:hover .product-info a.fa.fa-times {
+	display: block;
+}
+
 /* Label */
 .label.label-default {
 	color: #FFFFFF;
@@ -421,24 +445,6 @@ ul.tree.tree-bordered > li > ul > li > a {
 	height: 200px;
 }
 
-/*.chart .chart-header h3 {
-	text-align: center;
-	margin: 10px 0 5px 0;
-}
-.chart .chart-body {
-	height: 200px;
-}
-
-.chart .chart-body .axis path, .axis line {
-	fill: none;
-	stroke: #000;
-	shape-rendering: crispEdges;
-}
-
-.chart .chart-body .bar {
-	fill: steelblue;
-}*/
-
 /* Tab */
 body .tab-content>.tab-pane {
 	display: block;
@@ -448,7 +454,18 @@ body .tab-content>.tab-pane {
 }
 body .tab-content>.tab-pane.active {
 	height: auto;
-	overflow-y: auto;
+	overflow-x: visible;
+	overflow-y: visible;
+}
+
+body .box-body .nav-pills > li > a {
+	padding: 5px 15px;
+	border: none;
+}
+
+/* Box */
+.box .guideline {
+	margin-top: 0;
 }
 
 /* Customize */
@@ -474,8 +491,36 @@ body .tab-content>.tab-pane.active {
 	z-index: 3;
 }
 
-.main-header .logo .logo-lg small {
-	font-size: 12px;
+.main-header .logo img {
+	height: 34px;
+}
+
+.main-header .navbar-toggle {
+	float: none;
+	border-radius: 0;
+}
+.main-header .navbar-toggle:hover {
+	background: rgba(0, 0, 0, 0.1);
+}
+
+#moduleMenu > ul > li.active > a {
+	border-top: 3px solid rgba(255,255,255,0.8);
+	padding-top: 12px;
+}
+
+@media (max-width: 767px) {
+	#moduleMenu > ul > li.active > a {
+		padding: 10px 15px;
+		border-top: none;
+		border-left: 3px solid rgba(255,255,255,0.8);
+	}
+
+	.main-header .navbar .navbar-custom-menu .nav .dropdown-menu li a {
+		color: #333;
+	}
+	.main-header .navbar .navbar-custom-menu .nav .dropdown-menu li a:hover {
+		color: #FFF;
+	}
 }
 
 #timeRangePickerCntr .navbar-form {
@@ -534,34 +579,37 @@ body .login-box, body .register-box {
 }
 
 
-.content .miao-chart {
-	border: none;
-	margin: 0;
+/* Misc */
+body .tooltip-inner {
+	max-width: 500px;
 }
 
-.content .miao-chart .box .chart-entity-title {
-	height: auto;
-	margin: 0;
+.text-nowrap {
+	white-space: nowrap;
 }
 
-.content .miao-chart .chart-entity-menu {
-	display: block;
-	top: 5px;
-	right: 25px;
+.text-ellipsis,
+.label.text-ellipsis {
+	overflow:hidden;
+	text-overflow:ellipsis;
+	display: inline-block;
+	white-space: nowrap;
+	max-width: 100%;
 }
-
-.content .miao-chart .box .chart-entity-menu {
-	display: block;
-	top: 5px;
-	right: 25px;
-	padding-right: 10px;
-	border-right: 1px solid #f4f4f4;
+td.text-ellipsis {
+	display: table-cell;
 }
 
-.content .miao-chart .box .chart-entity-drillDownPath .drillDownPath {
-	position: static;
+.btn.btn-xs.sm {
+	font-size: 12px;
+	padding: 2px 6px;
 }
 
-.content .miao-chart[data-miao-drilldown-depth='0'] .box .box-footer {
-	display: none;
-}
+.noSelect {
+	-khtml-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+	-webkit-touch-callout: none;
+	-webkit-user-select: none;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/classification/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/classification/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/classification/controller.js
new file mode 100644
index 0000000..ffb8c92
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/classification/controller.js
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var featureControllers = angular.module('featureControllers');
+	var feature = featureControllers.register("classification");
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+
+	// =============================================================
+	// =                        Sensitivity                        =
+	// =============================================================
+	feature.navItem("sensitivity", "Classification", "user-secret");
+	feature.controller('sensitivity', function(PageConfig, Site, $scope, Application, Entities, UI) {
+		PageConfig.pageTitle = "Data Classification";
+		PageConfig.pageSubTitle = Site.current().tags.site;
+		$scope.ajaxId = Math.random();
+		$scope.viewConfig = Application.current().configObj.view;
+
+		if(!$scope.viewConfig) {
+			$.dialog({
+				title: "OPS",
+				content: "View configuration not defined in Application."
+			});
+			return;
+		}
+
+		// ===================== Function =====================
+		$scope.export = function() {
+			var _data = {};
+			UI.fieldConfirm({title: "Export Classification", confirm: false, size: "large"}, _data, [
+				{name: "Data", field: "data", type: "blob", rows: 20, optional: true, readonly: true}]
+			);
+
+			Entities.queryEntities($scope.viewConfig.service, {site: Site.current().tags.site})._promise.then(function(data) {
+				_data.data = JSON.stringify(data, null, "\t");
+			});
+		};
+
+		$scope.import = function() {
+			UI.fieldConfirm({title: "Import Classification", size: "large"}, {}, [
+				{name: "Data", field: "data", type: "blob", rows: 20, optional: true}
+			], function(entity) {
+				var _list = common.parseJSON(entity.data, false);
+				if(!_list) {
+					return "Invalid JSON format";
+				}
+				if(!$.isArray(_list)) {
+					return "Not an array";
+				}
+			}).then(null, null, function(holder) {
+				Entities.updateEntity($scope.viewConfig.service, common.parseJSON(holder.entity.data, []), {timestamp: false})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		$scope.deleteAll = function() {
+			UI.deleteConfirm("All the Classification Data").then(null, null, function(holder) {
+				Entities.deleteEntities($scope.viewConfig.service, {site: Site.current().tags.site})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+	});
+
+	// =============================================================
+	// =                    Sensitivity - Folder                   =
+	// =============================================================
+	feature.controller('sensitivityViewFolder', function(Site, $scope, $wrapState, Entities) {
+		$scope.path = $wrapState.param.path || "/";
+		$scope.pathUnitList = [];
+		$scope.items = [];
+
+		// Mark sensitivity
+		$scope._oriItem = {};
+		$scope._markItem = {};
+
+		// ======================= View =======================
+		// Path
+		function _refreshPathUnitList(_path) {
+			var _start,_current, _unitList = [];
+			_path = _path + (_path.match(/\/$/) ? "" : "/");
+			for(_current = _start = 0 ; _current < _path.length ; _current += 1) {
+				if(_path[_current] === "/") {
+					_unitList.push({
+						name: _path.substring(_start, _current + (_current === 0 ? 1 : 0)),
+						path: _path.substring(0, _current === 0 ? 1 : _current)
+					});
+					_start = _current + 1;
+				}
+			}
+			$scope.pathUnitList = _unitList;
+		}
+
+		// Item
+		$scope.updateItems = function(path) {
+			if(path) $scope.path = path;
+
+			$scope.items = Entities.query($scope.viewConfig.api, {site: Site.current().tags.site, path: $scope.path});
+			$scope.items._promise.success(function(data) {
+				Entities.dialog(data, function() {
+					if($scope.path !== "/") $scope.updateItems("/");
+				});
+			});
+			_refreshPathUnitList($scope.path);
+		};
+
+		$scope.getFileName = function(item) {
+			return (item.resource + "").replace(/^.*\//, "");
+		};
+
+		$scope.updateItems($scope.path);
+
+		// =================== Sensitivity ===================
+		$scope.markSensitivity = function(item) {
+			$scope._oriItem = item;
+			$scope._markItem = {
+				prefix: $scope.viewConfig.prefix,
+				tags: {
+					site: Site.current().tags.site
+				},
+				sensitivityType: ""
+			};
+			$scope._markItem.tags[$scope.viewConfig.keys[0]] = item.resource;
+			$("#sensitivityMDL").modal();
+			setTimeout(function() {
+				$("#sensitiveType").focus();
+			}, 500);
+		};
+		$scope.confirmUpateSensitivity = function() {
+			$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
+			Entities.updateEntity($scope.viewConfig.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
+				Entities.dialog(data);
+			});
+			$("#sensitivityMDL").modal('hide');
+		};
+		$scope.unmarkSensitivity = function(item) {
+			$.dialog({
+				title: "Unmark Confirm",
+				content: "Do you want to remove the sensitivity mark on '" + item.resource + "'?",
+				confirm: true
+			}, function(ret) {
+				if(!ret) return;
+
+				var _cond = {site: Site.current().tags.site};
+				_cond[$scope.viewConfig.keys[0]] = item.resource;
+				Entities.deleteEntities($scope.viewConfig.service, _cond);
+
+				item.sensitiveType = null;
+				$scope.$apply();
+			});
+		};
+	});
+
+	// =============================================================
+	// =                    Sensitivity - Table                    =
+	// =============================================================
+	feature.controller('sensitivityViewTable', function(Site, $scope, Entities) {
+		$scope.table = null;
+
+		// Mark sensitivity
+		$scope._oriItem = {};
+		$scope._markItem = {};
+
+		// ======================= View =======================
+		var _fillAttr = function(list, key, target) {
+			list._promise.then(function() {
+				$.each(list, function(i, unit) {
+					unit[key] = unit[target];
+				});
+			});
+		};
+
+		$scope.databases = Entities.query($scope.viewConfig.api.database, {site: Site.current().tags.site});
+		_fillAttr($scope.databases, "database", $scope.viewConfig.mapping.database);
+
+		$scope.loadTables = function(database) {
+			if(database.tables) return;
+			var _qry = {
+				site: Site.current().tags.site
+			};
+			_qry[$scope.viewConfig.mapping.database] = database[$scope.viewConfig.mapping.database];
+			database.tables = Entities.query($scope.viewConfig.api.table, _qry);
+			_fillAttr(database.tables, "table", $scope.viewConfig.mapping.table);
+			_fillAttr(database.tables, "database", $scope.viewConfig.mapping.database);
+		};
+
+		$scope.loadColumns = function(database, table) {
+			$scope.table = table;
+
+			if(table.columns) return;
+			var _qry = {
+				site: Site.current().tags.site
+			};
+			_qry[$scope.viewConfig.mapping.database] = database[$scope.viewConfig.mapping.database];
+			_qry[$scope.viewConfig.mapping.table] = table[$scope.viewConfig.mapping.table];
+			table.columns = Entities.query($scope.viewConfig.api.column, _qry);
+			_fillAttr(table.columns, "column", $scope.viewConfig.mapping.column);
+		};
+
+		// =================== Sensitivity ===================
+		$scope.markSensitivity = function(item, event) {
+			if(event) event.stopPropagation();
+
+			$scope._oriItem = item;
+			$scope._markItem = {
+				prefix: $scope.viewConfig.prefix,
+				tags: {
+					site: Site.current().tags.site
+				},
+				sensitivityType: ""
+			};
+			$scope._markItem.tags[$scope.viewConfig.keys[0]] = item.resource;
+			$("#sensitivityMDL").modal();
+			setTimeout(function() {
+				$("#sensitiveType").focus();
+			}, 500);
+		};
+		$scope.confirmUpateSensitivity = function() {
+			$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
+			Entities.updateEntity($scope.viewConfig.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
+				Entities.dialog(data);
+			});
+			$("#sensitivityMDL").modal('hide');
+		};
+		$scope.unmarkSensitivity = function(item, event) {
+			if(event) event.stopPropagation();
+
+			$.dialog({
+				title: "Unmark Confirm",
+				content: "Do you want to remove the sensitivity mark on '" + item.resource + "'?",
+				confirm: true
+			}, function(ret) {
+				if(!ret) return;
+
+				var _qry = {
+					site: Site.current().tags.site
+				};
+				_qry[$scope.viewConfig.keys[0]] = item.resource;
+				Entities.deleteEntities($scope.viewConfig.service, _qry);
+
+				item.sensitiveType = null;
+				$scope.$apply();
+			});
+		};
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity.html b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity.html
new file mode 100644
index 0000000..41fb291
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity.html
@@ -0,0 +1,40 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-folder-open"></i>
+		<h3 class="box-title ng-binding">{{Application.current().displayName}}</h3>
+		<div class="box-tools pull-right" ng-if="viewConfig">
+			<div class="btn-group">
+				<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
+					<span class="fa fa-wrench"></span>
+				</button>
+				<ul class="dropdown-menu" role="menu">
+					<li><a ng-click="import()"><span class="fa fa-cloud-upload"></span> Import</a></li>
+					<li><a ng-click="export()"><span class="fa fa-cloud-download"></span> Export</a></li>
+					<li class="divider"></li>
+					<li class="danger"><a ng-click="deleteAll()"><span class="fa fa-trash"></span> Delete All</a></li>
+				</ul>
+			</div>
+		</div>
+	</div>
+	<div class="box-body">
+		<ng-include ng-if="viewConfig" src="'public/feature/classification/page/sensitivity/' + viewConfig.type + '.html?_=' + ajaxId"></ng-include>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/folder.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/folder.html b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/folder.html
new file mode 100644
index 0000000..e7abd4f
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/folder.html
@@ -0,0 +1,110 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<div ng-controller="classification_sensitivityViewFolder">
+	<ul class="list-inline path">
+		<li>Path:</li>
+		<li ng-repeat="unit in pathUnitList">
+			<a ng-click="updateItems(unit.path)" class="label bg-black">{{unit.name}}</a>
+		</li>
+	</ul>
+
+	<table class="table table-bordered">
+		<thead>
+			<tr>
+				<th width="15%">File Name</th>
+				<th width="10%">Owner</th>
+				<th width="10%">Group</th>
+				<th>Sensitivity Type</th>
+				<th width="10" ng-show="Auth.isRole('ROLE_ADMIN')"> </th>
+			</tr>
+		</thead>
+		<tbody>
+			<tr ng-show="items._promise.$$state.status !== 1">
+				<td colspan="5">
+					<span class="fa fa-refresh fa-spin"> </span>
+					Loading...
+				</td>
+			</tr>
+			<tr ng-show="items._promise.$$state.status === 1 && !items.length">
+				<td colspan="5">
+					<span class="fa fa-exclamation-triangle"> </span>
+					Empty Folder
+				</td>
+			</tr>
+			<tr ng-repeat="item in items" ng-class="{warning : item.sensitiveType}">
+				<td>
+					<span ng-show="!item.isdir">
+						<span class="fa fa-file"> </span>
+						{{getFileName(item)}}
+					</span>
+					<a ng-show="item.isdir" ng-click="updateItems(item.resource)">
+						<span class="fa fa-folder"> </span>
+						{{getFileName(item)}}
+					</a>
+
+					<span class="pull-right" ng-show="item.childSensitiveTypes.length">
+						<span class="fa fa-dot-circle-o text-muted" uib-tooltip="Contain child sensitivity defination"> </span>
+					</span>
+				</td>
+				<td>{{item.owner}}</td>
+				<td>{{item.group}}</td>
+				<td>{{item.sensitiveType}}</td>
+				<td ng-show="Auth.isRole('ROLE_ADMIN')">
+					<button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(item)" ng-show="!item.sensitiveType"
+					uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
+					<button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(item)" ng-show="item.sensitiveType"
+					uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
+				</td>
+			</tr>
+		</tbody>
+	</table>
+
+
+	<!-- Modal: Create / Edit site -->
+	<div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
+		<div class="modal-dialog" role="document">
+			<div class="modal-content">
+				<div class="modal-header">
+					<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+						<span aria-hidden="true">&times;</span>
+					</button>
+					<h4 class="modal-title">Mark Sensitivity Data</h4>
+				</div>
+				<div class="modal-body">
+					<div class="form-group">
+						<label>Resource</label>
+						<input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags.filedir" />
+					</div>
+					<div class="form-group">
+						<label>* Sensitivity Type</label>
+						<input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
+					</div>
+				</div>
+				<div class="modal-footer">
+					<button type="button" class="btn btn-default" data-dismiss="modal">
+						Close
+					</button>
+					<button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
+						Update
+					</button>
+				</div>
+			</div>
+		</div>
+	</div>
+
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/table.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/table.html b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/table.html
new file mode 100644
index 0000000..13d5807
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/table.html
@@ -0,0 +1,150 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<div ng-controller="classification_sensitivityViewTable">
+	<p ng-show="databases._promise.$$state.status !== 1">
+		<span class="fa fa-refresh fa-spin"> </span>
+		Loading...
+	</p>
+
+	<div ng-show="databases._promise.$$state.status === 1" class="row">
+		<div class="col-md-4">
+			<label>
+				Databases
+				({{databases.length}})
+			</label>
+			<ul class="tree tree-bordered" style="max-height: 500px; overflow-y: auto;">
+				<li ng-repeat="db in databases">
+					<span class="tree-item box-clickable text-primary" ng-click="db.show = !db.show; loadTables(db);">
+						<span ng-class="{'text-warning' : db.sensitiveType}">
+							<span class="fa fa-database"> </span>
+							{{db.database}}
+							<span ng-show="db.tables._promise.$$state.status === 1">({{db.tables.length}})</span>
+
+							<span ng-show="Auth.isRole('ROLE_ADMIN')">
+								<a class="fa fa-eye text-muted hover" ng-click="markSensitivity(db, $event)" ng-show="!db.sensitiveType"
+								uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a>
+								<a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(db, $event)" ng-show="db.sensitiveType"
+								uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a>
+							</span>
+
+							<span class="pull-right" ng-show="db.childSensitiveTypes.length">
+								<span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span>
+							</span>
+							<span ng-show="db.sensitiveType" class="pull-right">[{{db.sensitiveType}}]</span>
+						</span>
+					</span>
+					<ul ng-show="db.show">
+						<li ng-show="db.tables._promise.$$state.status !== 1">
+							<span>
+								<span class="fa fa-refresh fa-spin"> </span>
+								Loading...
+							</span>
+						</li>
+						<li ng-repeat="tb in db.tables" ng-class="{active : tb === table}">
+							<span class="tree-item box-clickable text-primary" ng-click="loadColumns(db, tb)">
+								<span ng-class="{'text-warning' : tb.sensitiveType}">
+									<span class="fa fa-table"> </span>
+									{{tb.table}}
+
+									<span ng-show="Auth.isRole('ROLE_ADMIN')">
+										<a class="fa fa-eye text-muted hover" ng-click="markSensitivity(tb, $event)" ng-show="!tb.sensitiveType"
+										uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a>
+										<a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(tb, $event)" ng-show="tb.sensitiveType"
+										uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a>
+									</span>
+
+									<span class="pull-right" ng-show="tb.childSensitiveTypes.length">
+										<span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span>
+									</span>
+									<span ng-show="tb.sensitiveType" class="pull-right">[{{tb.sensitiveType}}]</span>
+								</span>
+							</span>
+						</li>
+					</ul>
+				</li>
+			</ul>
+		</div>
+		<div class="col-md-8">
+			<label ng-show="table">Route: {{table.database}} > {{table.table}}</label>
+			<p ng-show="table && table.columns._promise.$$state.status !== 1">
+				<span class="fa fa-refresh fa-spin"> </span>
+				Loading...
+			</p>
+			<div ng-show="table && table.columns._promise.$$state.status === 1">
+				<table class="table table-bordered">
+					<thead>
+						<tr>
+							<th width="40%">Column Name</th>
+							<th>Sensitivity Type</th>
+							<th width="10" ng-show="Auth.isRole('ROLE_ADMIN')"> </th>
+						</tr>
+					</thead>
+					<tbody>
+						<tr ng-repeat="col in table.columns" ng-class="{warning : col.sensitiveType}">
+							<td>{{col.column}}</td>
+							<td>{{col.sensitiveType}}</td>
+							<td ng-show="Auth.isRole('ROLE_ADMIN')">
+								<button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(col)" ng-show="!col.sensitiveType"
+								uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
+								<button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(col)" ng-show="col.sensitiveType"
+								uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
+							</td>
+						</tr>
+					</tbody>
+				</table>
+			</div>
+		</div>
+	</div>
+
+
+
+
+
+
+	<!-- Modal: Create / Edit site -->
+	<div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
+		<div class="modal-dialog" role="document">
+			<div class="modal-content">
+				<div class="modal-header">
+					<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+						<span aria-hidden="true">&times;</span>
+					</button>
+					<h4 class="modal-title">Mark Sensitivity Data</h4>
+				</div>
+				<div class="modal-body">
+					<div class="form-group">
+						<label>Resource</label>
+						<input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags[viewConfig.keys[0]]" />
+					</div>
+					<div class="form-group">
+						<label>* Sensitivity Type</label>
+						<input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
+					</div>
+				</div>
+				<div class="modal-footer">
+					<button type="button" class="btn btn-default" data-dismiss="modal">
+						Close
+					</button>
+					<button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
+						Update
+					</button>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
\ No newline at end of file


[4/9] incubator-eagle git commit: EAGLE-139 EAGLE-163 Eagle UI Modularization and fix bugs in policy extensions

Posted by hd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/userProfile/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/userProfile/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/userProfile/controller.js
new file mode 100644
index 0000000..f048a85
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/userProfile/controller.js
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	var featureControllers = angular.module('featureControllers');
+	var feature = featureControllers.register("userProfile");
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+
+	// ==============================================================
+	// =                        User Profile                        =
+	// ==============================================================
+
+	// ======================== Profile List ========================
+	//feature.navItem("list", "User Profiles", "graduation-cap");
+	feature.controller('list', function(PageConfig, Site, $scope, $interval, Entities) {
+		PageConfig.pageSubTitle = Site.current().tags.site;
+
+		$scope.common = common;
+		$scope.algorithms = [];
+
+		// ======================================== Algorithms ========================================
+		$scope.algorithmEntity = {};
+		Entities.queryEntities("AlertDefinitionService", {site: Site.current().tags.site, dataSource: "userProfile"})._promise.then(function(data) {
+			$scope.algorithmEntity = common.getValueByPath(data, "obj[0]");
+			$scope.algorithmEntity.policy = common.parseJSON($scope.algorithmEntity.policyDef);
+		});
+
+		// ======================================= User profile =======================================
+		$scope.profileList = Entities.queryEntities("MLModelService", {site: Site.current().tags.site}, ["user", "algorithm", "content", "version"]);
+		$scope.profileList._promise.then(function() {
+			var _algorithms = {};
+			var _users = {};
+
+			// Map user
+			$.each($scope.profileList, function(i, unit) {
+				_algorithms[unit.tags.algorithm] = unit.tags.algorithm;
+				var _user = _users[unit.tags.user] = _users[unit.tags.user] || {user: unit.tags.user};
+				_user[unit.tags.algorithm] = {
+					version: unit.version
+				};
+
+				// DE
+				if(unit.tags.algorithm === "DE") {
+					var _statistics = common.parseJSON(unit.content);
+					_statistics = common.getValueByPath(_statistics, "statistics", []);
+					_user[unit.tags.algorithm].topCommands = $.map(common.array.top(_statistics, "mean"), function(command) {
+						return command.commandName;
+					});
+				}
+			});
+
+			// Map algorithms
+			$scope.algorithms = $.map(_algorithms, function(algorithm) {
+				return algorithm;
+			}).sort();
+
+			$scope.profileList.splice(0);
+			$scope.profileList.push.apply($scope.profileList, common.map.toArray(_users));
+		});
+
+		// =========================================== Task ===========================================
+		$scope.tasks = [];
+		function _loadTasks() {
+			var _tasks = Entities.queryEntities("ScheduleTaskService", {
+				site: Site.current().tags.site,
+				_pageSize: 100,
+				_duration: 1000 * 60 * 60 * 24 * 14,
+				__ETD: 1000 * 60 * 60 * 24
+			});
+			_tasks._promise.then(function() {
+				$scope.tasks.splice(0);
+				$scope.tasks.push.apply($scope.tasks, _tasks);
+
+				// Duration
+				$.each($scope.tasks, function(i, data) {
+					if(data.timestamp && data.updateTime) {
+						var _ms = (new moment(data.updateTime)).diff(new moment(data.timestamp));
+						var _d = moment.duration(_ms);
+						data._duration = Math.floor(_d.asHours()) + moment.utc(_ms).format(":mm:ss");
+						data.duration = _ms;
+					} else {
+						data._duration = "--";
+					}
+				});
+			});
+		}
+
+		$scope.runningTaskCount = function () {
+			return common.array.count($scope.tasks, "INITIALIZED", "status") +
+				common.array.count($scope.tasks, "PENDING", "status") +
+				common.array.count($scope.tasks, "EXECUTING", "status");
+		};
+
+		// Create task
+		$scope.updateTask = function() {
+			$.dialog({
+				title: "Confirm",
+				content: "Do you want to update now?",
+				confirm: true
+			}, function(ret) {
+				if(!ret) return;
+
+				var _entity = {
+					status: "INITIALIZED",
+					detail: "Newly created command",
+					tags: {
+						site: Site.current().tags.site,
+						type: "USER_PROFILE_TRAINING"
+					},
+					timestamp: +new Date()
+				};
+				Entities.updateEntity("ScheduleTaskService", _entity, {timestamp: false})._promise.success(function(data) {
+					if(!Entities.dialog(data)) {
+						_loadTasks();
+					}
+				});
+			});
+		};
+
+		// Show detail
+		$scope.showTaskDetail = function(task) {
+			var _content = $("<pre>").text(task.detail);
+
+			var $mdl = $.dialog({
+				title: "Detail",
+				content: _content
+			});
+
+			_content.click(function(e) {
+				if(!e.ctrlKey) return;
+
+				$.dialog({
+					title: "Confirm",
+					content: "Remove this task?",
+					confirm: true
+				}, function(ret) {
+					if(!ret) return;
+
+					$mdl.modal('hide');
+					Entities.deleteEntity("ScheduleTaskService", task)._promise.then(function() {
+						_loadTasks();
+					});
+				});
+			});
+		};
+
+		_loadTasks();
+		var _loadInterval = $interval(_loadTasks, app.time.refreshInterval);
+		$scope.$on('$destroy',function(){
+			$interval.cancel(_loadInterval);
+		});
+	});
+
+	// ======================= Profile Detail =======================
+	feature.controller('detail', function(PageConfig, Site, $scope, $wrapState, Entities) {
+		PageConfig.pageTitle = "User Profile";
+		PageConfig.pageSubTitle = Site.current().tags.site;
+		PageConfig
+			.addNavPath("User Profile", "/userProfile/list")
+			.addNavPath("Detail");
+
+		$scope.user = $wrapState.param.filter;
+
+		// User profile
+		$scope.profiles = {};
+		$scope.profileList = Entities.queryEntities("MLModelService", {site: Site.current().tags.site, user: $scope.user});
+		$scope.profileList._promise.then(function() {
+			$.each($scope.profileList, function(i, unit) {
+				unit._content = common.parseJSON(unit.content);
+				$scope.profiles[unit.tags.algorithm] = unit;
+			});
+
+			// DE
+			if($scope.profiles.DE) {
+				console.log($scope.profiles.DE);
+
+				$scope.profiles.DE._chart = {};
+
+				$scope.profiles.DE.estimates = {};
+				$.each($scope.profiles.DE._content, function(key, value) {
+					if(key !== "statistics") {
+						$scope.profiles.DE.estimates[key] = value;
+					}
+				});
+
+				var _meanList = [];
+				var _stddevList = [];
+
+				$.each($scope.profiles.DE._content.statistics, function(i, unit) {
+					_meanList[i] = {
+						x: unit.commandName,
+						y: unit.mean
+					};
+					_stddevList[i] = {
+						x: unit.commandName,
+						y: unit.stddev
+					};
+				});
+				$scope.profiles.DE._chart.series = [
+					{
+						key: "mean",
+						values: _meanList
+					},
+					{
+						key: "stddev",
+						values: _stddevList
+					}
+				];
+
+				// Percentage table list
+				$scope.profiles.DE.meanList = [];
+				var _total = common.array.sum($scope.profiles.DE._content.statistics, "mean");
+				$.each($scope.profiles.DE._content.statistics, function(i, unit) {
+					$scope.profiles.DE.meanList.push({
+						command: unit.commandName,
+						percentage: unit.mean / _total
+					});
+				});
+			}
+
+			// EigenDecomposition
+			if($scope.profiles.EigenDecomposition && $scope.profiles.EigenDecomposition._content.principalComponents) {
+				$scope.profiles.EigenDecomposition._chart = {
+					series: [],
+				};
+
+				$.each($scope.profiles.EigenDecomposition._content.principalComponents, function(z, grp) {
+					var _line = [];
+					$.each(grp, function(x, y) {
+						_line.push([x,y,z]);
+					});
+
+					$scope.profiles.EigenDecomposition._chart.series.push({
+						data: _line
+					});
+				});
+			}
+		});
+
+		// UI
+		$scope.showRawData = function(content) {
+			$.dialog({
+				title: "Raw Data",
+				content: $("<pre>").text(content)
+			});
+		};
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/detail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/detail.html b/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/detail.html
new file mode 100644
index 0000000..0f94e03
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/detail.html
@@ -0,0 +1,87 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-user"> </i>
+		<h3 class="box-title">
+			{{user}}
+		</h3>
+	</div>
+	<div class="box-body">
+		<div>
+			<div class="inline-group">
+				<dl><dt>User</dt><dd>{{user}}</dd></dl>
+				<dl><dt>Site</dt><dd>{{Site.current().tags.site}}</dd></dl>
+			</div>
+			<div class="inline-group">
+				<dl><dt>Other Info</dt><dd class="text-muted">N/A</dd></dl>
+			</div>
+		</div>
+
+		<div class="overlay" ng-hide="profileList._promise.$$state.status === 1;">
+			<span class="fa fa-refresh fa-spin"></span>
+		</div>
+	</div>
+</div>
+
+<!-- Analysis -->
+<div class="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<li class="active">
+			<a href="[data-id='DE']" data-toggle="tab" ng-click=" currentTab='DE'">DE</a>
+		</li>
+		<li>
+			<a href="[data-id='EigenDecomposition']" data-toggle="tab" ng-click=" currentTab='EigenDecomposition'">EigenDecomposition</a>
+		</li>
+		<li class="pull-right">
+			<button class="btn btn-primary" ng-click="showRawData(currentTab === 'EigenDecomposition' ? profiles.EigenDecomposition.content : profiles.DE.content)">Raw Data</button>
+		</li>
+	</ul>
+	<div class="tab-content">
+		<div class="tab-pane active" data-id="DE">
+			<div class="row">
+				<div class="col-md-9">
+					<div nvd3="profiles.DE._chart.series" data-config="{chart: 'column', xType: 'text', height: 400}" class="nvd3-chart-cntr" height="400"></div>
+					<div class="inline-group text-center">
+						<dl ng-repeat="(key, value) in profiles.DE.estimates"><dt>{{key}}</dt><dd>{{value}}</dd></dl>
+					</div>
+				</div>
+
+				<div class="col-md-3">
+					<table class="table table-bordered">
+						<thead>
+							<tr>
+								<th>Command</th>
+								<th>Percentage</th>
+							</tr>
+						</thead>
+						<tbody>
+							<tr ng-repeat="unit in profiles.DE.meanList">
+								<td>{{unit.command}}</td>
+								<td class="text-right">{{(unit.percentage*100).toFixed(2)}}%</td>
+							</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+		</div><!-- /.tab-pane -->
+		<div class="tab-pane" data-id="EigenDecomposition">
+			<div line3d-chart height="400" data="profiles.EigenDecomposition._chart.series"> </div>
+		</div><!-- /.tab-pane -->
+	</div><!-- /.tab-content -->
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/list.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/list.html b/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/list.html
new file mode 100644
index 0000000..2f14479
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/list.html
@@ -0,0 +1,138 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-list-alt"> </i>
+		<h3 class="box-title">
+			User Profiles
+			<small><a data-toggle="collapse" href="[data-id='algorithms']">Detail</a></small>
+		</h3>
+		<div class="pull-right">
+			<a class="label label-primary" ng-class="runningTaskCount() ? 'label-primary' : 'label-default'" data-toggle="modal" data-target="#taskMDL">Update</a>
+		</div>
+	</div>
+	<div class="box-body">
+		<!-- Algorithms -->
+		<div data-id="algorithms" class="collapse">
+			<table class="table table-bordered">
+				<thead>
+					<tr>
+						<th>Name</th>
+						<td>Feature</td>
+					</tr>
+				</thead>
+				<tbody>
+					<tr ng-repeat="algorithm in algorithmEntity.policy.algorithms">
+						<td>{{algorithm.name}}</td>
+						<td>{{algorithm.features}}</td>
+					</tr>
+				</tbody>
+			</table>
+			<hr/>
+		</div>
+
+		<!-- User Profile List -->
+		<p ng-show="profileList._promise.$$state.status !== 1">
+			<span class="fa fa-refresh fa-spin"> </span>
+			Loading...
+		</p>
+
+		<div sorttable source="profileList" ng-show="profileList._promise.$$state.status === 1">
+			<table class="table table-bordered" ng-non-bindable>
+				<thead>
+					<tr>
+						<th width="10%" sortpath="user">User</th>
+						<th>Most Predominat Feature</th>
+						<th width="10"></th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr>
+						<td>
+							<a href="#/userProfile/detail/{{item.user}}">{{item.user}}</a>
+						</td>
+						<td>
+							{{item.DE.topCommands.slice(0,3).join(", ")}}
+						</td>
+						<td>
+							<a href="#/userProfile/detail/{{item.user}}">Detail</a>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+
+<!-- Modal: User profile Schedule Task -->
+<div class="modal fade" id="taskMDL" tabindex="-1" role="dialog">
+	<div class="modal-dialog modal-lg" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">Training History</h4>
+			</div>
+			<div class="modal-body">
+				<div sorttable source="tasks">
+					<table class="table table-bordered" ng-non-bindable>
+						<thead>
+							<tr>
+								<th sortpath="tags.type">Command</th>
+								<th sortpath="timestamp">Start Time</th>
+								<th sortpath="updateTime">Update Time</th>
+								<th sortpath="duration">Duration</th>
+								<th sortpath="status">Status</th>
+								<th width="10"> </th>
+							</tr>
+						</thead>
+						<tbody>
+							<tr>
+								<td>{{item.tags.type}}</td>
+								<td>{{common.format.date(item.timestamp) || "--"}}</td>
+								<td>{{common.format.date(item.updateTime) || "--"}}</td>
+								<td>{{item._duration}}</td>
+								<td class="text-nowrap">
+									<span class="fa fa-hourglass-start text-muted" ng-show="item.status === 'INITIALIZED'"></span>
+									<span class="fa fa-hourglass-half text-info" ng-show="item.status === 'PENDING'"></span>
+									<span class="fa fa-circle-o-notch text-primary" ng-show="item.status === 'EXECUTING'"></span>
+									<span class="fa fa-check-circle text-success" ng-show="item.status === 'SUCCEEDED'"></span>
+									<span class="fa fa-exclamation-circle text-danger" ng-show="item.status === 'FAILED'"></span>
+									<span class="fa fa-ban text-muted" ng-show="item.status === 'CANCELED'"></span>
+									{{item.status}}
+								</td>
+								<td>
+									<a ng-click="_parent.showTaskDetail(item)">Detail</a>
+								</td>
+							</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-primary pull-left" ng-click="updateTask()" ng-show="Auth.isRole('ROLE_ADMIN')">
+					Update Now
+				</button>
+				<button type="button" class="btn btn-default" data-dismiss="modal">
+					Close
+				</button>
+			</div>
+		</div>
+	</div>
+</div>
\ No newline at end of file

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

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/app.config.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/app.config.js b/eagle-webservice/src/main/webapp/app/public/js/app.config.js
index f18b846..1e6fd79 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/app.config.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/app.config.js
@@ -1,77 +1,104 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-(function() {
-	'use strict';
-
-	app.config = {
-		// ============================================================================
-		// =                                   URLs                                   =
-		// ============================================================================
-		urls: {
-			HOST: '..',
-
-			updateEntity: 'rest/entities?serviceName=${serviceName}',
-			queryEntity: 'rest/entities/rowkey?serviceName=${serviceName}&value=${encodedRowkey}',
-			queryEntities: 'rest/list?query=${serviceName}[${condition}]{${values}}&pageSize=100000',
-			deleteEntity: 'rest/entities/delete?serviceName=${serviceName}&byId=true',
-			deleteEntities: 'rest/entities?query=${serviceName}[${condition}]{*}&pageSize=100000',
-
-			queryGroup: 'rest/list?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000',
-			querySeries: 'rest/list?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000&timeSeries=true&intervalmin=${intervalmin}',
-
-			query: 'rest/',
-
-			userProfile: 'rest/authentication',
-			logout: 'logout',
-		},
-
-		// ============================================================================
-		// =                               Data Sources                               =
-		// ============================================================================
-		dataSource: {
-			uiInvisibleList: ["userProfile"],
-		},
-	};
-
-	// ============================================================================
-	// =                                   URLs                                   =
-	// ============================================================================
-	app.getURL = function(name, kvs) {
-		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
-		var _path = app.config.urls[name];
-		if(!_path) throw "URL:'" + name + "' not exist!";
-		var _url = (_host ? _host + "/" : '') + _path;
-		if(kvs !== undefined) {
-			_url = common.template(_url, kvs);
-		}
-		return _url;
-	};
-
-	app._Host = function(host) {
-		if(host) {
-			localStorage.setItem("HOST", host);
-			return app;
-		}
-		return localStorage.getItem("HOST");
-	};
-	app._Host.clear = function() {
-		localStorage.removeItem("HOST");
-	};
-	app._Host.sample = "http://localhost:9099/eagle-service";
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+	'use strict';
+
+	app.config = {
+		// ============================================================================
+		// =                                   URLs                                   =
+		// ============================================================================
+		urls: {
+			HOST: '..',
+
+			updateEntity: 'rest/entities?serviceName=${serviceName}',
+			queryEntity: 'rest/entities/rowkey?serviceName=${serviceName}&value=${encodedRowkey}',
+			queryEntities: 'rest/list?query=${serviceName}[${condition}]{${values}}&pageSize=100000',
+			deleteEntity: 'rest/entities/delete?serviceName=${serviceName}&byId=true',
+			deleteEntities: 'rest/entities?query=${serviceName}[${condition}]{*}&pageSize=100000',
+
+			queryGroup: 'rest/list?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000',
+			querySeries: 'rest/list?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000&timeSeries=true&intervalmin=${intervalmin}',
+
+			query: 'rest/',
+
+			userProfile: 'rest/authentication',
+			logout: 'logout',
+
+			DELETE_HOOK: {
+				FeatureDescService: 'rest/module/feature?feature=${feature}',
+				ApplicationDescService: 'rest/module/application?application=${application}',
+				SiteDescService: 'rest/module/site?site=${site}'
+			},
+			UPDATE_HOOK: {
+				SiteDescService: 'rest/module/siteApplication'
+			}
+		},
+	};
+
+	// ============================================================================
+	// =                                   URLs                                   =
+	// ============================================================================
+	app.getURL = function(name, kvs) {
+		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
+		var _path = app.config.urls[name];
+		if(!_path) throw "URL:'" + name + "' not exist!";
+		var _url = (_host ? _host + "/" : '') + _path;
+		if(kvs !== undefined) {
+			_url = common.template(_url, kvs);
+		}
+		return _url;
+	};
+
+	function getHookURL(hookType, serviceName) {
+		var _path = app.config.urls[hookType][serviceName];
+		if(!_path) return null;
+
+		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
+		var _url = (_host ? _host + "/" : '') + _path;
+		return _url;
+	}
+
+	/***
+	 * Eagle support delete function to process special entity delete. Which will delete all the relative entity.
+	 * @param serviceName
+	 */
+	app.getDeleteURL = function(serviceName) {
+		return getHookURL('DELETE_HOOK', serviceName);
+	};
+
+	/***
+	 * Eagle support update function to process special entity update. Which will update all the relative entity.
+	 * @param serviceName
+	 */
+	app.getUpdateURL = function(serviceName) {
+		return getHookURL('UPDATE_HOOK', serviceName);
+	};
+
+	app._Host = function(host) {
+		if(host) {
+			localStorage.setItem("HOST", host);
+			return app;
+		}
+		return localStorage.getItem("HOST");
+	};
+	app._Host.clear = function() {
+		localStorage.removeItem("HOST");
+	};
+	app._Host.sample = "http://localhost:9099/eagle-service";
 })();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/app.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/app.js b/eagle-webservice/src/main/webapp/app/public/js/app.js
index 777844d..e9dfb2a 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/app.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/app.js
@@ -18,693 +18,467 @@
 
 var app = {};
 
-/* App Module */
-var eagleApp = angular.module('eagleApp', ['ngRoute', 'ngCookies', 'damControllers']);
-
-eagleApp.config(function($routeProvider) {
+(function() {
 	'use strict';
 
-	$routeProvider.when('/dam/summary', {
-		templateUrl : 'partials/dam/summary.html',
-		controller : 'summaryCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// Authorization
-	}).when('/dam/login', {
-		templateUrl : 'partials/dam/login.html',
-		controller : 'authLoginCtrl',
-		access: {skipCheck: true},
-
-	// Policy
-	}).when('/dam/policyList', {
-		templateUrl : 'partials/dam/policyList.html',
-		controller : 'policyListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyList/:dataSource', {
-		templateUrl : 'partials/dam/policyList.html',
-		controller : 'policyListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyDetail/', {
-		templateUrl : 'partials/dam/policyDetail.html',
-		controller : 'policyDetailCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyDetail/:encodedRowkey', {
-		templateUrl : 'partials/dam/policyDetail.html',
-		controller : 'policyDetailCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyEdit/:encodedRowkey', {
-		templateUrl : 'partials/dam/policyEdit.html',
-		controller : 'policyEditCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyCreate/', {
-		templateUrl : 'partials/dam/policyEdit.html',
-		controller : 'policyCreateCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// Alert
-	}).when('/dam/alertList', {
-		templateUrl : 'partials/dam/alertList.html',
-		controller : 'alertListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/alertList/:dataSource', {
-		templateUrl : 'partials/dam/alertList.html',
-		controller : 'alertListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/alertDetail/:encodedRowkey', {
-		templateUrl : 'partials/dam/alertDetail.html',
-		controller : 'alertDetailCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// Stream
-	}).when('/dam/streamList', {
-		templateUrl : 'partials/dam/streamList.html',
-		controller : 'streamListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// Site
-	}).when('/dam/siteList', {
-		templateUrl : 'partials/dam/siteList.html',
-		controller : 'siteListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-		access: {roles: ["ROLE_ADMIN"]},
-
-	// Sensitivity
-	}).when('/dam/sensitivitySummary', {
-		templateUrl : 'partials/dam/sensitivitySummary.html',
-		controller : 'sensitivitySummaryCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/sensitivity/:dataSrc', {
-		templateUrl : 'partials/dam/sensitivity.html',
-		controller : 'sensitivityCtrl',
-		reloadOnSearch: false,
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// User Profile
-	}).when('/dam/userProfileList', {
-		templateUrl : 'partials/dam/userProfileList.html',
-		controller : 'userProfileListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/userProfileDetail/:user', {
-		templateUrl : 'partials/dam/userProfileDetail.html',
-		controller : 'userProfileDetailCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	}).otherwise({
-		redirectTo : '/dam/summary'
+	/* App Module */
+	var eagleApp = angular.module('eagleApp', ['ngRoute', 'ngAnimate', 'ui.router', 'eagleControllers', 'featureControllers', 'eagle.service']);
+
+	// ======================================================================================
+	// =                                   Feature Module                                   =
+	// ======================================================================================
+	var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
+	var FN_ARG_SPLIT = /,/;
+	var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
+	var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
+
+	var featureControllers = angular.module('featureControllers', ['ui.bootstrap', 'eagle.components']);
+	var featureControllerCustomizeHtmlTemplate = {};
+	var featureControllerProvider;
+	var featureProvider;
+
+	featureControllers.config(function ($controllerProvider, $provide) {
+		featureControllerProvider = $controllerProvider;
+		featureProvider = $provide;
 	});
-});
-
-eagleApp.service('globalContent', function(Entities, $rootScope, $route, $location) {
-	'use strict';
-
-	var content = {
-		pageTitle: "",
-		pageSubTitle: "",
-		pageList: [],
-		navPath: [],
-		navMapping: {},
-
-		hideSite: false,
-		lockSite: false,
 
-		dataSrcList: [],
+	featureControllers.service("Feature", function($wrapState, PageConfig, ConfigPageConfig, FeaturePageConfig) {
+		var _features = {};
+		var _services = {};
 
-		setConfig: function(config) {
-			// Clean up
-			content.navPath = [];
-
-			// Fill configuration
-			$.extend(content, config);
-		},
-	};
-
-	return content;
-});
-
-// Site
-eagleApp.service('Site', function(Authorization, Entities, $rootScope, $route, $location, $q) {
-	'use strict';
-
-	var _currentSite;
-	var content = {};
-
-	content.list = [];
-	content.list.set = {};
-	content.dataSrcList = [];
-
-	content.current = function(site) {
-		if(site) {
-			var _prev = _currentSite;
-			_currentSite = site;
-
-			// Broadcast if site update
-			if(!_prev || _prev.name !== _currentSite.name) {
-				if(sessionStorage) {
-					sessionStorage.setItem("site", _currentSite.name);
-				}
+		var Feature = function(name, version) {
+			this.name = name;
+			this.version = version;
+			this.features = {};
+		};
 
-				if(!content.hideSite) $route.reload();
+		/***
+		 * Inner function. Replace the dependency of constructor.
+		 * @param constructor
+		 * @private
+		 */
+		Feature.prototype._replaceDependencies = function(constructor) {
+			var i, srvName;
+			var _constructor, _$inject;
+			var fnText, argDecl;
+
+			if($.isArray(constructor)) {
+				_constructor = constructor[constructor.length - 1];
+				_$inject = constructor.slice(0, -1);
+			} else if(constructor.$inject) {
+				_constructor = constructor;
+				_$inject = constructor.$inject;
+			} else {
+				_$inject = [];
+				_constructor = constructor;
+				fnText = constructor.toString().replace(STRIP_COMMENTS, '');
+				argDecl = fnText.match(FN_ARGS);
+				$.each(argDecl[1].split(FN_ARG_SPLIT), function(i, arg) {
+					arg.replace(FN_ARG, function(all, underscore, name) {
+						_$inject.push(name);
+					});
+				});
 			}
-		}
-		return _currentSite;
-	};
-	content.find = function(siteName) {
-		return common.array.find(siteName, content.list, "name");
-	};
-	content.url = function(site, url) {
-		if(arguments.length == 1) {
-			url = site;
-		} else {
-			content.current(site);
-		}
-		$location.url(url);
+			_constructor.$inject = _$inject;
 
-		if ($rootScope.$$phase != '$apply' && $rootScope.$$phase != '$digest') {
-			$rootScope.$apply();
-		}
-	};
-
-	var _promise;
-	content.refresh = function() {
-		content.list = [];
-		content.list.set = {};
-
-		content.dataSrcList = Entities.queryEntities("AlertDataSourceService", '');
-		content.dataSrcList._promise.success(function() {
-			$.each(content.dataSrcList, function(i, dataSrc) {
-				var _site = content.list.set[dataSrc.tags.site];
-				if(!_site) {
-					_site = content.list.set[dataSrc.tags.site] = {
-						name: dataSrc.tags.site,
-						dataSrcList: []
-					};
-					_site.dataSrcList.find = function(dataSrcName) {
-						return common.array.find(dataSrcName, _site.dataSrcList, "tags.dataSource");
-					};
-					content.list.push(_site);
-				}
-				_site.dataSrcList.push(dataSrc);
+			for(i = 0 ; i < _$inject.length ; i += 1) {
+				srvName = _$inject[i];
+				_$inject[i] = this.features[srvName] || _$inject[i];
+			}
 
-				// UI visible check
-				if($.inArray(dataSrc.tags.dataSource, app.config.dataSource.uiInvisibleList) !== -1) {
-					dataSrc.hide = true;
-				}
-			});
+			return _constructor;
+		};
 
-			if(sessionStorage && content.find(sessionStorage.getItem("site"))) {
-				content.current(content.find(sessionStorage.getItem("site")));
+		/***
+		 * Register a common service for feature usage. Common service will share between the feature. If you are coding customize feature, use 'Feature.service' is the better way.
+		 * @param name
+		 * @param constructor
+		 */
+		Feature.prototype.commonService = function(name, constructor) {
+			if(!_services[name]) {
+				featureProvider.service(name, constructor);
+				_services[name] = this.name;
 			} else {
-				content.current(content.list[0]);
+				throw "Service '" + name + "' has already be registered by feature '" + _services[name] + "'";
 			}
-		});
+		};
 
-		_promise = content.dataSrcList._promise;
-		return _promise;
-	};
+		/***
+		 * Register a service for feature usage.
+		 * @param name
+		 * @param constructor
+		 */
+		Feature.prototype.service = function(name, constructor) {
+			var _serviceName;
+			if(!this.features[name]) {
+				_serviceName = "__FEATURE_" + this.name + "_" + name;
+				featureProvider.service(_serviceName, this._replaceDependencies(constructor));
+				this.features[name] = _serviceName;
+			} else {
+				console.warn("Service '" + name + "' has already be registered.");
+			}
+		};
 
-	content._promise = function() {
-		if(!_promise) {
-			content.refresh();
-		}
-		return _promise;
-	};
+		/***
+		 * Create an navigation item in left navigation bar
+		 * @param path
+		 * @param title
+		 * @param icon use Font Awesome. Needn't with 'fa fa-'.
+		 */
+		Feature.prototype.navItem = function(path, title, icon) {
+			title = title || path;
+			icon = icon || "question";
+
+			FeaturePageConfig.addNavItem(this.name, {
+				icon: icon,
+				title: title,
+				url: "#/" + this.name + "/" + path
+			});
+		};
 
-	return content;
-});
+		/***
+		 * Register a controller.
+		 * @param name
+		 * @param constructor
+		 */
+		Feature.prototype.controller = function(name, constructor, htmlTemplatePath) {
+			var _name = this.name + "_" + name;
+
+			// Replace feature registered service
+			constructor = this._replaceDependencies(constructor);
+
+			// Register controller
+			featureControllerProvider.register(_name, constructor);
+			if(htmlTemplatePath) {
+				featureControllerCustomizeHtmlTemplate[_name] = htmlTemplatePath;
+			}
 
-// Authorization
-eagleApp.service('Authorization', function($http, $location, $cookies) {
-	'use strict';
+			return _name;
+		};
 
-	$http.defaults.withCredentials = true;
-
-	var _path = "";
-	var _userProfile = null;
-
-	var content = {
-		isLogin: true,	// Status mark. Work for UI status check, changed when eagle api return 403 authorization failure.
-		needLogin: function() {
-			_path = _path || $location.path();
-			content.isLogin = false;
-			$location.path("/dam/login");
-		},
-		login: function(username, password) {
-			var _hash = btoa(username + ':' + password);
-			return $http({
-				url : app.getURL('userProfile'),
-				method : "GET",
-				headers: {
-					'Authorization': "Basic " + _hash
-				}
-			}).then(function() {
-				content.isLogin = true;
-				return true;
-			}, function() {
-				return false;
-			});
-		},
-		logout: function() {
-			$http({
-				url : app.getURL('logout'),
-				method : "GET",
-			});
-		},
-		path: function(path) {
-			if(typeof path === "string") {
-				_path = path;
-			} else if(path === true) {
-				$location.path(_path || "");
-				_path = "";
+		/***
+		 * Register a configuration controller for admin usage.
+		 * @param name
+		 * @param constructor
+		 */
+		Feature.prototype.configController = function(name, constructor, htmlTemplatePath) {
+			var _name = "config_" + this.name + "_" + name;
+
+			// Replace feature registered service
+			constructor = this._replaceDependencies(constructor);
+
+			// Register controller
+			featureControllerProvider.register(_name, constructor);
+			if(htmlTemplatePath) {
+				featureControllerCustomizeHtmlTemplate[_name] = htmlTemplatePath;
 			}
-		},
-	};
-
-	var _promise;
-	content.userProfile = {};
-	content.isRole = function(role) {
-		if(!content.userProfile.roles) return null;
-
-		return content.userProfile.roles[role] === true;
-	};
-
-	content.refresh = function() {
-		_promise = $http({
-			url : app.getURL('userProfile'),
-			method : "GET",
-		}).then(function(data) {
-			content.userProfile = data.data;
-
-			// Role
-			content.userProfile.roles = {};
-			$.each(content.userProfile.authorities, function(i, role) {
-				content.userProfile.roles[role.authority] = true;
-			});
-		});
-		return _promise;
-	};
 
-	content._promise = function() {
-		if(!_promise) {
-			content.refresh();
-		}
-		return _promise;
-	};
+			return _name;
+		};
 
-	return content;
-});
+		/***
+		 * Create an navigation item in left navigation bar for admin configuraion page
+		 * @param path
+		 * @param title
+		 * @param icon use Font Awesome. Needn't with 'fa fa-'.
+		 */
+		Feature.prototype.configNavItem = function(path, title, icon) {
+			title = title || path;
+			icon = icon || "question";
+
+			ConfigPageConfig.addNavItem(this.name, {
+				icon: icon,
+				title: title,
+				url: "#/config/" + this.name + "/" + path
+			});
+		};
 
-eagleApp.service('Entities', function($http, $q, $rootScope, $location, Authorization) {
-	'use strict';
+		// Register
+		featureControllers.register = Feature.register = function(featureName) {
+			_features[featureName] = _features[featureName] || new Feature(featureName);
+			return _features[featureName];
+		};
 
-	// Query
-	function _query(name, kvs) {
-		kvs = kvs || {};
-		var _list = [];
-		var _condition = kvs._condition || {};
-		var _addtionalCondition = _condition.additionalCondition || {};
-		var _startTime, _endTime;
-		var _startTimeStr, _endTimeStr;
-
-		// Initial
-		// > Condition
-		delete kvs._condition;
-		if(_condition) {
-			kvs.condition = _condition.condition;
-		}
+		// Page go
+		Feature.go = function(feature, page, filter) {
+			if(!filter) {
+				$wrapState.go("page", {
+					feature: feature,
+					page: page
+				}, 2);
+			} else {
+				$wrapState.go("pageFilter", {
+					feature: feature,
+					page: page,
+					filter: filter
+				}, 2);
+			}
+		};
 
-		// > Values
-		if(!kvs.values) {
-			kvs.values = "*";
-		} else if($.isArray(kvs.values)) {
-			kvs.values = $.map(kvs.values, function(field) {
-				return (field[0] === "@" ? '' : '@') + field;
-			}).join(",");
-		}
+		return Feature;
+	});
 
-		var _url = app.getURL(name, kvs);
+	// ======================================================================================
+	// =                                   Router config                                    =
+	// ======================================================================================
+	eagleApp.config(function ($stateProvider, $urlRouterProvider) {
+		// Resolve
+		function _resolve(config) {
+			config = config || {};
 
-		// Fill special parameters
-		// > Query by time duration
-		if(_addtionalCondition._duration) {
-			_endTime = app.time.now();
-			_startTime = _endTime.clone().subtract(_addtionalCondition._duration, "ms");
+			var resolve = {
+				Site: function (Site) {
+					return Site._promise();
+				},
+				Authorization: function (Authorization) {
+					if(!config.roleType) {
+						return Authorization._promise();
+					} else {
+						return Authorization.rolePromise(config.roleType);
+					}
+				},
+				Application: function (Application) {
+					return Application._promise();
+				}
+			};
+
+			if(config.featureCheck) {
+				resolve._navigationCheck = function($q, $wrapState, Site, Application) {
+					var _deferred = $q.defer();
+
+					$q.all(Site._promise(), Application._promise()).then(function() {
+						var _match, i, tmpApp;
+						var _site = Site.current();
+						var _app = Application.current();
+
+						// Check application
+						if(_site && (
+							!_app ||
+							!_site.applicationList.set[_app.tags.application] ||
+							!_site.applicationList.set[_app.tags.application].enabled
+							)
+						) {
+							_match = false;
+
+							for(i = 0 ; i < _site.applicationGroupList.length ; i += 1) {
+								tmpApp = _site.applicationGroupList[i].enabledList[0];
+								if(tmpApp) {
+									_app = Application.current(tmpApp);
+									_match = true;
+									break;
+								}
+							}
+
+							if(!_match) {
+								_app = null;
+								Application.current(null);
+							}
+						}
+					}).finally(function() {
+						_deferred.resolve();
+					});
 
-			// Debug usage. Extend more time duration for end time
-			if(_addtionalCondition.__ETD) {
-				_endTime.add(_addtionalCondition.__ETD, "ms");
+					return _deferred.promise;
+				};
 			}
 
-			_addtionalCondition._startTime = _startTime;
-			_addtionalCondition._endTime = _endTime;
-
-			_startTimeStr = _startTime.format("YYYY-MM-DD HH:mm:ss");
-			_endTimeStr = _endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
+			return resolve;
+		}
 
-			_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
-		} else if(_addtionalCondition._startTime && _addtionalCondition._endTime) {
-			_startTimeStr = _addtionalCondition._startTime.format("YYYY-MM-DD HH:mm:ss");
-			_endTimeStr = _addtionalCondition._endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
+		// Router
+		var _featureBase = {
+			templateUrl: function ($stateParams) {
+				var _htmlTemplate = featureControllerCustomizeHtmlTemplate[$stateParams.feature + "_" + $stateParams.page];
+				return  "public/feature/" + $stateParams.feature + "/page/" + (_htmlTemplate ||  $stateParams.page) + ".html?_=" + Math.random();
+			},
+			controllerProvider: function ($stateParams) {
+				return $stateParams.feature + "_" + $stateParams.page;
+			},
+			resolve: _resolve({featureCheck: true}),
+			pageConfig: "FeaturePageConfig"
+		};
 
-			_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
-		}
+		$urlRouterProvider.otherwise("/landing");
+		$stateProvider
+			// =================== Landing ===================
+			.state('landing', {
+				url: "/landing",
+				templateUrl: "partials/landing.html?_=" + Math.random(),
+				controller: "landingCtrl",
+				resolve: _resolve({featureCheck: true})
+			})
+
+			// ================ Authorization ================
+			.state('login', {
+				url: "/login",
+				templateUrl: "partials/login.html?_=" + Math.random(),
+				controller: "authLoginCtrl",
+				access: {skipCheck: true}
+			})
+
+			// ================ Configuration ================
+			// Site
+			.state('configSite', {
+				url: "/config/site",
+				templateUrl: "partials/config/site.html?_=" + Math.random(),
+				controller: "configSiteCtrl",
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			})
+
+			// Application
+			.state('configApplication', {
+				url: "/config/application",
+				templateUrl: "partials/config/application.html?_=" + Math.random(),
+				controller: "configApplicationCtrl",
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			})
+
+			// Feature
+			.state('configFeature', {
+				url: "/config/feature",
+				templateUrl: "partials/config/feature.html?_=" + Math.random(),
+				controller: "configFeatureCtrl",
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			})
+
+			// Feature configuration page
+			.state('configFeatureDetail', $.extend({url: "/config/:feature/:page"}, {
+				templateUrl: function ($stateParams) {
+					var _htmlTemplate = featureControllerCustomizeHtmlTemplate[$stateParams.feature + "_" + $stateParams.page];
+					return  "public/feature/" + $stateParams.feature + "/page/" + (_htmlTemplate ||  $stateParams.page) + ".html?_=" + Math.random();
+				},
+				controllerProvider: function ($stateParams) {
+					return "config_" + $stateParams.feature + "_" + $stateParams.page;
+				},
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			}))
+
+			// =================== Feature ===================
+			// Dynamic feature page
+			.state('page', $.extend({url: "/:feature/:page"}, _featureBase))
+			.state('pageFilter', $.extend({url: "/:feature/:page/:filter"}, _featureBase))
+		;
+	});
 
-		// > Query contains metric name
-		if(_addtionalCondition._metricName) {
-			_url += "&metricName=" + _addtionalCondition._metricName;
-		}
+	eagleApp.filter('parseJSON', function () {
+		return function (input, defaultVal) {
+			return common.parseJSON(input, defaultVal);
+		};
+	});
 
-		// > Customize page size
-		if(_addtionalCondition._pageSize) {
-			_url = _url.replace(/pageSize=\d+/, "pageSize=" + _addtionalCondition._pageSize);
-		}
+	eagleApp.filter('split', function () {
+		return function (input, regex) {
+			return input.split(regex);
+		};
+	});
 
-		// AJAX
-		var canceler = $q.defer();
-		_list._promise = $http.get(_url, {timeout: canceler.promise}).success(function(data) {
-			_list.push.apply(_list, data.obj);
-		});
-		_list._promise.abort = function() {
-			canceler.resolve();
+	eagleApp.filter('reverse', function () {
+		return function (items) {
+			return items.slice().reverse();
 		};
+	});
 
-		_list._promise.then(function() {}, function(data) {
-			if(data.status === 403) {
-				Authorization.needLogin();
+	// ======================================================================================
+	// =                                   Main Controller                                  =
+	// ======================================================================================
+	eagleApp.controller('MainCtrl', function ($scope, $wrapState, $http, $injector, PageConfig, FeaturePageConfig, Site, Authorization, Entities, nvd3, Application, Feature, UI) {
+		window.site = $scope.Site = Site;
+		window.auth = $scope.Auth = Authorization;
+		window.entities = $scope.Entities = Entities;
+		window.application = $scope.Application = Application;
+		window.pageConfig = $scope.PageConfig = PageConfig;
+		window.featurePageConfig = $scope.FeaturePageConfig = FeaturePageConfig;
+		window.feature = $scope.Feature = Feature;
+		window.ui = $scope.UI = UI;
+		window.nvd3 = nvd3;
+		$scope.app = app;
+		$scope.common = common;
+
+		Object.defineProperty(window, "scope",{
+			get: function() {
+				return angular.element("[ui-view]").scope();
 			}
 		});
 
-		return _list;
-	}
-	function _post(url, entities) {
-		var _list = [];
-		_list._promise = $http({
-			method: 'POST',
-			url: url,
-			headers: {
-				"Content-Type": "application/json"
-			},
-			data: entities
-		}).success(function(data) {
-			_list.push.apply(_list, data.obj);
-		});
-		return _list;
-	}
-	function _delete(url) {
-		var _list = [];
-		_list._promise = $http({
-			method: 'DELETE',
-			url: url,
-			headers: {
-				"Content-Type": "application/json"
-			},
-		}).success(function(data) {
-			_list.push.apply(_list, data.obj);
-		});
-		return _list;
-	}
-	function ParseCondition(condition) {
-		var _this = this;
-		_this.condition = "";
-		_this.additionalCondition = {};
-
-		if(typeof condition === "string") {
-			_this.condition = condition;
-		} else {
-			_this.condition = $.map(condition, function(value, key) {
-				if(!key.match(/^_/)) {
-					if(value === undefined || value === null) {
-						return '@' + key + '=~".*"';
-					} else {
-						return '@' + key + '="' + value + '"';
-					}
-				} else {
-					_this.additionalCondition[key] = value;
-					return null;
-				}
-			}).join(" AND ");
-		}
-		return _this;
-	}
+		// Clean up
+		$scope.$on('$stateChangeStart', function (event, next, nextParam, current, currentParam) {
+			console.log("[Switch] current ->", current, currentParam);
+			console.log("[Switch] next ->", next, nextParam);
+			// Page initialization
+			PageConfig.reset();
 
-	var pkg = {
-		_query: _query,
-		_post: _post,
-
-		updateEntity: function(serviceName, entities, config) {
-			config = config || {};
-			if(!$.isArray(entities)) entities = [entities];
+			// Dynamic navigation list
+			if(next.pageConfig) {
+				$scope.PageConfig.navConfig = $injector.get(next.pageConfig);
+			} else {
+				$scope.PageConfig.navConfig = {};
+			}
 
-			// Post clone entities
-			var _entities = $.map(entities, function(entity) {
-				var _entity = {};
+			// Authorization
+			// > Login check
+			if (!common.getValueByPath(next, "access.skipCheck", false)) {
+				if (!Authorization.isLogin) {
+					console.log("[Authorization] Need access. Redirect...");
+					$wrapState.go("login");
+				}
+			}
 
-				// Clone variables
-				$.each(entity, function(key, value) {
-					// Skip inner variables
-					if(!key.match(/^__/)) {
-						_entity[key] = entity[key];
+			// > Role control
+			/*var _roles = common.getValueByPath(next, "access.roles", []);
+			if (_roles.length && Authorization.userProfile.roles) {
+				var _roleMatch = false;
+				$.each(_roles, function (i, roleName) {
+					if (Authorization.isRole(roleName)) {
+						_roleMatch = true;
+						return false;
 					}
 				});
 
-				// Add timestamp
-				if(config.timestamp !== false) {
-					if(config.createTime !== false && !_entity.createdTime) {
-						_entity.createdTime = new moment().valueOf();
-					}
-					if(config.lastModifiedDate !== false) {
-						_entity.lastModifiedDate = new moment().valueOf();
-					}
+				if (!_roleMatch) {
+					$wrapState.path("/dam");
 				}
+			}*/
+		});
 
-				return _entity;
-			});
+		$scope.$on('$stateChangeError', function (event, next, nextParam, current, currentParam, error) {
+			console.error("[Switch] Error", arguments);
+		});
 
-			return _post(app.getURL("updateEntity", {serviceName: serviceName}), _entities);
-		},
+		// Get side bar navigation item class
+		$scope.getNavClass = function (page) {
+			var path = page.url.replace(/^#/, '');
 
-		deleteEntity: function(serviceName, entities) {
-			if(!$.isArray(entities)) entities = [entities];
+			if ($wrapState.path() === path) {
+				PageConfig.pageTitle = PageConfig.pageTitle || page.title;
+				return "active";
+			} else {
+				return "";
+			}
+		};
 
-			var _entities = $.map(entities, function(entity) {
-				return typeof entity === "object" ? entity.encodedRowkey : entity;
-			});
-			return _post(app.getURL("deleteEntity", {serviceName: serviceName}), _entities);
-		},
-		deleteEntities: function(serviceName, condition) {
-			return _delete(app.getURL("deleteEntities", {serviceName: serviceName, condition: new ParseCondition(condition).condition}));
-		},
-
-		queryEntity: function(serviceName, encodedRowkey) {
-			return _query("queryEntity", {serviceName: serviceName, encodedRowkey: encodedRowkey});
-		},
-		queryEntities: function(serviceName, condition, fields) {
-			return _query("queryEntities", {serviceName: serviceName, _condition: new ParseCondition(condition), values: fields});
-		},
-		queryGroup: function(serviceName, condition, groupBy, fields) {
-			return _query("queryGroup", {serviceName: serviceName, _condition: new ParseCondition(condition), groupBy: groupBy, values: fields});
-		},
-		querySeries: function(serviceName, condition, groupBy, fields, intervalmin) {
-			var _cond = new ParseCondition(condition);
-			var _list = _query("querySeries", {serviceName: serviceName, _condition: _cond, groupBy: groupBy, values: fields, intervalmin: intervalmin});
-			_list._promise.success(function() {
-				if(_list.length === 0) {
-					_list._empty = true;
-					_list._convert = true;
-
-					for(var i = 0; i <= (_cond.additionalCondition._endTime.valueOf() - _cond.additionalCondition._startTime.valueOf()) / (1000 * 60 * intervalmin); i += 1) {
-						_list.push(0);
-					}
-				} else if(_list.length === 1) {
-					_list._convert = true;
-					var _unit = _list.pop();
-					_list.push.apply(_list, _unit.value[0]);
-				}
+		// Get side bar navigation item class visible
+		$scope.getNavVisible = function (page) {
+			if (!page.roles) return true;
 
-				if(_list._convert) {
-					var _current = _cond.additionalCondition._startTime.clone();
-					$.each(_list, function(i, value) {
-						_list[i] = {
-							x: _current.valueOf(),
-							y: value
-						};
-						_current.add(intervalmin, "m");
-					});
+			for (var i = 0; i < page.roles.length; i += 1) {
+				var roleName = page.roles[i];
+				if (Authorization.isRole(roleName)) {
+					return true;
 				}
-			});
-			return _list;
-		},
-
-		query: function(path, params) {
-			var _list = [];
-			_list._promise = $http({
-				method: 'GET',
-				url: app.getURL("query") + path,
-				params: params
-			}).success(function(data) {
-				_list.push.apply(_list, data.obj);
-			});
-			return _list;
-		},
-
-		dialog: function(data, callback) {
-			if(data.success === false || (data.exception || "").trim()) {
-				return $.dialog({
-					title: "OPS",
-					content: $("<pre>").html(data.exception)
-				}, callback);
 			}
-			return false;
-		},
-	};
-	return pkg;
-});
-
-eagleApp.filter('parseJSON', function() {
-	'use strict';
-
-	return function(input, defaultVal) {
-		return common.parseJSON(input, defaultVal);
-	};
-});
 
-eagleApp.filter('split', function() {
-	'use strict';
-
-	return function(input, regex) {
-		return input.split(regex);
-	};
-});
-
-eagleApp.filter('reverse', function() {
-	'use strict';
-
-	return function(items) {
-		return items.slice().reverse();
-	};
-});
-
-eagleApp.controller('MainCtrl', function($scope, $location, $http, globalContent, Site, Authorization, Entities, nvd3) {
-	'use strict';
-
-	window.globalContent = $scope.globalContent = globalContent;
-	window.site = $scope.site = Site;
-	window.auth = $scope.auth = Authorization;
-	window.entities = $scope.entities = Entities;
-	window.nvd3 = nvd3;
-	$scope.app = app;
-
-	// Clean up
-	$scope.$on('$routeChangeStart', function(event, next, current) {
-		// Page initialization
-		globalContent.pageTitle = "";
-		globalContent.pageSubTitle = "";
-		globalContent.hideSite = false;
-		globalContent.lockSite = false;
-		globalContent.hideSidebar = false;
-		globalContent.hideUser = false;
+			return false;
+		};
 
 		// Authorization
-		// > Login check
-		if(!common.getValueByPath(next, "access.skipCheck", false)) {
-			if(!Authorization.isLogin) {
-				$location.path("/dam/login");
-			}
-		}
-
-		// > Role control
-		var _roles = common.getValueByPath(next, "access.roles", []);
-		if(_roles.length && Authorization.userProfile.roles) {
-			var _roleMatch = false;
-			$.each(_roles, function(i, roleName) {
-				if(Authorization.isRole(roleName)) {
-					_roleMatch = true;
-					return false;
-				}
-			});
-
-			if(!_roleMatch) {
-				$location.path("/dam");
-			}
-		}
+		$scope.logout = function () {
+			console.log("[Authorization] Logout. Redirect...");
+			Authorization.logout();
+			$wrapState.go("login");
+		};
 	});
-
-	// Get side bar navigation item class
-	$scope.getNavClass = function(page) {
-		var path = page.url.replace(/^#/, '');
-
-		if ($location.path() == path) {
-			globalContent.pageTitle = globalContent.pageTitle || page.title;
-			return "active";
-		} else {
-			return "";
-		}
-	};
-
-	// Get side bar navigation item class visible
-	$scope.getNavVisible = function(page) {
-		if(!page.roles) return true;
-
-		for(var i = 0 ; i < page.roles.length ; i += 1) {
-			var roleName = page.roles[i];
-			if(Authorization.isRole(roleName)) {
-				return true;
-			}
-		}
-
-		return false;
-	};
-
-	// Authorization
-	$scope.logout = function() {
-		Authorization.logout();
-		$location.path("/dam/login");
-	};
-});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/app.ui.js b/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
index 4e0ee6e..28727ba 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
@@ -1,66 +1,76 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-(function() {
-	// ================== AdminLTE Update ==================
-	var _boxSelect = $.AdminLTE.options.boxWidgetOptions.boxWidgetSelectors;
-
-	// Box collapse
-	$(document).on("click", _boxSelect.collapse, function(e) {
-		if(common.getValueByPath($._data(this), "events.click")) return;
-
-		e.preventDefault();
-		$.AdminLTE.boxWidget.collapse($(this));
-	});
-
-	// Box remove
-	$(document).on("click", _boxSelect.remove, function(e) {
-		if(common.getValueByPath($._data(this), "events.click")) return;
-
-		e.preventDefault();
-		$.AdminLTE.boxWidget.remove($(this));
-	});
-
-	// =================== jQuery Update ===================
-	// Slide Toggle
-	var _slideToggle = $.fn.slideToggle;
-	$.fn.slideToggle = function(showOrHide) {
-		if(arguments.length === 1 && typeof showOrHide === "boolean") {
-			if(showOrHide) {
-				this.slideDown();
-			} else {
-				this.slideUp();
-			}
-		} else {
-			_slideToggle.apply(this, arguments);
-		}
-	};
-
-	// Fade Toggle
-	var _fadeToggle = $.fn.fadeToggle;
-	$.fn.fadeToggle = function(showOrHide) {
-		if(arguments.length === 1 && typeof showOrHide === "boolean") {
-			if(showOrHide) {
-				this.fadeIn();
-			} else {
-				this.fadeOut();
-			}
-		} else {
-			_fadeToggle.apply(this, arguments);
-		}
-	};
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+	// ================== AdminLTE Update ==================
+	var _boxSelect = $.AdminLTE.options.boxWidgetOptions.boxWidgetSelectors;
+
+	// Box collapse
+	$(document).on("click", _boxSelect.collapse, function(e) {
+		if(common.getValueByPath($._data(this), "events.click")) return;
+
+		e.preventDefault();
+		$.AdminLTE.boxWidget.collapse($(this));
+	});
+
+	// Box remove
+	$(document).on("click", _boxSelect.remove, function(e) {
+		if(common.getValueByPath($._data(this), "events.click")) return;
+
+		e.preventDefault();
+		$.AdminLTE.boxWidget.remove($(this));
+	});
+
+	// =================== jQuery Update ===================
+	// Slide Toggle
+	var _slideToggle = $.fn.slideToggle;
+	$.fn.slideToggle = function(showOrHide) {
+		if(arguments.length === 1 && typeof showOrHide === "boolean") {
+			if(showOrHide) {
+				this.slideDown();
+			} else {
+				this.slideUp();
+			}
+		} else {
+			_slideToggle.apply(this, arguments);
+		}
+	};
+
+	// Fade Toggle
+	var _fadeToggle = $.fn.fadeToggle;
+	$.fn.fadeToggle = function(showOrHide) {
+		if(arguments.length === 1 && typeof showOrHide === "boolean") {
+			if(showOrHide) {
+				this.fadeIn();
+			} else {
+				this.fadeOut();
+			}
+		} else {
+			_fadeToggle.apply(this, arguments);
+		}
+	};
+
+	// Modal
+	var _modal = $.fn.modal;
+	$.fn.modal = function() {
+		setTimeout(function() {
+			$(this).find("input, textarea").filter(':visible:first').focus();
+		}.bind(this), 500);
+		_modal.apply(this, arguments);
+		return this;
+	};
 })();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/common.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/common.js b/eagle-webservice/src/main/webapp/app/public/js/common.js
index c5b385f..706eeec 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/common.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/common.js
@@ -1,211 +1,226 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var common = {};
-
-common.template = function (str, list) {
-	$.each(list, function(key, value) {
-		var _regex = new RegExp("\\$\\{" + key + "\\}", "g");
-		str = str.replace(_regex, value);
-	});
-	return str;
-};
-
-common.getValueByPath = function (unit, path, defaultValue) {
-	if(unit === null || unit === undefined) throw "Unit or path can't be empty!";
-	if(path === "" || path === null || path === undefined) return unit;
-
-	path = path.replace(/\[(\d+)\]/g, ".$1").replace(/^\./, "").split(/\./);
-	$.each(path, function(i, path) {
-		unit = unit[path];
-		if(unit === null || unit === undefined) {
-			unit = null;
-			return false;
-		}
-	});
-	if(unit === null && defaultValue !== undefined) {
-		unit = defaultValue;
-	}
-	return unit;
-};
-
-common.setValueByPath = function(unit, path, value) {
-	if(!unit || typeof path !== "string" || path === "") throw "Unit or path can't be empty!";
-
-	var _inArray = false;
-	var _end = 0;
-	var _start = 0;
-	var _unit = unit;
-
-	function _nextPath(array) {
-		var _key = path.slice(_start, _end);
-		if(_inArray) {
-			_key = _key.slice(0, -1);
-		}
-		if(!_unit[_key]) {
-			if(array) {
-				_unit[_key] = [];
-			} else {
-				_unit[_key] = {};
-			}
-		}
-		_unit = _unit[_key];
-	}
-
-	for(; _end < path.length ; _end += 1) {
-		if(path[_end] === ".") {
-			_nextPath(false);
-			_start = _end + 1;
-			_inArray = false;
-		} else if(path[_end] === "[") {
-			_nextPath(true);
-			_start = _end + 1;
-			_inArray = true;
-		}
-	}
-
-	_unit[path.slice(_start, _inArray ? -1 : _end)] = value;
-
-	return unit;
-};
-
-common.parseJSON = function (str, defaultVal) {
-	try {
-		return JSON.parse(str);
-	} catch(err) {
-		if(defaultVal === undefined) {
-			console.warn("Can't parse JSON: " + str);
-		}
-	}
-	return defaultVal === undefined ? null : defaultVal;
-};
-
-common.isEmpty = function(val) {
-	if($.isArray(val)) {
-		return val.length === 0;
-	} else {
-		return val === null || val === undefined;
-	}
-};
-
-// ====================== Format ======================
-common.format = {};
-
-/*
- * Format date to string. Support number, string, Date instance. Will auto convert time zone offset(Moment instance will keep time zone).
- */
-common.format.date = function(val, type) {
-	if(val === undefined || val === null) return "";
-
-	if(typeof val === "number" || typeof val === "string" || val instanceof Date) {
-		val = app.time.offset(val);
-	}
-	switch(type) {
-	default:
-		return val.format("YYYY-MM-DD HH:mm:ss") + (val.utcOffset() === 0 ? '[UTC]' : '');
-	}
-};
-
-// ====================== Array =======================
-common.array = {};
-
-common.array.sum = function(list, path) {
-	var _sum = 0;
-	if(list) {
-		$.each(list, function(i, unit) {
-			var _val = common.getValueByPath(unit, path);
-			if(typeof _val === "number") {
-				_sum += _val;
-			}
-		});
-	}
-	return _sum;
-};
-
-common.array.max = function(list, path) {
-	var _max = null;
-	if(list) {
-		$.each(list, function(i, unit) {
-			var _val = common.getValueByPath(unit, path);
-			if(typeof _val === "number" && (_max === null || _max < _val)) {
-				_max = _val;
-			}
-		});
-	}
-	return _max;
-};
-
-common.array.bottom = function(list, path, count) {
-	var _list = list.slice();
-
-	_list.sort(function(a, b) {
-		var _a = common.getValueByPath(a, path, null);
-		var _b = common.getValueByPath(b, path, null);
-
-		if(_a === _b) return 0;
-		if(_a < _b || _a === null) {
-			return -1;
-		} else {
-			return 1;
-		}
-	});
-	return !count ? _list : _list.slice(0, count);
-};
-common.array.top = function(list, path, count) {
-	var _list = common.array.bottom(list, path);
-	_list.reverse();
-	return !count ? _list : _list.slice(0, count);
-};
-
-common.array.find = function(val, list, path, findAll) {
-	path = path || "";
-	var _list = $.grep(list, function(unit) {
-		return val === common.getValueByPath(unit, path);
-	});
-	return findAll ? _list : (_list.length === 0 ? null : _list[0]);
-};
-
-common.array.filter = function(val, list, path) {
-	return common.array.find(val, list, path, true);
-};
-
-common.array.count = function(list, val, path) {
-	if(arguments.length === 1) {
-		return list.length;
-	} else {
-		return common.array.find(val, list, path, true).length;
-	}
-};
-
-common.array.remove = function(val, list) {
-	for(var i = 0 ; i < list.length ; i += 1) {
-		if(list[i] === val) {
-			list.splice(i, 1);
-			i -= 1;
-		}
-	}
-};
-
-// ======================= Map ========================
-common.map = {};
-
-common.map.toArray = function(map) {
-	return $.map(map, function(unit) {
-		return unit;
-	});
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var common = {};
+
+common.template = function (str, list) {
+	$.each(list, function(key, value) {
+		var _regex = new RegExp("\\$\\{" + key + "\\}", "g");
+		str = str.replace(_regex, value);
+	});
+	return str;
+};
+
+common.getValueByPath = function (unit, path, defaultValue) {
+	if(unit === null || unit === undefined) throw "Unit or path can't be empty!";
+	if(path === "" || path === null || path === undefined) return unit;
+
+	path = path.replace(/\[(\d+)\]/g, ".$1").replace(/^\./, "").split(/\./);
+	$.each(path, function(i, path) {
+		unit = unit[path];
+		if(unit === null || unit === undefined) {
+			unit = null;
+			return false;
+		}
+	});
+	if(unit === null && defaultValue !== undefined) {
+		unit = defaultValue;
+	}
+	return unit;
+};
+
+common.setValueByPath = function(unit, path, value) {
+	if(!unit || typeof path !== "string" || path === "") throw "Unit or path can't be empty!";
+
+	var _inArray = false;
+	var _end = 0;
+	var _start = 0;
+	var _unit = unit;
+
+	function _nextPath(array) {
+		var _key = path.slice(_start, _end);
+		if(_inArray) {
+			_key = _key.slice(0, -1);
+		}
+		if(!_unit[_key]) {
+			if(array) {
+				_unit[_key] = [];
+			} else {
+				_unit[_key] = {};
+			}
+		}
+		_unit = _unit[_key];
+	}
+
+	for(; _end < path.length ; _end += 1) {
+		if(path[_end] === ".") {
+			_nextPath(false);
+			_start = _end + 1;
+			_inArray = false;
+		} else if(path[_end] === "[") {
+			_nextPath(true);
+			_start = _end + 1;
+			_inArray = true;
+		}
+	}
+
+	_unit[path.slice(_start, _inArray ? -1 : _end)] = value;
+
+	return unit;
+};
+
+common.parseJSON = function (str, defaultVal) {
+	try {
+		str = (str + "").trim();
+		if(Number(str).toString() === str) throw "Number format";
+		return JSON.parse(str);
+	} catch(err) {
+		if(defaultVal === undefined) {
+			console.warn("Can't parse JSON: " + str);
+		}
+	}
+	return defaultVal === undefined ? null : defaultVal;
+};
+
+common.isEmpty = function(val) {
+	if($.isArray(val)) {
+		return val.length === 0;
+	} else {
+		return val === null || val === undefined;
+	}
+};
+
+// ====================== Format ======================
+common.format = {};
+
+/*
+ * Format date to string. Support number, string, Date instance. Will auto convert time zone offset(Moment instance will keep time zone).
+ */
+common.format.date = function(val, type) {
+	if(val === undefined || val === null) return "";
+
+	if(typeof val === "number" || typeof val === "string" || val instanceof Date) {
+		val = app.time.offset(val);
+	}
+	switch(type) {
+	default:
+		return val.format("YYYY-MM-DD HH:mm:ss") + (val.utcOffset() === 0 ? '[UTC]' : '');
+	}
+};
+
+// ====================== Array =======================
+common.array = {};
+
+common.array.sum = function(list, path) {
+	var _sum = 0;
+	if(list) {
+		$.each(list, function(i, unit) {
+			var _val = common.getValueByPath(unit, path);
+			if(typeof _val === "number") {
+				_sum += _val;
+			}
+		});
+	}
+	return _sum;
+};
+
+common.array.max = function(list, path) {
+	var _max = null;
+	if(list) {
+		$.each(list, function(i, unit) {
+			var _val = common.getValueByPath(unit, path);
+			if(typeof _val === "number" && (_max === null || _max < _val)) {
+				_max = _val;
+			}
+		});
+	}
+	return _max;
+};
+
+common.array.bottom = function(list, path, count) {
+	var _list = list.slice();
+
+	_list.sort(function(a, b) {
+		var _a = common.getValueByPath(a, path, null);
+		var _b = common.getValueByPath(b, path, null);
+
+		if(_a === _b) return 0;
+		if(_a < _b || _a === null) {
+			return -1;
+		} else {
+			return 1;
+		}
+	});
+	return !count ? _list : _list.slice(0, count);
+};
+common.array.top = function(list, path, count) {
+	var _list = common.array.bottom(list, path);
+	_list.reverse();
+	return !count ? _list : _list.slice(0, count);
+};
+
+common.array.find = function(val, list, path, findAll) {
+	path = path || "";
+	var _list = $.grep(list, function(unit) {
+		return val === common.getValueByPath(unit, path);
+	});
+	return findAll ? _list : (_list.length === 0 ? null : _list[0]);
+};
+
+common.array.filter = function(val, list, path) {
+	return common.array.find(val, list, path, true);
+};
+
+common.array.count = function(list, val, path) {
+	if(arguments.length === 1) {
+		return list.length;
+	} else {
+		return common.array.find(val, list, path, true).length;
+	}
+};
+
+common.array.remove = function(val, list) {
+	for(var i = 0 ; i < list.length ; i += 1) {
+		if(list[i] === val) {
+			list.splice(i, 1);
+			i -= 1;
+		}
+	}
+};
+
+common.array.insert = function(val, list, index) {
+	list.splice(index, 0, val);
+};
+
+common.array.moveOffset = function(item, list, offset) {
+	var _index = $.inArray(item, list);
+	var _tgtPos = _index + offset;
+	if(_tgtPos < 0 || _tgtPos >= list.length) return;
+
+	common.array.remove(item, list);
+	common.array.insert(item, list, _index + offset);
+};
+
+// ======================= Map ========================
+common.map = {};
+
+common.map.toArray = function(map) {
+	return $.map(map, function(unit) {
+		return unit;
+	});
 };
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/eagle-webservice/src/main/webapp/app/public/js/ctrl/alertController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/alertController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/alertController.js
deleted file mode 100644
index 2399234..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/alertController.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// =============================================================
-// =                        Alert List                         =
-// =============================================================
-damControllers.controller('alertListCtrl', function(globalContent, Site, damContent, $scope, $routeParams, $interval, $timeout, Entities) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.pageSubTitle = Site.current().name;
-
-	var MAX_PAGESIZE = 10000;
-
-	// Initial load
-	$scope.dataSource = $routeParams.dataSource;
-
-	$scope.alertList = [];
-	$scope.alertList.ready = false;
-
-	// Load data
-	function _loadAlerts() {
-		if($scope.alertList._promise) {
-			$scope.alertList._promise.abort();
-		}
-
-		var _list = Entities.queryEntities("AlertService", {
-			site: Site.current().name,
-			dataSource: $scope.dataSource,
-			hostname: null,
-			_pageSize: MAX_PAGESIZE,
-			_duration: 1000 * 60 * 60 * 24 * 30,
-			__ETD: 1000 * 60 * 60 * 24
-		});
-		$scope.alertList._promise = _list._promise;
-		_list._promise.then(function() {
-			var index;
-
-			if($scope.alertList[0]) {
-				// List new alerts
-				for(index = 0 ; index < _list.length ; index += 1) {
-					var _alert = _list[index];
-					_alert.__new = true;
-					if(_alert.encodedRowkey === $scope.alertList[0].encodedRowkey) {
-						break;
-					}
-				}
-
-				if(index > 0) {
-					$scope.alertList.unshift.apply($scope.alertList, _list.slice(0, index));
-
-					// Clean up UI highlight
-					$timeout(function() {
-						$.each(_list, function(i, alert) {
-							delete alert.__new;
-						});
-					}, 100);
-				}
-			} else {
-				// List all alerts
-				$scope.alertList.push.apply($scope.alertList, _list);
-			}
-
-			$scope.alertList.ready = true;
-		});
-	}
-
-	_loadAlerts();
-	var _loadInterval = $interval(_loadAlerts, app.time.refreshInterval);
-	$scope.$on('$destroy',function(){
-		$interval.cancel(_loadInterval);
-	});
-});
-
-// =============================================================
-// =                       Alert Detail                        =
-// =============================================================
-damControllers.controller('alertDetailCtrl', function(globalContent, Site, damContent, $scope, $routeParams, Entities) {
-	'use strict';
-
-	globalContent.setConfig(damContent.config);
-	globalContent.pageTitle = "Alert Detail";
-	globalContent.navPath = ["Alert List", "Alert Detail"];
-	globalContent.lockSite = true;
-
-	$scope.common = common;
-
-	// Query policy
-	$scope.alertList = Entities.queryEntity("AlertService", $routeParams.encodedRowkey);
-	$scope.alertList._promise.then(function() {
-		if($scope.alertList.length === 0) {
-			$.dialog({
-				title: "OPS!",
-				content: "Alert not found!",
-			}, function() {
-				location.href = "#/dam/alertList";
-			});
-			return;
-		} else {
-			var alert = $scope.alertList[0];
-
-			$scope.alert = alert;
-			Site.current(Site.find($scope.alert.tags.site));
-			console.log($scope.alert);
-		}
-	});
-
-	// UI
-	$scope.getMessageTime = function(alert) {
-		var _time = common.getValueByPath(alert, "alertContext.properties.timestamp");
-		return Number(_time);
-	};
-});

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