kylin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From shaofeng...@apache.org
Subject [11/43] kylin git commit: KYLIN-1074 support load hive table from listed tree.
Date Fri, 04 Mar 2016 02:02:52 GMT
KYLIN-1074 support load hive table from listed tree.


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

Branch: refs/heads/helix-rebase
Commit: bc7d4f5846d52a17873738047e117e9410d17823
Parents: cf05409
Author: Jason <jiatuer@163.com>
Authored: Wed Mar 2 15:18:31 2016 +0800
Committer: Jason <jiatuer@163.com>
Committed: Wed Mar 2 15:18:55 2016 +0800

----------------------------------------------------------------------
 ...port-load-hive-table-from-listed-tree-.patch | 864 +++++++++++++++++++
 build/conf/kylin.properties                     |   2 +
 .../test_case_data/sandbox/kylin.properties     |   1 +
 pom.xml                                         |   1 +
 .../kylin/rest/controller/TableController.java  |  44 +
 .../apache/kylin/source/hive/HiveClient.java    |   8 +
 webapp/app/index.html                           |   1 +
 webapp/app/js/controllers/sourceMeta.js         | 185 +++-
 .../app/js/directives/angular-tree-control.js   | 363 ++++++++
 webapp/app/js/services/kylinProperties.js       |  12 +-
 webapp/app/js/services/tables.js                |   6 +-
 .../app/partials/tables/source_table_tree.html  |  26 +
 webapp/bower.json                               |   3 +-
 webapp/grunt.json                               |   1 -
 14 files changed, 1509 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/0001-KYLIN-1074-support-load-hive-table-from-listed-tree-.patch
----------------------------------------------------------------------
diff --git a/0001-KYLIN-1074-support-load-hive-table-from-listed-tree-.patch b/0001-KYLIN-1074-support-load-hive-table-from-listed-tree-.patch
new file mode 100644
index 0000000..31cc017
--- /dev/null
+++ b/0001-KYLIN-1074-support-load-hive-table-from-listed-tree-.patch
@@ -0,0 +1,864 @@
+From 1a79ef1aec557259f9611f5b3199c2e90400be77 Mon Sep 17 00:00:00 2001
+From: Jason <jiatuer@163.com>
+Date: Wed, 2 Mar 2016 14:40:19 +0800
+Subject: [PATCH] KYLIN-1074 support load hive table from listed tree, patch
+ from @nichunen
+
+---
+ build/conf/kylin.properties                        |   2 +
+ examples/test_case_data/sandbox/kylin.properties   |   1 +
+ pom.xml                                            |   2 +
+ .../kylin/rest/controller/TableController.java     |  44 +++
+ .../org/apache/kylin/source/hive/HiveClient.java   |   8 +
+ webapp/app/index.html                              |   1 +
+ webapp/app/js/controllers/sourceMeta.js            | 186 ++++++++++-
+ webapp/app/js/directives/angular-tree-control.js   | 363 +++++++++++++++++++++
+ webapp/app/js/services/kylinProperties.js          |  15 +-
+ webapp/app/js/services/tables.js                   |   7 +-
+ webapp/app/partials/tables/source_table_tree.html  |  26 ++
+ webapp/bower.json                                  |   3 +-
+ webapp/grunt.json                                  |   1 -
+ 13 files changed, 649 insertions(+), 10 deletions(-)
+ create mode 100644 webapp/app/js/directives/angular-tree-control.js
+
+diff --git a/build/conf/kylin.properties b/build/conf/kylin.properties
+index a4b8c3b..e8add7c 100644
+--- a/build/conf/kylin.properties
++++ b/build/conf/kylin.properties
+@@ -158,3 +158,5 @@ deploy.env=DEV
+ 
+ ###########################deprecated configs#######################
+ kylin.sandbox=true
++
++kylin.web.hive.limit=20
+\ No newline at end of file
+diff --git a/examples/test_case_data/sandbox/kylin.properties b/examples/test_case_data/sandbox/kylin.properties
+index 9451b78..1a74b80 100644
+--- a/examples/test_case_data/sandbox/kylin.properties
++++ b/examples/test_case_data/sandbox/kylin.properties
+@@ -131,3 +131,4 @@ kylin.web.contact_mail=
+ deploy.env=DEV
+ 
+ 
++kylin.web.hive.limit=20
+\ No newline at end of file
+diff --git a/pom.xml b/pom.xml
+index 9d9a54b..537693f 100644
+--- a/pom.xml
++++ b/pom.xml
+@@ -774,6 +774,8 @@
+                                 <!-- MIT license -->
+                                 <exclude>webapp/app/css/AdminLTE.css</exclude>
+                                 <exclude>webapp/app/js/directives/kylin_abn_tree_directive.js</exclude>
++                                <exclude>webapp/app/js/directives/angular-tree-control.js</exclude>
++
+ 
+                                 <!--configuration file -->
+                                 <exclude>webapp/app/routes.json</exclude>
+diff --git a/server/src/main/java/org/apache/kylin/rest/controller/TableController.java b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
+index 39af7db..ea5fdd4 100644
+--- a/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
++++ b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
+@@ -33,6 +33,7 @@ import org.apache.kylin.rest.request.CardinalityRequest;
+ import org.apache.kylin.rest.request.StreamingRequest;
+ import org.apache.kylin.rest.response.TableDescResponse;
+ import org.apache.kylin.rest.service.CubeService;
++import org.apache.kylin.source.hive.HiveClient;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.springframework.beans.factory.annotation.Autowired;
+@@ -205,6 +206,49 @@ public class TableController extends BasicController {
+         return descs;
+     }
+ 
++    /**
++     * Show all databases in Hive
++     *
++     * @return Hive databases list
++     * @throws IOException
++     */
++    @RequestMapping(value = "/hive", method = { RequestMethod.GET })
++    @ResponseBody
++    private static List<String> showHiveDatabases() throws IOException {
++        HiveClient hiveClient = new HiveClient();
++        List<String> results = null;
++
++        try {
++            results = hiveClient.getHiveDbNames();
++        } catch (Exception e) {
++            e.printStackTrace();
++            throw new IOException(e);
++        }
++        return results;
++    }
++
++    /**
++     * Show all tables in a Hive database
++     *
++     * @return Hive table list
++     * @throws IOException
++     */
++    @RequestMapping(value = "/hive/{database}", method = { RequestMethod.GET })
++    @ResponseBody
++    private static List<String> showHiveTables(@PathVariable String database) throws IOException {
++        HiveClient hiveClient = new HiveClient();
++        List<String> results = null;
++
++        try {
++            results = hiveClient.getHiveTableNames(database);
++        } catch (Exception e) {
++            e.printStackTrace();
++            throw new IOException(e);
++        }
++        return results;
++    }
++
++
+     public void setCubeService(CubeService cubeService) {
+         this.cubeMgmtService = cubeService;
+     }
+diff --git a/source-hive/src/main/java/org/apache/kylin/source/hive/HiveClient.java b/source-hive/src/main/java/org/apache/kylin/source/hive/HiveClient.java
+index 178889e..a99b304 100644
+--- a/source-hive/src/main/java/org/apache/kylin/source/hive/HiveClient.java
++++ b/source-hive/src/main/java/org/apache/kylin/source/hive/HiveClient.java
+@@ -132,6 +132,14 @@ public class HiveClient {
+         return getBasicStatForTable(new org.apache.hadoop.hive.ql.metadata.Table(table), StatsSetupConst.NUM_FILES);
+     }
+ 
++    public List<String> getHiveDbNames() throws Exception {
++        return getMetaStoreClient().getAllDatabases();
++    }
++
++    public List<String> getHiveTableNames(String database) throws Exception {
++        return getMetaStoreClient().getAllTables(database);
++    }
++
+     /**
+      * COPIED FROM org.apache.hadoop.hive.ql.stats.StatsUtil for backward compatibility
+      * 
+diff --git a/webapp/app/index.html b/webapp/app/index.html
+index 11ca283..b4eb9d7 100644
+--- a/webapp/app/index.html
++++ b/webapp/app/index.html
+@@ -113,6 +113,7 @@
+ <script src="js/filters/filter.js"></script>
+ <script src="js/directives/directives.js"></script>
+ <script src="js/directives/kylin_abn_tree_directive.js"></script>
++<script src="js/directives/angular-tree-control.js"></script>
+ <script src="js/factories/graph.js"></script>
+ <script src="js/services/cache.js"></script>
+ <script src="js/services/message.js"></script>
+diff --git a/webapp/app/js/controllers/sourceMeta.js b/webapp/app/js/controllers/sourceMeta.js
+index abdeeb8..c87d6ef 100755
+--- a/webapp/app/js/controllers/sourceMeta.js
++++ b/webapp/app/js/controllers/sourceMeta.js
+@@ -19,14 +19,14 @@
+ 'use strict';
+ 
+ KylinApp
+-  .controller('SourceMetaCtrl', function ($scope, $cacheFactory, $q, $window, $routeParams, CubeService, $modal, TableService, $route, loadingRequest, SweetAlert, tableConfig, TableModel,cubeConfig) {
++  .controller('SourceMetaCtrl', function ($scope, $cacheFactory, $q, $window, $routeParams, CubeService, $modal, TableService, $route, loadingRequest, SweetAlert, tableConfig, TableModel,cubeConfig,kylinConfig) {
+     var $httpDefaultCache = $cacheFactory.get('$http');
+     $scope.tableModel = TableModel;
+     $scope.tableModel.selectedSrcDb = [];
+     $scope.tableModel.selectedSrcTable = {};
+     $scope.window = 0.68 * $window.innerHeight;
+     $scope.tableConfig = tableConfig;
+-
++    $scope.kylinConfig = kylinConfig;
+ 
+     $scope.state = {
+       filterAttr: 'id', filterReverse: false, reverseColumn: 'id',
+@@ -100,13 +100,193 @@ KylinApp
+       });
+     };
+ 
+-    var ModalInstanceCtrl = function ($scope, $location, $modalInstance, tableNames, MessageService, projectName, scope) {
++    $scope.openTreeModal = function () {
++      $modal.open({
++        templateUrl: 'addHiveTableFromTree.html',
++        controller: ModalInstanceCtrl,
++        resolve: {
++          tableNames: function () {
++            return $scope.tableNames;
++          },
++          projectName:function(){
++            return  $scope.projectModel.selectedProject;
++          },
++          scope: function () {
++            return $scope;
++          }
++        }
++      });
++    };
++
++    var ModalInstanceCtrl = function ($scope, $location, $modalInstance, tableNames, MessageService, projectName, scope,kylinConfig) {
+       $scope.tableNames = "";
+       $scope.projectName = projectName;
+       $scope.cancel = function () {
+         $modalInstance.dismiss('cancel');
+       };
++
++      $scope.kylinConfig = kylinConfig;
++
++
++      $scope.treeOptions = {multiSelection: true};
++      $scope.selectedNodes = [];
++      $scope.hiveLimit =  kylinConfig.getHiveLimit();
++
++      $scope.loadHive = function () {
++        if($scope.hiveLoaded)
++          return;
++        TableService.showHiveDatabases({}, function (databases) {
++          $scope.dbNum = databases.length;
++          if (databases.length > 0) {
++            $scope.hiveMap = {};
++            for (var i = 0; i < databases.length; i++) {
++              var dbName = databases[i];
++              var hiveData = {"dbname":dbName,"tables":[],"expanded":false};
++              $scope.hive.push(hiveData);
++              $scope.hiveMap[dbName] = i;
++            }
++          }
++          $scope.hiveLoaded = true;
++          $scope.showMoreDatabases();
++        });
++      }
++
++      $scope.showMoreTables = function(hiveTables, node){
++        var shownTimes = parseInt(node.children.length / $scope.hiveLimit);
++        var from = $scope.hiveLimit * shownTimes;
++        var to = 0;
++        var hasMore = false;
++        if(from + $scope.hiveLimit > hiveTables.length) {
++          to = hiveTables.length - 1;
++        } else {
++          to = from + $scope.hiveLimit - 1;
++          hasMore = true;
++        }
++        if(!angular.isUndefined(node.children[from])){
++          node.children.pop();
++        }
++
++        for(var idx = from; idx <= to; idx++){
++          node.children.push({"label":node.label+'.'+hiveTables[idx],"id":idx-from+1,"children":[]});
++        }
++
++        if(hasMore){
++          var loading = {"label":"","id":65535,"children":[]};
++          node.children.push(loading);
++        }
++      }
++
++      $scope.showAllTables = function(hiveTables, node){
++        var shownTimes = parseInt(node.children.length / $scope.hiveLimit);
++        var from = $scope.hiveLimit * shownTimes;
++        var to = hiveTables.length - 1;
++        if(!angular.isUndefined(node.children[from])){
++          node.children.pop();
++        }
++        for(var idx = from; idx <= to; idx++){
++          node.children.push({"label":node.label+'.'+hiveTables[idx],"id":idx-from+1,"children":[]});
++        }
++      }
++
++      $scope.showMoreDatabases = function(){
++        var shownTimes = parseInt($scope.treedata.length / $scope.hiveLimit);
++        var from = $scope.hiveLimit * shownTimes;
++        var to = 0;
++        var hasMore = false;
++        if(from + $scope.hiveLimit > $scope.hive.length) {
++          to = $scope.hive.length - 1;
++        } else {
++          to = from + $scope.hiveLimit - 1;
++          hasMore = true;
++        }
++        if(!angular.isUndefined($scope.treedata[from])){
++          $scope.treedata.pop();
++        }
++
++        for(var idx = from; idx <= to; idx++){
++          var children = [];
++          var loading = {"label":"","id":0,"children":[]};
++          children.push(loading);
++          $scope.treedata.push({"label":$scope.hive[idx].dbname,"id":idx+1,"children":children,"expanded":false});
++        }
++
++        if(hasMore){
++          var loading = {"label":"","id":65535,"children":[0]};
++          $scope.treedata.push(loading);
++        }
++      }
++
++      $scope.showAllDatabases = function(){
++        var shownTimes = parseInt($scope.treedata.length / $scope.hiveLimit);
++        var from = $scope.hiveLimit * shownTimes;
++        var to = $scope.hive.length - 1;
++
++        if(!angular.isUndefined($scope.treedata[from])){
++          $scope.treedata.pop();
++        }
++
++        for(var idx = from; idx <= to; idx++){
++          var children = [];
++          var loading = {"label":"","id":0,"children":[]};
++          children.push(loading);
++          $scope.treedata.push({"label":$scope.hive[idx].dbname,"id":idx+1,"children":children,"expanded":false});
++        }
++      }
++
++      $scope.showMoreClicked = function($parentNode){
++        if($parentNode == null){
++          $scope.showMoreDatabases();
++        } else {
++          $scope.showMoreTables($scope.hive[$scope.hiveMap[$parentNode.label]].tables,$parentNode);
++        }
++      }
++
++      $scope.showAllClicked = function($parentNode){
++        if($parentNode == null){
++          $scope.showAllDatabases();
++        } else {
++          $scope.showAllTables($scope.hive[$scope.hiveMap[$parentNode.label]].tables,$parentNode);
++        }
++      }
++
++      $scope.showToggle = function(node) {
++        if(node.expanded == false){
++          TableService.showHiveTables({"database": node.label},function (hive_tables){
++            var tables = [];
++            for (var i = 0; i < hive_tables.length; i++) {
++              tables.push(hive_tables[i]);
++            }
++            $scope.hive[$scope.hiveMap[node.label]].tables = tables;
++            $scope.showMoreTables(tables,node);
++            node.expanded = true;
++          });
++        }
++      }
++
++      $scope.showSelected = function(node) {
++
++      }
++
++      if(angular.isUndefined($scope.hive) || angular.isUndefined($scope.hiveLoaded) || angular.isUndefined($scope.treedata) ){
++        $scope.hive = [];
++        $scope.hiveLoaded = false;
++        $scope.treedata = [];
++        $scope.loadHive();
++      }
++
++
++
++
+       $scope.add = function () {
++
++        if($scope.tableNames.length === 0 && $scope.selectedNodes.length > 0) {
++          for(var i = 0; i <  $scope.selectedNodes.length; i++){
++            if($scope.selectedNodes[i].label.indexOf(".") >= 0){
++              $scope.tableNames += ($scope.selectedNodes[i].label) += ',';
++            }
++          }
++        }
++
+         if ($scope.tableNames.trim() === "") {
+           SweetAlert.swal('', 'Please input table(s) you want to synchronize.', 'info');
+           return;
+diff --git a/webapp/app/js/directives/angular-tree-control.js b/webapp/app/js/directives/angular-tree-control.js
+new file mode 100644
+index 0000000..6fca987
+--- /dev/null
++++ b/webapp/app/js/directives/angular-tree-control.js
+@@ -0,0 +1,363 @@
++/*
++ * The MIT License (MIT)
++ *
++ * Copyright (c) 2013 Steve
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy of
++ * this software and associated documentation files (the "Software"), to deal in
++ * the Software without restriction, including without limitation the rights to
++ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
++ * the Software, and to permit persons to whom the Software is furnished to do so,
++ *   subject to the following conditions:
++ *
++ *   The above copyright notice and this permission notice shall be included in all
++ * copies or substantial portions of the Software.
++ *
++ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
++ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
++ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
++ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
++ */
++
++(function ( angular ) {
++  'use strict';
++
++  angular.module( 'treeControl', [] )
++    .directive( 'treecontrol', ['$compile', function( $compile ) {
++      /**
++       * @param cssClass - the css class
++       * @param addClassProperty - should we wrap the class name with class=""
++       */
++      function classIfDefined(cssClass, addClassProperty) {
++        if (cssClass) {
++          if (addClassProperty)
++            return 'class="' + cssClass + '"';
++          else
++            return cssClass;
++        }
++        else
++          return "";
++      }
++
++      function ensureDefault(obj, prop, value) {
++        if (!obj.hasOwnProperty(prop))
++          obj[prop] = value;
++      }
++
++      return {
++        restrict: 'EA',
++        require: "treecontrol",
++        transclude: true,
++        scope: {
++          treeModel: "=",
++          selectedNode: "=?",
++          selectedNodes: "=?",
++          expandedNodes: "=?",
++          onSelection: "&",
++          onNodeToggle: "&",
++          options: "=?",
++          orderBy: "@",
++          reverseOrder: "@",
++          filterExpression: "=?",
++          filterComparator: "=?",
++          onDblclick: "&"
++        },
++        controller: ['$scope', function( $scope ) {
++
++          function defaultIsLeaf(node) {
++            return !node[$scope.options.nodeChildren] || node[$scope.options.nodeChildren].length === 0;
++          }
++
++          function shallowCopy(src, dst) {
++            if (angular.isArray(src)) {
++              dst = dst || [];
++
++              for ( var i = 0; i < src.length; i++) {
++                dst[i] = src[i];
++              }
++            } else if (angular.isObject(src)) {
++              dst = dst || {};
++
++              for (var key in src) {
++                if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
++                  dst[key] = src[key];
++                }
++              }
++            }
++
++            return dst || src;
++          }
++          function defaultEquality(a, b) {
++            if (a === undefined || b === undefined)
++              return false;
++            a = shallowCopy(a);
++            a[$scope.options.nodeChildren] = [];
++            b = shallowCopy(b);
++            b[$scope.options.nodeChildren] = [];
++            return angular.equals(a, b);
++          }
++
++          $scope.options = $scope.options || {};
++          ensureDefault($scope.options, "multiSelection", false);
++          ensureDefault($scope.options, "nodeChildren", "children");
++          ensureDefault($scope.options, "dirSelectable", "true");
++          ensureDefault($scope.options, "injectClasses", {});
++          ensureDefault($scope.options.injectClasses, "ul", "");
++          ensureDefault($scope.options.injectClasses, "li", "");
++          ensureDefault($scope.options.injectClasses, "liSelected", "");
++          ensureDefault($scope.options.injectClasses, "iExpanded", "");
++          ensureDefault($scope.options.injectClasses, "iCollapsed", "");
++          ensureDefault($scope.options.injectClasses, "iLeaf", "");
++          ensureDefault($scope.options.injectClasses, "label", "");
++          ensureDefault($scope.options.injectClasses, "labelSelected", "");
++          ensureDefault($scope.options, "equality", defaultEquality);
++          ensureDefault($scope.options, "isLeaf", defaultIsLeaf);
++
++          $scope.selectedNodes = $scope.selectedNodes || [];
++          $scope.expandedNodes = $scope.expandedNodes || [];
++          $scope.expandedNodesMap = {};
++          for (var i=0; i < $scope.expandedNodes.length; i++) {
++            $scope.expandedNodesMap[""+i] = $scope.expandedNodes[i];
++          }
++          $scope.parentScopeOfTree = $scope.$parent;
++
++
++          function isSelectedNode(node) {
++            if (!$scope.options.multiSelection && ($scope.options.equality(node, $scope.selectedNode)))
++              return true;
++            else if ($scope.options.multiSelection && $scope.selectedNodes) {
++              for (var i = 0; (i < $scope.selectedNodes.length); i++) {
++                if ($scope.options.equality(node, $scope.selectedNodes[i])) {
++                  return true;
++                }
++              }
++              return false;
++            }
++          }
++
++          $scope.headClass = function(node) {
++            var liSelectionClass = classIfDefined($scope.options.injectClasses.liSelected, false);
++            var injectSelectionClass = "";
++            if (liSelectionClass && isSelectedNode(node))
++              injectSelectionClass = " " + liSelectionClass;
++            if ($scope.options.isLeaf(node))
++              return "tree-leaf" + injectSelectionClass;
++            if ($scope.expandedNodesMap[this.$id])
++              return "tree-expanded" + injectSelectionClass;
++            else
++              return "tree-collapsed" + injectSelectionClass;
++          };
++
++          $scope.iBranchClass = function() {
++            if ($scope.expandedNodesMap[this.$id])
++              return classIfDefined($scope.options.injectClasses.iExpanded);
++            else
++              return classIfDefined($scope.options.injectClasses.iCollapsed);
++          };
++
++          $scope.nodeExpanded = function() {
++            return !!$scope.expandedNodesMap[this.$id];
++          };
++
++          $scope.selectNodeHead = function() {
++            var expanding = $scope.expandedNodesMap[this.$id] === undefined;
++            $scope.expandedNodesMap[this.$id] = (expanding ? this.node : undefined);
++            if (expanding) {
++              $scope.expandedNodes.push(this.node);
++            }
++            else {
++              var index;
++              for (var i=0; (i < $scope.expandedNodes.length) && !index; i++) {
++                if ($scope.options.equality($scope.expandedNodes[i], this.node)) {
++                  index = i;
++                }
++              }
++              if (index != undefined)
++                $scope.expandedNodes.splice(index, 1);
++            }
++            if ($scope.onNodeToggle)
++              $scope.onNodeToggle({node: this.node, expanded: expanding});
++          };
++
++          $scope.selectNodeLabel = function( selectedNode ){
++            if(selectedNode[$scope.options.nodeChildren] && selectedNode[$scope.options.nodeChildren].length > 0){
++              this.selectNodeHead();
++            }
++            if($scope.options.dirSelectable || !(selectedNode[$scope.options.nodeChildren] && selectedNode[$scope.options.nodeChildren].length > 0) )
++             {
++              var selected = false;
++              if ($scope.options.multiSelection) {
++                var pos = $scope.selectedNodes.indexOf(selectedNode);
++                if (pos === -1) {
++                  $scope.selectedNodes.push(selectedNode);
++                  selected = true;
++                } else {
++                  $scope.selectedNodes.splice(pos, 1);
++                }
++              } else {
++                if ($scope.selectedNode != selectedNode) {
++                  $scope.selectedNode = selectedNode;
++                  selected = true;
++                }
++                else {
++                  $scope.selectedNode = undefined;
++                }
++              }
++              if ($scope.onSelection)
++                $scope.onSelection({node: selectedNode, selected: selected});
++            }
++          };
++
++
++          $scope.dblClickNode = function(selectedNode){
++            if($scope.onDblclick!=null){
++              $scope.onDblclick({node:selectedNode});
++            }
++          }
++
++          $scope.selectedClass = function() {
++            var isThisNodeSelected = isSelectedNode(this.node);
++            var labelSelectionClass = classIfDefined($scope.options.injectClasses.labelSelected, false);
++            var injectSelectionClass = "";
++            if (labelSelectionClass && isThisNodeSelected)
++              injectSelectionClass = " " + labelSelectionClass;
++
++            return isThisNodeSelected?"tree-selected" + injectSelectionClass:"";
++          };
++
++          //tree template
++          var orderBy = $scope.orderBy ? ' | orderBy:orderBy:reverseOrder' : '';
++          var template =
++            '<ul '+classIfDefined($scope.options.injectClasses.ul, true)+'>' +
++            '<li ng-repeat="node in node.' + $scope.options.nodeChildren + ' | filter:filterExpression:filterComparator ' + orderBy + '" ng-class="headClass(node)" '+classIfDefined($scope.options.injectClasses.li, true)+'>' +
++            '<i class="tree-branch-head" ng-class="iBranchClass()" ng-click="selectNodeHead(node)"></i>' +
++            '<i class="tree-leaf-head '+classIfDefined($scope.options.injectClasses.iLeaf, false)+'"></i>' +
++            '<div class="tree-label '+classIfDefined($scope.options.injectClasses.label, false)+'" ng-class="selectedClass()" ng-click="selectNodeLabel(node)" ng-dblclick="dblClickNode(node)" tree-transclude></div>' +
++            '<treeitem ng-if="nodeExpanded()"></treeitem>' +
++            '</li>' +
++            '</ul>';
++
++          this.template = $compile(template);
++        }],
++        compile: function(element, attrs, childTranscludeFn) {
++          return function ( scope, element, attrs, treemodelCntr ) {
++
++            scope.$watch("treeModel", function updateNodeOnRootScope(newValue) {
++              if (angular.isArray(newValue)) {
++                if (angular.isDefined(scope.node) && angular.equals(scope.node[scope.options.nodeChildren], newValue))
++                  return;
++                scope.node = {};
++                scope.synteticRoot = scope.node;
++                scope.node[scope.options.nodeChildren] = newValue;
++              }
++              else {
++                if (angular.equals(scope.node, newValue))
++                  return;
++                scope.node = newValue;
++              }
++            });
++
++            scope.$watchCollection('expandedNodes', function(newValue) {
++              var notFoundIds = 0;
++              var newExpandedNodesMap = {};
++              var $liElements = element.find('li');
++              var existingScopes = [];
++              // find all nodes visible on the tree and the scope $id of the scopes including them
++              angular.forEach($liElements, function(liElement) {
++                var $liElement = angular.element(liElement);
++                var liScope = $liElement.scope();
++                existingScopes.push(liScope);
++              });
++              // iterate over the newValue, the new expanded nodes, and for each find it in the existingNodesAndScopes
++              // if found, add the mapping $id -> node into newExpandedNodesMap
++              // if not found, add the mapping num -> node into newExpandedNodesMap
++              angular.forEach(newValue, function(newExNode) {
++                var found = false;
++                for (var i=0; (i < existingScopes.length) && !found; i++) {
++                  var existingScope = existingScopes[i];
++                  if (scope.options.equality(newExNode, existingScope.node)) {
++                    newExpandedNodesMap[existingScope.$id] = existingScope.node;
++                    found = true;
++                  }
++                }
++                if (!found)
++                  newExpandedNodesMap[notFoundIds++] = newExNode;
++              });
++              scope.expandedNodesMap = newExpandedNodesMap;
++            });
++
++//                        scope.$watch('expandedNodesMap', function(newValue) {
++//
++//                        });
++
++            //Rendering template for a root node
++            treemodelCntr.template( scope, function(clone) {
++              element.html('').append( clone );
++            });
++            // save the transclude function from compile (which is not bound to a scope as apposed to the one from link)
++            // we can fix this to work with the link transclude function with angular 1.2.6. as for angular 1.2.0 we need
++            // to keep using the compile function
++            scope.$treeTransclude = childTranscludeFn;
++          }
++        }
++      };
++    }])
++    .directive("treeitem", function() {
++      return {
++        restrict: 'E',
++        require: "^treecontrol",
++        link: function( scope, element, attrs, treemodelCntr) {
++          // Rendering template for the current node
++          treemodelCntr.template(scope, function(clone) {
++            element.html('').append(clone);
++          });
++        }
++      }
++    })
++    .directive("treeTransclude", function() {
++      return {
++        link: function(scope, element, attrs, controller) {
++          if (!scope.options.isLeaf(scope.node)) {
++            angular.forEach(scope.expandedNodesMap, function (node, id) {
++              if (scope.options.equality(node, scope.node)) {
++                scope.expandedNodesMap[scope.$id] = scope.node;
++                scope.expandedNodesMap[id] = undefined;
++              }
++            });
++          }
++          if (!scope.options.multiSelection && scope.options.equality(scope.node, scope.selectedNode)) {
++            scope.selectedNode = scope.node;
++          } else if (scope.options.multiSelection) {
++            var newSelectedNodes = [];
++            for (var i = 0; (i < scope.selectedNodes.length); i++) {
++              if (scope.options.equality(scope.node, scope.selectedNodes[i])) {
++                newSelectedNodes.push(scope.node);
++              }
++            }
++            scope.selectedNodes = newSelectedNodes;
++          }
++
++          // create a scope for the transclusion, whos parent is the parent of the tree control
++          scope.transcludeScope = scope.parentScopeOfTree.$new();
++          scope.transcludeScope.node = scope.node;
++          scope.transcludeScope.$parentNode = (scope.$parent.node === scope.synteticRoot)?null:scope.$parent.node;
++          scope.transcludeScope.$index = scope.$index;
++          scope.transcludeScope.$first = scope.$first;
++          scope.transcludeScope.$middle = scope.$middle;
++          scope.transcludeScope.$last = scope.$last;
++          scope.transcludeScope.$odd = scope.$odd;
++          scope.transcludeScope.$even = scope.$even;
++          scope.$on('$destroy', function() {
++            scope.transcludeScope.$destroy();
++          });
++
++          scope.$treeTransclude(scope.transcludeScope, function(clone) {
++            element.empty();
++            element.append(clone);
++          });
++        }
++      }
++    });
++})( angular );
+diff --git a/webapp/app/js/services/kylinProperties.js b/webapp/app/js/services/kylinProperties.js
+index a03403b..b1f04c0 100644
+--- a/webapp/app/js/services/kylinProperties.js
++++ b/webapp/app/js/services/kylinProperties.js
+@@ -20,6 +20,7 @@ KylinApp.service('kylinConfig', function (AdminService, $log) {
+   var _config;
+   var timezone;
+   var deployEnv;
++  var hiveLimit;
+ 
+ 
+   this.init = function () {
+@@ -56,12 +57,22 @@ KylinApp.service('kylinConfig', function (AdminService, $log) {
+   }
+ 
+   this.getDeployEnv = function () {
++    this.deployEnv = this.getProperty("deploy.env");
+     if (!this.deployEnv) {
+-      this.deployEnv = this.getProperty("deploy.env").trim();
++      return "DEV";
+     }
+-    return this.deployEnv.toUpperCase();
++    return this.deployEnv.toUpperCase().trim();
+   }
+ 
++  this.getHiveLimit = function () {
++    this.hiveLimit = this.getProperty("kylin.web.hive.limit");
++    if (!this.hiveLimit) {
++      return 20;
++    }
++    return this.hiveLimit;
++  }
++
++
+   //fill config info for Config from backend
+   this.initWebConfigInfo = function () {
+ 
+diff --git a/webapp/app/js/services/tables.js b/webapp/app/js/services/tables.js
+index 3b5e9f4..9b2d376 100755
+--- a/webapp/app/js/services/tables.js
++++ b/webapp/app/js/services/tables.js
+@@ -17,13 +17,14 @@
+  */
+ 
+ KylinApp.factory('TableService', ['$resource', function ($resource, config) {
+-  return $resource(Config.service.url + 'tables/:tableName/:action', {}, {
++  return $resource(Config.service.url + 'tables/:tableName/:action/:database', {}, {
+     list: {method: 'GET', params: {}, cache: true, isArray: true},
+     get: {method: 'GET', params: {}, isArray: false},
+     getExd: {method: 'GET', params: {action: 'exd-map'}, isArray: false},
+     reload: {method: 'PUT', params: {action: 'reload'}, isArray: false},
+     loadHiveTable: {method: 'POST', params: {}, isArray: false},
+     addStreamingSrc: {method: 'POST', params: {action:'addStreamingSrc'}, isArray: false},
+-    genCardinality: {method: 'PUT', params: {action: 'cardinality'}, isArray: false}
+-  });
++    genCardinality: {method: 'PUT', params: {action: 'cardinality'}, isArray: false},
++    showHiveDatabases: {method: 'GET', params: {action:'hive'}, cache: true, isArray: true},
++    showHiveTables: {method: 'GET', params: {action:'hive'}, cache: true, isArray: true}  });
+ }]);
+diff --git a/webapp/app/partials/tables/source_table_tree.html b/webapp/app/partials/tables/source_table_tree.html
+index 767eb43..c091dca 100755
+--- a/webapp/app/partials/tables/source_table_tree.html
++++ b/webapp/app/partials/tables/source_table_tree.html
+@@ -26,6 +26,7 @@
+         <div class="col-xs-5" style="padding-left: 0px;margin-top: 20px;">
+             <div class="pull-right">
+                 <a class="btn btn-xs btn-primary" tooltip="Load Hive Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openModal()"><i class="fa fa-download"></i></a>
++                <a class="btn btn-xs btn-info" tooltip="Load Hive Table From Tree"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openTreeModal()"><i class="fa fa-download"></i></a>
+                 <a class="btn btn-xs btn-primary" tooltip="Add Streaming Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openStreamingSourceModal()"><i class="fa fa-area-chart"></i></a>
+             </div>
+         </div>
+@@ -47,3 +48,28 @@
+ </div>
+ 
+ <div ng-include="'partials/tables/table_load.html'"></div>
++
++<script type="text/ng-template" id="addHiveTableFromTree.html">
++  <div class="modal-header"><button class="close" type="button" data-dismiss="modal" ng-click="cancel()">×</button>
++    <h4>Load Hive Table Metadata From Tree</h4>
++  </div>
++  <div class="modal-body">
++    <span><strong>Project: </strong>{{ $parent.projectName!=null?$parent.projectName:'NULL'}}</span>
++    <div class="form-group searchBox">
++      <input type="text" placeholder="Filter ..." class="nav-search-input" ng-model="predicate" />
++    </div>
++    <loading ng-if="!hiveLoaded" text="Loading Databases..."></loading>
++    <treecontrol class="tree-light check" tree-model="treedata" selected-nodes="selectedNodes" filter-expression="predicate" on-selection="showSelected(node)" on-node-toggle="showToggle(node)" options="treeOptions">
++      <div ng-if="node.label==''&&node.id==0"><img src="image/ajax-loader.gif">Loading Tables...</div>
++      <button class="btn btn-xs btn-primary" ng-if="node.label==''&&node.id==65535" ng-click="showMoreClicked($parentNode)">Show More</button>
++      <button class="btn btn-xs btn-primary" ng-if="node.label==''&&node.id==65535" ng-click="showAllClicked($parentNode)">Show All</button>
++      {{node.label}}
++    </treecontrol>
++  </div>
++
++  <div class="modal-footer">
++    <button class="btn btn-primary" ng-click="add()">Sync</button>
++    <button class="btn btn-primary" ng-click="cancel()">Cancel</button>
++  </div>
++
++</script>
+diff --git a/webapp/bower.json b/webapp/bower.json
+index 41144f9..bba4a52 100755
+--- a/webapp/bower.json
++++ b/webapp/bower.json
+@@ -32,7 +32,8 @@
+     "bootstrap-sweetalert": "~0.4.3",
+     "angular-toggle-switch":"1.3.0",
+     "angular-ui-select": "0.13.2",
+-    "angular-sanitize": "1.2.18"
++    "angular-sanitize": "1.2.18",
++    "angular-tree-control": "0.2.8"
+   },
+   "devDependencies": {
+     "less.js": "~1.4.0",
+diff --git a/webapp/grunt.json b/webapp/grunt.json
+index 3219b5e..86ad1dc 100755
+--- a/webapp/grunt.json
++++ b/webapp/grunt.json
+@@ -19,7 +19,6 @@
+                 "app/components/angularLocalStorage/src/angularLocalStorage.js",
+                 "app/components/angular-base64/angular-base64.min.js",
+                 "app/components/ng-grid/build/ng-grid.js",
+-                "app/components/angular-tree-control/angular-tree-control.js",
+                 "app/components/ace-builds/src-min-noconflict/ace.js",
+                 "app/components/ace-builds/src-min-noconflict/ext-language_tools.js",
+                 "app/components/ace-builds/src-min-noconflict/mode-json.js",
+-- 
+2.5.4 (Apple Git-61)
+

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/build/conf/kylin.properties
----------------------------------------------------------------------
diff --git a/build/conf/kylin.properties b/build/conf/kylin.properties
index 5532339..78a564d 100644
--- a/build/conf/kylin.properties
+++ b/build/conf/kylin.properties
@@ -148,3 +148,5 @@ deploy.env=DEV
 
 ###########################deprecated configs#######################
 kylin.sandbox=true
+
+ kylin.web.hive.limit=20
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/examples/test_case_data/sandbox/kylin.properties
----------------------------------------------------------------------
diff --git a/examples/test_case_data/sandbox/kylin.properties b/examples/test_case_data/sandbox/kylin.properties
index 0c68a7e..7c9919b 100644
--- a/examples/test_case_data/sandbox/kylin.properties
+++ b/examples/test_case_data/sandbox/kylin.properties
@@ -116,4 +116,5 @@ kylin.web.contact_mail=
 #env DEV|QA|PROD
 deploy.env=DEV
 
+ kylin.web.hive.limit=20
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 42a0c6d..2e42841 100644
--- a/pom.xml
+++ b/pom.xml
@@ -781,6 +781,7 @@
                                 <!-- MIT license -->
                                 <exclude>webapp/app/css/AdminLTE.css</exclude>
                                 <exclude>webapp/app/js/directives/kylin_abn_tree_directive.js</exclude>
+                                <exclude>webapp/app/js/directives/angular-tree-control.js</exclude>
 
                                 <!--configuration file -->
                                 <exclude>webapp/app/routes.json</exclude>

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/kylin/rest/controller/TableController.java b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
index 98e8d58..bd04ad8 100644
--- a/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
+++ b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
@@ -36,6 +36,7 @@ import org.apache.kylin.rest.response.TableDescResponse;
 import org.apache.kylin.rest.service.CubeService;
 import org.apache.kylin.rest.service.ModelService;
 import org.apache.kylin.rest.service.ProjectService;
+import org.apache.kylin.source.hive.HiveClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -258,6 +259,49 @@ public class TableController extends BasicController {
         return descs;
     }
 
+
+    /**
+     * Show all databases in Hive
+     *
+     * @return Hive databases list
+     * @throws IOException
+     */
+    @RequestMapping(value = "/hive", method = { RequestMethod.GET })
+    @ResponseBody
+    private static List<String> showHiveDatabases() throws IOException {
+        HiveClient hiveClient = new HiveClient();
+        List<String> results = null;
+
+        try {
+            results = hiveClient.getHiveDbNames();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new IOException(e);
+        }
+        return results;
+    }
+
+    /**
+     * Show all tables in a Hive database
+     *
+     * @return Hive table list
+     * @throws IOException
+     */
+    @RequestMapping(value = "/hive/{database}", method = { RequestMethod.GET })
+    @ResponseBody
+    private static List<String> showHiveTables(@PathVariable String database) throws IOException {
+        HiveClient hiveClient = new HiveClient();
+        List<String> results = null;
+
+        try {
+            results = hiveClient.getHiveTableNames(database);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new IOException(e);
+        }
+        return results;
+    }
+
     public void setCubeService(CubeService cubeService) {
         this.cubeMgmtService = cubeService;
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/source-hive/src/main/java/org/apache/kylin/source/hive/HiveClient.java
----------------------------------------------------------------------
diff --git a/source-hive/src/main/java/org/apache/kylin/source/hive/HiveClient.java b/source-hive/src/main/java/org/apache/kylin/source/hive/HiveClient.java
index 178889e..a99b304 100644
--- a/source-hive/src/main/java/org/apache/kylin/source/hive/HiveClient.java
+++ b/source-hive/src/main/java/org/apache/kylin/source/hive/HiveClient.java
@@ -132,6 +132,14 @@ public class HiveClient {
         return getBasicStatForTable(new org.apache.hadoop.hive.ql.metadata.Table(table), StatsSetupConst.NUM_FILES);
     }
 
+    public List<String> getHiveDbNames() throws Exception {
+        return getMetaStoreClient().getAllDatabases();
+    }
+
+    public List<String> getHiveTableNames(String database) throws Exception {
+        return getMetaStoreClient().getAllTables(database);
+    }
+
     /**
      * COPIED FROM org.apache.hadoop.hive.ql.stats.StatsUtil for backward compatibility
      * 

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/webapp/app/index.html
----------------------------------------------------------------------
diff --git a/webapp/app/index.html b/webapp/app/index.html
index 11ca283..b4eb9d7 100644
--- a/webapp/app/index.html
+++ b/webapp/app/index.html
@@ -113,6 +113,7 @@
 <script src="js/filters/filter.js"></script>
 <script src="js/directives/directives.js"></script>
 <script src="js/directives/kylin_abn_tree_directive.js"></script>
+<script src="js/directives/angular-tree-control.js"></script>
 <script src="js/factories/graph.js"></script>
 <script src="js/services/cache.js"></script>
 <script src="js/services/message.js"></script>

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/webapp/app/js/controllers/sourceMeta.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/sourceMeta.js b/webapp/app/js/controllers/sourceMeta.js
index cbd9f52..69f1a44 100755
--- a/webapp/app/js/controllers/sourceMeta.js
+++ b/webapp/app/js/controllers/sourceMeta.js
@@ -100,6 +100,24 @@ KylinApp
       });
     };
 
+    $scope.openTreeModal = function () {
+      $modal.open({
+        templateUrl: 'addHiveTableFromTree.html',
+        controller: ModalInstanceCtrl,
+        resolve: {
+          tableNames: function () {
+            return $scope.tableNames;
+          },
+          projectName:function(){
+            return  $scope.projectModel.selectedProject;
+          },
+          scope: function () {
+            return $scope;
+          }
+        }
+      });
+    };
+
     $scope.openUnLoadModal = function () {
       $modal.open({
         templateUrl: 'removeHiveTable.html',
@@ -119,13 +137,175 @@ KylinApp
       });
     };
 
-    var ModalInstanceCtrl = function ($scope, $location, $modalInstance, tableNames, MessageService, projectName, scope) {
+    var ModalInstanceCtrl = function ($scope, $location, $modalInstance, tableNames, MessageService, projectName, scope,kylinConfig) {
       $scope.tableNames = "";
       $scope.projectName = projectName;
       $scope.cancel = function () {
         $modalInstance.dismiss('cancel');
       };
+
+      $scope.kylinConfig = kylinConfig;
+
+
+      $scope.treeOptions = {multiSelection: true};
+      $scope.selectedNodes = [];
+      $scope.hiveLimit =  kylinConfig.getHiveLimit();
+
+      $scope.loadHive = function () {
+        if($scope.hiveLoaded)
+          return;
+        TableService.showHiveDatabases({}, function (databases) {
+          $scope.dbNum = databases.length;
+          if (databases.length > 0) {
+            $scope.hiveMap = {};
+            for (var i = 0; i < databases.length; i++) {
+              var dbName = databases[i];
+              var hiveData = {"dbname":dbName,"tables":[],"expanded":false};
+              $scope.hive.push(hiveData);
+              $scope.hiveMap[dbName] = i;
+            }
+          }
+          $scope.hiveLoaded = true;
+          $scope.showMoreDatabases();
+        });
+      }
+
+      $scope.showMoreTables = function(hiveTables, node){
+        var shownTimes = parseInt(node.children.length / $scope.hiveLimit);
+        var from = $scope.hiveLimit * shownTimes;
+        var to = 0;
+        var hasMore = false;
+        if(from + $scope.hiveLimit > hiveTables.length) {
+          to = hiveTables.length - 1;
+        } else {
+          to = from + $scope.hiveLimit - 1;
+          hasMore = true;
+        }
+        if(!angular.isUndefined(node.children[from])){
+          node.children.pop();
+        }
+
+        for(var idx = from; idx <= to; idx++){
+          node.children.push({"label":node.label+'.'+hiveTables[idx],"id":idx-from+1,"children":[]});
+        }
+
+        if(hasMore){
+          var loading = {"label":"","id":65535,"children":[]};
+          node.children.push(loading);
+        }
+      }
+
+      $scope.showAllTables = function(hiveTables, node){
+        var shownTimes = parseInt(node.children.length / $scope.hiveLimit);
+        var from = $scope.hiveLimit * shownTimes;
+        var to = hiveTables.length - 1;
+        if(!angular.isUndefined(node.children[from])){
+          node.children.pop();
+        }
+        for(var idx = from; idx <= to; idx++){
+          node.children.push({"label":node.label+'.'+hiveTables[idx],"id":idx-from+1,"children":[]});
+        }
+      }
+
+      $scope.showMoreDatabases = function(){
+        var shownTimes = parseInt($scope.treedata.length / $scope.hiveLimit);
+        var from = $scope.hiveLimit * shownTimes;
+        var to = 0;
+        var hasMore = false;
+        if(from + $scope.hiveLimit > $scope.hive.length) {
+          to = $scope.hive.length - 1;
+        } else {
+          to = from + $scope.hiveLimit - 1;
+          hasMore = true;
+        }
+        if(!angular.isUndefined($scope.treedata[from])){
+          $scope.treedata.pop();
+        }
+
+        for(var idx = from; idx <= to; idx++){
+          var children = [];
+          var loading = {"label":"","id":0,"children":[]};
+          children.push(loading);
+          $scope.treedata.push({"label":$scope.hive[idx].dbname,"id":idx+1,"children":children,"expanded":false});
+        }
+
+        if(hasMore){
+          var loading = {"label":"","id":65535,"children":[0]};
+          $scope.treedata.push(loading);
+        }
+      }
+
+      $scope.showAllDatabases = function(){
+        var shownTimes = parseInt($scope.treedata.length / $scope.hiveLimit);
+        var from = $scope.hiveLimit * shownTimes;
+        var to = $scope.hive.length - 1;
+
+        if(!angular.isUndefined($scope.treedata[from])){
+          $scope.treedata.pop();
+        }
+
+        for(var idx = from; idx <= to; idx++){
+          var children = [];
+          var loading = {"label":"","id":0,"children":[]};
+          children.push(loading);
+          $scope.treedata.push({"label":$scope.hive[idx].dbname,"id":idx+1,"children":children,"expanded":false});
+        }
+      }
+
+      $scope.showMoreClicked = function($parentNode){
+        if($parentNode == null){
+          $scope.showMoreDatabases();
+        } else {
+          $scope.showMoreTables($scope.hive[$scope.hiveMap[$parentNode.label]].tables,$parentNode);
+        }
+      }
+
+      $scope.showAllClicked = function($parentNode){
+        if($parentNode == null){
+          $scope.showAllDatabases();
+        } else {
+          $scope.showAllTables($scope.hive[$scope.hiveMap[$parentNode.label]].tables,$parentNode);
+        }
+      }
+
+      $scope.showToggle = function(node) {
+        if(node.expanded == false){
+          TableService.showHiveTables({"database": node.label},function (hive_tables){
+            var tables = [];
+            for (var i = 0; i < hive_tables.length; i++) {
+              tables.push(hive_tables[i]);
+            }
+            $scope.hive[$scope.hiveMap[node.label]].tables = tables;
+            $scope.showMoreTables(tables,node);
+            node.expanded = true;
+          });
+        }
+      }
+
+      $scope.showSelected = function(node) {
+
+      }
+
+      if(angular.isUndefined($scope.hive) || angular.isUndefined($scope.hiveLoaded) || angular.isUndefined($scope.treedata) ){
+        $scope.hive = [];
+        $scope.hiveLoaded = false;
+        $scope.treedata = [];
+        $scope.loadHive();
+      }
+
+
+
+
       $scope.add = function () {
+
+        if($scope.tableNames.length === 0 && $scope.selectedNodes.length > 0) {
+          for(var i = 0; i <  $scope.selectedNodes.length; i++){
+            if($scope.selectedNodes[i].label.indexOf(".") >= 0){
+              $scope.tableNames += ($scope.selectedNodes[i].label) += ',';
+            }
+          }
+        }
+
         if ($scope.tableNames.trim() === "") {
           SweetAlert.swal('', 'Please input table(s) you want to synchronize.', 'info');
           return;
@@ -172,7 +352,8 @@ KylinApp
         })
       }
 
-      $scope.remove = function () {
+
+    $scope.remove = function () {
         if ($scope.tableNames.trim() === "") {
           SweetAlert.swal('', 'Please input table(s) you want to synchronize.', 'info');
           return;

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/webapp/app/js/directives/angular-tree-control.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/directives/angular-tree-control.js b/webapp/app/js/directives/angular-tree-control.js
new file mode 100644
index 0000000..6fca987
--- /dev/null
+++ b/webapp/app/js/directives/angular-tree-control.js
@@ -0,0 +1,363 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 Steve
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ *   subject to the following conditions:
+ *
+ *   The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+(function ( angular ) {
+  'use strict';
+
+  angular.module( 'treeControl', [] )
+    .directive( 'treecontrol', ['$compile', function( $compile ) {
+      /**
+       * @param cssClass - the css class
+       * @param addClassProperty - should we wrap the class name with class=""
+       */
+      function classIfDefined(cssClass, addClassProperty) {
+        if (cssClass) {
+          if (addClassProperty)
+            return 'class="' + cssClass + '"';
+          else
+            return cssClass;
+        }
+        else
+          return "";
+      }
+
+      function ensureDefault(obj, prop, value) {
+        if (!obj.hasOwnProperty(prop))
+          obj[prop] = value;
+      }
+
+      return {
+        restrict: 'EA',
+        require: "treecontrol",
+        transclude: true,
+        scope: {
+          treeModel: "=",
+          selectedNode: "=?",
+          selectedNodes: "=?",
+          expandedNodes: "=?",
+          onSelection: "&",
+          onNodeToggle: "&",
+          options: "=?",
+          orderBy: "@",
+          reverseOrder: "@",
+          filterExpression: "=?",
+          filterComparator: "=?",
+          onDblclick: "&"
+        },
+        controller: ['$scope', function( $scope ) {
+
+          function defaultIsLeaf(node) {
+            return !node[$scope.options.nodeChildren] || node[$scope.options.nodeChildren].length === 0;
+          }
+
+          function shallowCopy(src, dst) {
+            if (angular.isArray(src)) {
+              dst = dst || [];
+
+              for ( var i = 0; i < src.length; i++) {
+                dst[i] = src[i];
+              }
+            } else if (angular.isObject(src)) {
+              dst = dst || {};
+
+              for (var key in src) {
+                if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
+                  dst[key] = src[key];
+                }
+              }
+            }
+
+            return dst || src;
+          }
+          function defaultEquality(a, b) {
+            if (a === undefined || b === undefined)
+              return false;
+            a = shallowCopy(a);
+            a[$scope.options.nodeChildren] = [];
+            b = shallowCopy(b);
+            b[$scope.options.nodeChildren] = [];
+            return angular.equals(a, b);
+          }
+
+          $scope.options = $scope.options || {};
+          ensureDefault($scope.options, "multiSelection", false);
+          ensureDefault($scope.options, "nodeChildren", "children");
+          ensureDefault($scope.options, "dirSelectable", "true");
+          ensureDefault($scope.options, "injectClasses", {});
+          ensureDefault($scope.options.injectClasses, "ul", "");
+          ensureDefault($scope.options.injectClasses, "li", "");
+          ensureDefault($scope.options.injectClasses, "liSelected", "");
+          ensureDefault($scope.options.injectClasses, "iExpanded", "");
+          ensureDefault($scope.options.injectClasses, "iCollapsed", "");
+          ensureDefault($scope.options.injectClasses, "iLeaf", "");
+          ensureDefault($scope.options.injectClasses, "label", "");
+          ensureDefault($scope.options.injectClasses, "labelSelected", "");
+          ensureDefault($scope.options, "equality", defaultEquality);
+          ensureDefault($scope.options, "isLeaf", defaultIsLeaf);
+
+          $scope.selectedNodes = $scope.selectedNodes || [];
+          $scope.expandedNodes = $scope.expandedNodes || [];
+          $scope.expandedNodesMap = {};
+          for (var i=0; i < $scope.expandedNodes.length; i++) {
+            $scope.expandedNodesMap[""+i] = $scope.expandedNodes[i];
+          }
+          $scope.parentScopeOfTree = $scope.$parent;
+
+
+          function isSelectedNode(node) {
+            if (!$scope.options.multiSelection && ($scope.options.equality(node, $scope.selectedNode)))
+              return true;
+            else if ($scope.options.multiSelection && $scope.selectedNodes) {
+              for (var i = 0; (i < $scope.selectedNodes.length); i++) {
+                if ($scope.options.equality(node, $scope.selectedNodes[i])) {
+                  return true;
+                }
+              }
+              return false;
+            }
+          }
+
+          $scope.headClass = function(node) {
+            var liSelectionClass = classIfDefined($scope.options.injectClasses.liSelected, false);
+            var injectSelectionClass = "";
+            if (liSelectionClass && isSelectedNode(node))
+              injectSelectionClass = " " + liSelectionClass;
+            if ($scope.options.isLeaf(node))
+              return "tree-leaf" + injectSelectionClass;
+            if ($scope.expandedNodesMap[this.$id])
+              return "tree-expanded" + injectSelectionClass;
+            else
+              return "tree-collapsed" + injectSelectionClass;
+          };
+
+          $scope.iBranchClass = function() {
+            if ($scope.expandedNodesMap[this.$id])
+              return classIfDefined($scope.options.injectClasses.iExpanded);
+            else
+              return classIfDefined($scope.options.injectClasses.iCollapsed);
+          };
+
+          $scope.nodeExpanded = function() {
+            return !!$scope.expandedNodesMap[this.$id];
+          };
+
+          $scope.selectNodeHead = function() {
+            var expanding = $scope.expandedNodesMap[this.$id] === undefined;
+            $scope.expandedNodesMap[this.$id] = (expanding ? this.node : undefined);
+            if (expanding) {
+              $scope.expandedNodes.push(this.node);
+            }
+            else {
+              var index;
+              for (var i=0; (i < $scope.expandedNodes.length) && !index; i++) {
+                if ($scope.options.equality($scope.expandedNodes[i], this.node)) {
+                  index = i;
+                }
+              }
+              if (index != undefined)
+                $scope.expandedNodes.splice(index, 1);
+            }
+            if ($scope.onNodeToggle)
+              $scope.onNodeToggle({node: this.node, expanded: expanding});
+          };
+
+          $scope.selectNodeLabel = function( selectedNode ){
+            if(selectedNode[$scope.options.nodeChildren] && selectedNode[$scope.options.nodeChildren].length > 0){
+              this.selectNodeHead();
+            }
+            if($scope.options.dirSelectable || !(selectedNode[$scope.options.nodeChildren] && selectedNode[$scope.options.nodeChildren].length > 0) )
+             {
+              var selected = false;
+              if ($scope.options.multiSelection) {
+                var pos = $scope.selectedNodes.indexOf(selectedNode);
+                if (pos === -1) {
+                  $scope.selectedNodes.push(selectedNode);
+                  selected = true;
+                } else {
+                  $scope.selectedNodes.splice(pos, 1);
+                }
+              } else {
+                if ($scope.selectedNode != selectedNode) {
+                  $scope.selectedNode = selectedNode;
+                  selected = true;
+                }
+                else {
+                  $scope.selectedNode = undefined;
+                }
+              }
+              if ($scope.onSelection)
+                $scope.onSelection({node: selectedNode, selected: selected});
+            }
+          };
+
+
+          $scope.dblClickNode = function(selectedNode){
+            if($scope.onDblclick!=null){
+              $scope.onDblclick({node:selectedNode});
+            }
+          }
+
+          $scope.selectedClass = function() {
+            var isThisNodeSelected = isSelectedNode(this.node);
+            var labelSelectionClass = classIfDefined($scope.options.injectClasses.labelSelected, false);
+            var injectSelectionClass = "";
+            if (labelSelectionClass && isThisNodeSelected)
+              injectSelectionClass = " " + labelSelectionClass;
+
+            return isThisNodeSelected?"tree-selected" + injectSelectionClass:"";
+          };
+
+          //tree template
+          var orderBy = $scope.orderBy ? ' | orderBy:orderBy:reverseOrder' : '';
+          var template =
+            '<ul '+classIfDefined($scope.options.injectClasses.ul, true)+'>' +
+            '<li ng-repeat="node in node.' + $scope.options.nodeChildren + ' | filter:filterExpression:filterComparator ' + orderBy + '" ng-class="headClass(node)" '+classIfDefined($scope.options.injectClasses.li, true)+'>' +
+            '<i class="tree-branch-head" ng-class="iBranchClass()" ng-click="selectNodeHead(node)"></i>' +
+            '<i class="tree-leaf-head '+classIfDefined($scope.options.injectClasses.iLeaf, false)+'"></i>' +
+            '<div class="tree-label '+classIfDefined($scope.options.injectClasses.label, false)+'" ng-class="selectedClass()" ng-click="selectNodeLabel(node)" ng-dblclick="dblClickNode(node)" tree-transclude></div>' +
+            '<treeitem ng-if="nodeExpanded()"></treeitem>' +
+            '</li>' +
+            '</ul>';
+
+          this.template = $compile(template);
+        }],
+        compile: function(element, attrs, childTranscludeFn) {
+          return function ( scope, element, attrs, treemodelCntr ) {
+
+            scope.$watch("treeModel", function updateNodeOnRootScope(newValue) {
+              if (angular.isArray(newValue)) {
+                if (angular.isDefined(scope.node) && angular.equals(scope.node[scope.options.nodeChildren], newValue))
+                  return;
+                scope.node = {};
+                scope.synteticRoot = scope.node;
+                scope.node[scope.options.nodeChildren] = newValue;
+              }
+              else {
+                if (angular.equals(scope.node, newValue))
+                  return;
+                scope.node = newValue;
+              }
+            });
+
+            scope.$watchCollection('expandedNodes', function(newValue) {
+              var notFoundIds = 0;
+              var newExpandedNodesMap = {};
+              var $liElements = element.find('li');
+              var existingScopes = [];
+              // find all nodes visible on the tree and the scope $id of the scopes including them
+              angular.forEach($liElements, function(liElement) {
+                var $liElement = angular.element(liElement);
+                var liScope = $liElement.scope();
+                existingScopes.push(liScope);
+              });
+              // iterate over the newValue, the new expanded nodes, and for each find it in the existingNodesAndScopes
+              // if found, add the mapping $id -> node into newExpandedNodesMap
+              // if not found, add the mapping num -> node into newExpandedNodesMap
+              angular.forEach(newValue, function(newExNode) {
+                var found = false;
+                for (var i=0; (i < existingScopes.length) && !found; i++) {
+                  var existingScope = existingScopes[i];
+                  if (scope.options.equality(newExNode, existingScope.node)) {
+                    newExpandedNodesMap[existingScope.$id] = existingScope.node;
+                    found = true;
+                  }
+                }
+                if (!found)
+                  newExpandedNodesMap[notFoundIds++] = newExNode;
+              });
+              scope.expandedNodesMap = newExpandedNodesMap;
+            });
+
+//                        scope.$watch('expandedNodesMap', function(newValue) {
+//
+//                        });
+
+            //Rendering template for a root node
+            treemodelCntr.template( scope, function(clone) {
+              element.html('').append( clone );
+            });
+            // save the transclude function from compile (which is not bound to a scope as apposed to the one from link)
+            // we can fix this to work with the link transclude function with angular 1.2.6. as for angular 1.2.0 we need
+            // to keep using the compile function
+            scope.$treeTransclude = childTranscludeFn;
+          }
+        }
+      };
+    }])
+    .directive("treeitem", function() {
+      return {
+        restrict: 'E',
+        require: "^treecontrol",
+        link: function( scope, element, attrs, treemodelCntr) {
+          // Rendering template for the current node
+          treemodelCntr.template(scope, function(clone) {
+            element.html('').append(clone);
+          });
+        }
+      }
+    })
+    .directive("treeTransclude", function() {
+      return {
+        link: function(scope, element, attrs, controller) {
+          if (!scope.options.isLeaf(scope.node)) {
+            angular.forEach(scope.expandedNodesMap, function (node, id) {
+              if (scope.options.equality(node, scope.node)) {
+                scope.expandedNodesMap[scope.$id] = scope.node;
+                scope.expandedNodesMap[id] = undefined;
+              }
+            });
+          }
+          if (!scope.options.multiSelection && scope.options.equality(scope.node, scope.selectedNode)) {
+            scope.selectedNode = scope.node;
+          } else if (scope.options.multiSelection) {
+            var newSelectedNodes = [];
+            for (var i = 0; (i < scope.selectedNodes.length); i++) {
+              if (scope.options.equality(scope.node, scope.selectedNodes[i])) {
+                newSelectedNodes.push(scope.node);
+              }
+            }
+            scope.selectedNodes = newSelectedNodes;
+          }
+
+          // create a scope for the transclusion, whos parent is the parent of the tree control
+          scope.transcludeScope = scope.parentScopeOfTree.$new();
+          scope.transcludeScope.node = scope.node;
+          scope.transcludeScope.$parentNode = (scope.$parent.node === scope.synteticRoot)?null:scope.$parent.node;
+          scope.transcludeScope.$index = scope.$index;
+          scope.transcludeScope.$first = scope.$first;
+          scope.transcludeScope.$middle = scope.$middle;
+          scope.transcludeScope.$last = scope.$last;
+          scope.transcludeScope.$odd = scope.$odd;
+          scope.transcludeScope.$even = scope.$even;
+          scope.$on('$destroy', function() {
+            scope.transcludeScope.$destroy();
+          });
+
+          scope.$treeTransclude(scope.transcludeScope, function(clone) {
+            element.empty();
+            element.append(clone);
+          });
+        }
+      }
+    });
+})( angular );

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/webapp/app/js/services/kylinProperties.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/kylinProperties.js b/webapp/app/js/services/kylinProperties.js
index a03403b..546db2b 100644
--- a/webapp/app/js/services/kylinProperties.js
+++ b/webapp/app/js/services/kylinProperties.js
@@ -56,12 +56,20 @@ KylinApp.service('kylinConfig', function (AdminService, $log) {
   }
 
   this.getDeployEnv = function () {
+    this.deployEnv = this.getProperty("deploy.env");
     if (!this.deployEnv) {
-      this.deployEnv = this.getProperty("deploy.env").trim();
+      return "DEV";
     }
-    return this.deployEnv.toUpperCase();
+    return this.deployEnv.toUpperCase().trim();
   }
 
+  this.getHiveLimit = function () {
+    this.hiveLimit = this.getProperty("kylin.web.hive.limit");
+    if (!this.hiveLimit) {
+      return 20;
+    }
+    return this.hiveLimit;
+  }
   //fill config info for Config from backend
   this.initWebConfigInfo = function () {
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/webapp/app/js/services/tables.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/tables.js b/webapp/app/js/services/tables.js
index ca7fc42..4199d6c 100755
--- a/webapp/app/js/services/tables.js
+++ b/webapp/app/js/services/tables.js
@@ -17,7 +17,7 @@
  */
 
 KylinApp.factory('TableService', ['$resource', function ($resource, config) {
-  return $resource(Config.service.url + 'tables/:tableName/:action', {}, {
+  return $resource(Config.service.url + 'tables/:tableName/:action/:database', {}, {
     list: {method: 'GET', params: {}, cache: true, isArray: true},
     get: {method: 'GET', params: {}, isArray: false},
     getExd: {method: 'GET', params: {action: 'exd-map'}, isArray: false},
@@ -25,6 +25,8 @@ KylinApp.factory('TableService', ['$resource', function ($resource, config) {
     loadHiveTable: {method: 'POST', params: {}, isArray: false},
     unLoadHiveTable: {method: 'DELETE', params: {}, isArray: false},
     addStreamingSrc: {method: 'POST', params: {action:'addStreamingSrc'}, isArray: false},
-    genCardinality: {method: 'PUT', params: {action: 'cardinality'}, isArray: false}
+    genCardinality: {method: 'PUT', params: {action: 'cardinality'}, isArray: false},
+    showHiveDatabases: {method: 'GET', params: {action:'hive'}, cache: true, isArray: true},
+    showHiveTables: {method: 'GET', params: {action:'hive'}, cache: true, isArray: true}
   });
 }]);

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/webapp/app/partials/tables/source_table_tree.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/tables/source_table_tree.html b/webapp/app/partials/tables/source_table_tree.html
index 4eddc4f..c2dc219 100755
--- a/webapp/app/partials/tables/source_table_tree.html
+++ b/webapp/app/partials/tables/source_table_tree.html
@@ -26,6 +26,7 @@
         <div class="col-xs-5" style="padding-left: 0px;margin-top: 20px;">
             <div class="pull-right">
               <a class="btn btn-xs btn-primary" tooltip="Load Hive Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openModal()"><i class="fa fa-download"></i></a>
+              <a class="btn btn-xs btn-info" tooltip="Load Hive Table From Tree"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openTreeModal()"><i class="fa fa-download"></i></a>
               <a class="btn btn-xs btn-info" tooltip="UnLoad Hive Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openUnLoadModal()"><i class="fa fa-remove"></i></a>
               <a class="btn btn-xs btn-primary" tooltip="Add Streaming Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openStreamingSourceModal()"><i class="fa fa-area-chart"></i></a>
             </div>
@@ -47,5 +48,30 @@
     </div>
 </div>
 
+<script type="text/ng-template" id="addHiveTableFromTree.html">
+  <div class="modal-header"><button class="close" type="button" data-dismiss="modal" ng-click="cancel()">×</button>
+    <h4>Load Hive Table Metadata From Tree</h4>
+  </div>
+  <div class="modal-body">
+    <span><strong>Project: </strong>{{ $parent.projectName!=null?$parent.projectName:'NULL'}}</span>
+    <div class="form-group searchBox">
+      <input type="text" placeholder="Filter ..." class="nav-search-input" ng-model="predicate" />
+    </div>
+    <loading ng-if="!hiveLoaded" text="Loading Databases..."></loading>
+    <treecontrol class="tree-light check" tree-model="treedata" selected-nodes="selectedNodes" filter-expression="predicate" on-selection="showSelected(node)" on-node-toggle="showToggle(node)" options="treeOptions">
+      <div ng-if="node.label==''&&node.id==0"><img src="image/ajax-loader.gif">Loading Tables...</div>
+      <button class="btn btn-xs btn-primary" ng-if="node.label==''&&node.id==65535" ng-click="showMoreClicked($parentNode)">Show More</button>
+      <button class="btn btn-xs btn-primary" ng-if="node.label==''&&node.id==65535" ng-click="showAllClicked($parentNode)">Show All</button>
+      {{node.label}}
+    </treecontrol>
+  </div>
+
+  <div class="modal-footer">
+    <button class="btn btn-primary" ng-click="add()">Sync</button>
+    <button class="btn btn-primary" ng-click="cancel()">Cancel</button>
+  </div>
+
+</script>
+
 <div ng-include="'partials/tables/table_load.html'"></div>
 <div ng-include="'partials/tables/table_unload.html'"></div>

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/webapp/bower.json
----------------------------------------------------------------------
diff --git a/webapp/bower.json b/webapp/bower.json
index 41144f9..bba4a52 100755
--- a/webapp/bower.json
+++ b/webapp/bower.json
@@ -32,7 +32,8 @@
     "bootstrap-sweetalert": "~0.4.3",
     "angular-toggle-switch":"1.3.0",
     "angular-ui-select": "0.13.2",
-    "angular-sanitize": "1.2.18"
+    "angular-sanitize": "1.2.18",
+    "angular-tree-control": "0.2.8"
   },
   "devDependencies": {
     "less.js": "~1.4.0",

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc7d4f58/webapp/grunt.json
----------------------------------------------------------------------
diff --git a/webapp/grunt.json b/webapp/grunt.json
index 3219b5e..86ad1dc 100755
--- a/webapp/grunt.json
+++ b/webapp/grunt.json
@@ -19,7 +19,6 @@
                 "app/components/angularLocalStorage/src/angularLocalStorage.js",
                 "app/components/angular-base64/angular-base64.min.js",
                 "app/components/ng-grid/build/ng-grid.js",
-                "app/components/angular-tree-control/angular-tree-control.js",
                 "app/components/ace-builds/src-min-noconflict/ace.js",
                 "app/components/ace-builds/src-min-noconflict/ext-language_tools.js",
                 "app/components/ace-builds/src-min-noconflict/mode-json.js",


Mime
View raw message