Repository: incubator-eagle
Updated Branches:
  refs/heads/master eb0734b6f -> d37643b0a


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": {


Reply via email to