http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/app.time.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/app.time.js 
b/eagle-webservice/src/main/webapp/_app/public/js/app.time.js
new file mode 100644
index 0000000..f5f41c1
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/app.time.js
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+// Time Zone
+(function() {
+       "use strict";
+
+       app.time = {
+               UTC_OFFSET: 0,
+               now: function() {
+                       return app.time.offset();
+               },
+               offset: function(time) {
+                       // Parse string number
+                       if(typeof time === "string" && !isNaN(+time)) {
+                               time = +time;
+                       }
+
+                       var _mom = new moment(time);
+                       _mom.utcOffset(app.time.UTC_OFFSET);
+                       return _mom;
+               },
+               /*
+                * Return the moment object which use server time zone and keep 
the time.
+                */
+               srvZone: function(time) {
+                       var _timezone = time._isAMomentObject ? 
time.utcOffset() : new moment().utcOffset();
+                       var _mom = app.time.offset(time);
+                       _mom.subtract(app.time.UTC_OFFSET, "m").add(_timezone, 
"m");
+                       return _mom;
+               },
+
+               refreshInterval: 1000 * 10
+       };
+
+       // Moment update
+       moment.fn.toISO = function() {
+               return this.format("YYYY-MM-DDTHH:mm:ss.000Z");
+       };
+
+       // Force convert date
+       var _toDate = moment.fn.toDate;
+       moment.fn.toDate = function(ignoreTimeZone) {
+               if(!ignoreTimeZone) return _toDate.bind(this)();
+               return new Date(
+                       this.year(),
+                       this.month(),
+                       this.date(),
+                       this.hour(),
+                       this.minute(),
+                       this.second(),
+                       this.millisecond()
+               );
+       };
+})();
\ No newline at end of file

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

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

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/components/charts/line3d.js
----------------------------------------------------------------------
diff --git 
a/eagle-webservice/src/main/webapp/_app/public/js/components/charts/line3d.js 
b/eagle-webservice/src/main/webapp/_app/public/js/components/charts/line3d.js
new file mode 100644
index 0000000..c2bf23b
--- /dev/null
+++ 
b/eagle-webservice/src/main/webapp/_app/public/js/components/charts/line3d.js
@@ -0,0 +1,348 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('line3dChart', function($compile) {
+       'use strict';
+
+       return {
+               restrict : 'AE',
+               scope : {
+                       title : "@",
+                       data : "=",
+
+                       height : "=?height"
+               },
+               controller : function(line3dCharts, $scope, $element, $attrs) {
+                       $scope.height = $scope.height || 200;
+
+                       var _chart = line3dCharts($scope);
+
+                       var _chartBody = $element.find(".chart-body");
+                       _chartBody.height($scope.height);
+
+                       _chart.gen($element.find(".chart-body"), $attrs.data, {
+                               height : $scope.height,
+                       });
+               },
+               template : '<div class="chart">' + '<div class="chart-header">' 
+ '<h3>{{title}}</h3>' + '</div>' + '<div class="chart-body">' + '</div>' + 
'</div>',
+               replace : true
+       };
+});
+
+eagleComponents.service('line3dCharts', function() {
+       'use strict';
+
+       var charts = function($scope) {
+               return {
+                       gen : function(ele, series, config) {
+                               // ======================= Initialization 
=======================
+                               ele = $(ele);
+
+                               series = series || [];
+                               config = config || {};
+
+                               var _bounds = [{min:-10, max: 10},{min:-10, 
max: 10},{min:-10, max: 10}];
+                               var _scale = 1;
+
+                               // ======================= Set Up D3 View 
=======================
+                               var width = ele.innerWidth(), height = 
config.height;
+
+                               var color = ["#7cb5ec", "#f7a35c", "#90ee7e", 
"#7798BF", "#aaeeee"];
+
+                               var svg = 
d3.select(ele[0]).append("svg").attr("width", width).attr("height", height);
+
+                               // ========================== Function 
==========================
+                               var yaw=0.5,pitch=0.5,drag;
+                               var transformPrecalc = [];
+
+                               var offsetPoint = function(point) {
+                                       var _point = [
+                                               +point[0] - (_bounds[0].max + 
_bounds[0].min) / 2,
+                                               -point[1] + (_bounds[1].max + 
_bounds[1].min) / 2,
+                                               -point[2] + (_bounds[2].max + 
_bounds[2].min) / 2
+                                       ];
+                                       return [_point[0] * _scale, _point[1] * 
_scale, _point[2] * _scale];
+                               };
+
+                               var transfromPointX = function(point) {
+                                       point = offsetPoint(point);
+                                       return transformPrecalc[0] * point[0] + 
transformPrecalc[1] * point[1] + transformPrecalc[2] * point[2] + width / 2;
+                               };
+                               var transfromPointY = function(point) {
+                                       point = offsetPoint(point);
+                                       return transformPrecalc[3] * point[0] + 
transformPrecalc[4] * point[1] + transformPrecalc[5] * point[2] + height / 2;
+                               };
+                               var transfromPointZ = function(point) {
+                                       point = offsetPoint(point);
+                                       return transformPrecalc[6] * point[0] + 
transformPrecalc[7] * point[1] + transformPrecalc[8] * point[2];
+                               };
+                               var transformPoint2D = function(point) {
+                                       var _point = [point[0], point[1], 
point[2]];
+                                       return 
transfromPointX(_point).toFixed(10) + "," + transfromPointY(_point).toFixed(10);
+                               };
+
+                               var setTurtable = function(yaw, pitch, update) {
+                                       var cosA = Math.cos(pitch);
+                                       var sinA = Math.sin(pitch);
+                                       var cosB = Math.cos(yaw);
+                                       var sinB = Math.sin(yaw);
+                                       transformPrecalc[0] = cosB;
+                                       transformPrecalc[1] = 0;
+                                       transformPrecalc[2] = sinB;
+                                       transformPrecalc[3] = sinA * sinB;
+                                       transformPrecalc[4] = cosA;
+                                       transformPrecalc[5] = -sinA * cosB;
+                                       transformPrecalc[6] = -sinB * cosA;
+                                       transformPrecalc[7] = sinA;
+                                       transformPrecalc[8] = cosA * cosB;
+
+                                       if(update) _update();
+                               };
+                               setTurtable(0.4,0.4);
+
+                               // =========================== Redraw 
===========================
+                               var _coordinateList = [];
+                               var _axisText = [];
+                               var coordinate = svg.selectAll(".axis");
+                               var axisText = svg.selectAll(".axis-text");
+                               var lineList = svg.selectAll(".line");
+
+                               function _redraw(series) {
+                                       var _desX, _desY, _desZ, _step;
+
+                                       // Bounds
+                                       if(series) {
+                                               _bounds = [{},{},{}];
+                                               $.each(series, function(j, 
series) {
+                                                       // Points
+                                                       $.each(series.data, 
function(k, point) {
+                                                               for(var i = 0 ; 
i < 3 ; i += 1) {
+                                                                       // 
Minimum
+                                                                       
if(_bounds[i].min === undefined || point[i] < _bounds[i].min) {
+                                                                               
_bounds[i].min = point[i];
+                                                                       }
+                                                                       // 
Maximum
+                                                                       
if(_bounds[i].max === undefined || _bounds[i].max < point[i]) {
+                                                                               
_bounds[i].max = point[i];
+                                                                       }
+                                                               }
+                                                       });
+                                               });
+                                       }
+
+                                       _desX = _bounds[0].max - _bounds[0].min;
+                                       _desY = _bounds[1].max - _bounds[1].min;
+                                       _desZ = _bounds[2].max - _bounds[2].min;
+
+                                       // Step
+                                       (function() {
+                                               var _stepX = _desX / 10;
+                                               var _stepY = _desY / 10;
+                                               var _stepZ = _desZ / 10;
+
+                                               _step = Math.min(_stepX, 
_stepY, _stepZ);
+                                               _step = Math.max(_step, 0.5);
+                                               _step = 0.5;
+                                       })();
+
+                                       // Scale
+                                       (function() {
+                                               var _scaleX = width / _desX;
+                                               var _scaleY = height / _desY / 
2;
+                                               var _scaleZ = width / _desZ;
+                                               _scale = Math.min(_scaleX, 
_scaleY, _scaleZ) / 2;
+                                       })();
+
+                                       // Coordinate
+                                       // > Basic
+                                       _coordinateList = [
+                                               {color: "rgba(0,0,0,0.1)", 
data: [[0,0,-100],[0,0,100]]},
+                                               {color: "rgba(0,0,0,0.1)", 
data: [[0,-100,0],[0,100,0]]},
+                                               {color: "rgba(0,0,0,0.1)", 
data: [[-100,0,0],[100,0,0]]},
+
+                                               {color: "rgba(0,0,255,0.3)", 
data: 
[[_bounds[0].min,_bounds[1].min,_bounds[2].min],[_bounds[0].max,_bounds[1].min,_bounds[2].min]]},
+                                               {color: "rgba(0,0,255,0.3)", 
data: 
[[_bounds[0].min,_bounds[1].min,_bounds[2].min],[_bounds[0].min,_bounds[1].min,_bounds[2].max]]},
+
+                                               {color: "rgba(0,0,255,0.3)", 
data: 
[[_bounds[0].min,_bounds[1].min,_bounds[2].max],[_bounds[0].max,_bounds[1].min,_bounds[2].max]]},
+                                               {color: "rgba(0,0,255,0.3)", 
data: 
[[_bounds[0].max,_bounds[1].min,_bounds[2].min],[_bounds[0].max,_bounds[1].min,_bounds[2].max]]},
+
+                                               {color: "rgba(0,0,255,0.3)", 
data: 
[[_bounds[0].min,_bounds[1].max,_bounds[2].min],[_bounds[0].max,_bounds[1].max,_bounds[2].min]]},
+                                               {color: "rgba(0,0,255,0.3)", 
data: 
[[_bounds[0].min,_bounds[1].max,_bounds[2].min],[_bounds[0].min,_bounds[1].max,_bounds[2].max]]},
+
+                                               {color: "rgba(0,0,255,0.3)", 
data: 
[[_bounds[0].min,_bounds[1].min,_bounds[2].min],[_bounds[0].min,_bounds[1].max,_bounds[2].min]]},
+                                               {color: "rgba(0,0,255,0.3)", 
data: 
[[_bounds[0].max,_bounds[1].min,_bounds[2].min],[_bounds[0].max,_bounds[1].max,_bounds[2].min]]},
+                                               {color: "rgba(0,0,255,0.3)", 
data: 
[[_bounds[0].min,_bounds[1].min,_bounds[2].max],[_bounds[0].min,_bounds[1].max,_bounds[2].max]]},
+                                       ];
+
+                                       _axisText = [];
+                                       function _axisPoint(point, dimension, 
number) {
+                                               // Coordinate
+                                               if(dimension === 1) {
+                                                       _coordinateList.push({
+                                                               color: 
"rgba(0,0,0,0.2)",
+                                                               
data:[[_step/5,point[1],0],[0,point[1],0],[0,point[1],_step/5]]
+                                                       });
+                                               } else {
+                                                       _coordinateList.push({
+                                                               color: 
"rgba(0,0,0,0.2)",
+                                                               
data:[[point[0],-_step/8,point[2]], [point[0],_step/8,point[2]]]
+                                                       });
+                                               }
+
+                                               // Axis Text
+                                               if(number.toFixed(0) == number 
+ "") {
+                                                       point.text = number;
+                                                       point.dimension = 
dimension;
+                                                       _axisText.push(point);
+                                               }
+                                       }
+                                       function _axisPoints(dimension, bound) {
+                                               var i, _unit;
+                                               for(i = _step ; i < bound.max + 
_step ; i += _step) {
+                                                       _unit = [0,0,0];
+                                                       _unit[dimension] = i;
+                                                       _axisPoint(_unit, 
dimension, i);
+                                               }
+                                               for(i = -_step ; i > bound.min 
- _step ; i -= _step) {
+                                                       _unit = [0,0,0];
+                                                       _unit[dimension] = i;
+                                                       _axisPoint(_unit, 
dimension, i);
+                                               }
+                                       }
+                                       // > Steps
+                                       _axisPoint([0,0,0],1,0);
+                                       _axisPoints(0, _bounds[0]);
+                                       _axisPoints(1, _bounds[1]);
+                                       _axisPoints(2, _bounds[2]);
+
+                                       // > Draw
+                                       coordinate = 
coordinate.data(_coordinateList);
+                                       coordinate.enter()
+                                       .append("path")
+                                               .attr("fill", "none")
+                                               .attr("stroke", function(d) 
{return d.color;});
+                                       coordinate.exit().remove();
+
+                                       // Axis Text
+                                       axisText = axisText.data(_axisText);
+                                       axisText.enter()
+                                               .append("text")
+                                               .classed("noSelect", true)
+                                               .attr("fill", "rgba(0,0,0,0.5)")
+                                               .attr("text-anchor", 
function(d) {return d.dimension === 1 ? "start" : "middle";})
+                                               .text(function(d) {return 
d.text;});
+                                       axisText.transition()
+                                               .attr("text-anchor", 
function(d) {return d.dimension === 1 ? "end" : "middle";})
+                                               .text(function(d) {return 
d.text;});
+                                       axisText.exit().remove();
+
+                                       // Lines
+                                       lineList = lineList.data(series || []);
+                                       lineList.enter()
+                                               .append("path")
+                                                       .attr("fill", "none")
+                                                       .attr("stroke", 
function(d) {return d.color;});
+                                       lineList.exit().remove();
+
+                                       _update();
+                               }
+
+                               function _update() {
+                                       coordinate
+                                               .attr("d", function(d) {
+                                               var path = "";
+                                               $.each(d.data, function(i, 
point) {
+                                                       path += (i === 0 ? "M" 
: "L") + transformPoint2D(point);
+                                               });
+                                               return path;
+                                               });
+
+                                       axisText
+                                               .attr("x", function(d) {return 
transfromPointX(d) + (d.dimension === 1 ? -3 : 0);})
+                                               .attr("y", function(d) {return 
transfromPointY(d) + (d.dimension === 1 ? 0 : -5);});
+
+                                       lineList
+                                               .attr("d", function(d, index) {
+                                                       var path = "";
+                                                       $.each(d.data, 
function(i, point) {
+                                                               path += (i === 
0 ? "M" : "L") + transformPoint2D(point);
+                                                       });
+                                                       return path;
+                                               });
+                               }
+
+
+                               svg.on("mousedown", function() {
+                                       drag = [d3.mouse(this), yaw, pitch];
+                               }).on("mouseup", function() {
+                                       drag = false;
+                               }).on("mousemove", function() {
+                                       if (drag) {
+                                               var mouse = d3.mouse(this);
+                                               yaw = drag[1] - (mouse[0] - 
drag[0][0]) / 50;
+                                               pitch = drag[2] + (mouse[1] - 
drag[0][1]) / 50;
+                                               pitch = Math.max(-Math.PI / 2, 
Math.min(Math.PI / 2, pitch));
+                                               setTurtable(yaw, pitch, true);
+                                       }
+                               });
+
+                               // =========================== Render 
===========================
+                               _redraw();
+
+                               function _render() {
+                                       // ======== Parse Data ========
+                                       var _series = typeof series === 
"string" ? $scope.data : series;
+                                       if(!_series) return;
+
+                                       // Clone
+                                       _series = $.map(_series, 
function(series) {
+                                               return {
+                                                       name: series.name,
+                                                       color: series.color,
+                                                       data: series.data
+                                               };
+                                       });
+
+                                       // Colors
+                                       $.each(_series, function(i, series) {
+                                               series.color = series.color || 
color[i % color.length];
+                                       });
+
+                                       // Render
+                                       _redraw(_series);
+                               }
+
+                               // ======================= Dynamic Detect 
=======================
+                               if(typeof series === "string") {
+                                       $scope.$parent.$watch(series, 
function() {
+                                               _render();
+                                       }, true);
+                               } else {
+                                       _render();
+                               }
+
+
+                               // ========================== Clean Up 
==========================
+                               $scope.$on('$destroy', function() {
+                                       svg.remove();
+                               });
+                       },
+               };
+       };
+       return charts;
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/components/file.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/components/file.js 
b/eagle-webservice/src/main/webapp/_app/public/js/components/file.js
new file mode 100644
index 0000000..a8d78db
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/file.js
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('file', function($compile) {
+       'use strict';
+
+       return {
+               restrict : 'A',
+               scope: {
+                       filepath: "=?filepath",
+               },
+               controller: function($scope, $element, $attrs) {
+                       // Watch change(Only support clean the data)
+                       if($attrs.filepath) {
+                               $scope.$parent.$watch($attrs.filepath, 
function(value) {
+                                       if(!value) $element.val(value);
+                               });
+                       }
+
+                       // Bind changed value
+                       $element.on("change", function() {
+                               var _path = $(this).val();
+                               if($attrs.filepath) {
+                                       common.setValueByPath($scope.$parent, 
$attrs.filepath, _path);
+                                       $scope.$parent.$apply();
+                               }
+                       });
+
+                       $scope.$on('$destroy',function(){
+                               $element.off("change");
+                       });
+               },
+               replace: false
+       };
+});

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/components/main.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/components/main.js 
b/eagle-webservice/src/main/webapp/_app/public/js/components/main.js
new file mode 100644
index 0000000..a0d9f9f
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/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.
+ */
+
+var eagleComponents = angular.module('eagle.components', []);

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/components/nvd3.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/components/nvd3.js 
b/eagle-webservice/src/main/webapp/_app/public/js/components/nvd3.js
new file mode 100644
index 0000000..8687c78
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/nvd3.js
@@ -0,0 +1,418 @@
+/*
+ * 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.
+ */
+
+eagleComponents.service('nvd3', function() {
+       var nvd3 = {
+               charts: [],
+               colors: [
+                       "#7CB5EC", "#F7A35C", "#90EE7E", "#7798BF", "#AAEEEE"
+               ]
+       };
+
+       // ============================================
+       // =              Format Convert              =
+       // ============================================
+
+       /***
+        * Format: [series:{key:name, value: [{x, y}]}]
+        */
+
+       nvd3.convert = {};
+       nvd3.convert.eagle = function(seriesList) {
+               return $.map(seriesList, function(series) {
+                       var seriesObj = $.isArray(series) ? {values: series} : 
series;
+                       if(!seriesObj.key) seriesObj.key = "value";
+                       return seriesObj;
+               });
+       };
+
+       nvd3.convert.druid = function(seriesList) {
+               var _seriesList = [];
+
+               $.each(seriesList, function(i, series) {
+                       if(!series.length) return;
+
+                       // Fetch keys
+                       var _measure = series[0];
+                       var _keys = $.map(_measure.event, function(value, key) {
+                               return key !== "metric" ? key : null;
+                       });
+
+                       // Parse series
+                       _seriesList.push.apply(_seriesList, $.map(_keys, 
function(key) {
+                               return {
+                                       key: key,
+                                       values: $.map(series, function(unit) {
+                                               return {
+                                                       x: new 
moment(unit.timestamp).valueOf(),
+                                                       y: unit.event[key]
+                                               };
+                                       })
+                               };
+                       }));
+               });
+
+               return _seriesList;
+       };
+
+       // ============================================
+       // =                    UI                    =
+       // ============================================
+       // Resize with refresh
+       function chartResize() {
+               $.each(nvd3.charts, function(i, chart) {
+                       if(chart) chart.nvd3Update();
+               });
+       }
+       $(window).on("resize.components.nvd3", chartResize);
+       $("body").on("collapsed.pushMenu expanded.pushMenu", function() {
+               setTimeout(chartResize, 300);
+       });
+
+       return nvd3;
+});
+
+/**
+ * config:
+ *             chart:                  Defined chart type: line, column, area
+ *             xTitle:                 Defined x axis title.
+ *             yTitle:                 Defined y axis title.
+ *             xType:                  Defined x axis label type: number, 
decimal, time
+ *             yType:                  Defined y axis label type
+ *             yMin:                   Defined minimum of y axis
+ *             yMax:                   Defined maximum of y axis
+ *             displayType:    Defined the chart display type. Each chart has 
own type.
+ */
+eagleComponents.directive('nvd3', function(nvd3) {
+       'use strict';
+
+       return {
+               restrict: 'AE',
+               scope: {
+                       nvd3: "=",
+                       title: "@?title",                               // title
+                       chart: "@?chart",                               // Same 
as config.chart
+                       config: "=?config",
+                       watching: "@?watching",                 // Default 
watching data(nvd3) only. true will also watching chart & config. false do not 
watching.
+
+                       holder: "=?holder"                              // 
Container for holder to call the chart function
+               },
+               controller: function($scope, $element, $attrs, $timeout) {
+                       var _config, _chartType;
+                       var _chart;
+                       var _chartCntr;
+                       var _holder, _holder_updateTimes;
+
+                       // Destroy
+                       function destroy() {
+                               var _index = $.inArray(_chart, nvd3.charts);
+                               if(!_chartCntr) return _index;
+
+                               // Clean events
+                               d3.select(_chartCntr)
+                                       .on("touchmove",null)
+                                       .on("mousemove",null, true)
+                                       .on("mouseout" ,null,true)
+                                       .on("dblclick" ,null)
+                                       .on("click", null);
+
+                               // Clean elements
+                               d3.select(_chartCntr).selectAll("*").remove();
+                               $element.find(".nvtooltip").remove();
+                               $(_chartCntr).remove();
+
+                               // Clean chart in nvd3 pool
+                               nvd3.charts[_index] = null;
+                               _chart = null;
+
+                               return _index;
+                       }
+
+                       // Setup chart environment. Will clean old chart and 
build new chart if recall.
+                       function initChart() {
+                               // Clean up if already have chart
+                               var _preIndex = destroy();
+
+                               // Initialize
+                               _config = $.extend({}, $scope.config);
+                               _chartType = $scope.chart || _config.chart;
+                               _chartCntr = 
$(document.createElementNS("http://www.w3.org/2000/svg";, "svg"))
+                                       .css("min-height", 50)
+                                       .attr("_rnd", Math.random())
+                                       .appendTo($element)[0];
+
+                               // Size
+                               if(_config.height) {
+                                       $(_chartCntr).css("height", 
_config.height);
+                               }
+
+                               switch(_chartType) {
+                                       case "line":
+                                               _chart = nv.models.lineChart()
+                                                       
.useInteractiveGuideline(true)
+                                                       .showLegend(true)
+                                                       .showYAxis(true)
+                                                       .showXAxis(true)
+                                                       .options({
+                                                               duration: 350
+                                                       });
+                                               break;
+                                       case "column":
+                                               _chart = 
nv.models.multiBarChart()
+                                                       .groupSpacing(0.1)
+                                                       .options({
+                                                               duration: 350
+                                                       });
+                                               break;
+                                       case "area":
+                                               _chart = 
nv.models.stackedAreaChart()
+                                                       
.useInteractiveGuideline(true)
+                                                       .showLegend(true)
+                                                       .showYAxis(true)
+                                                       .showXAxis(true)
+                                                       .options({
+                                                               duration: 350
+                                                       });
+                                               break;
+                                       case "pie":
+                                               _chart = 
nv.models.dimensionalPieChart()
+                                                       .x(function(d) { return 
d.key; })
+                                                       .y(function(d) { return 
d.values[d.values.length - 1].y; });
+                                               break;
+                                       default :
+                                               throw "Type not defined: " + 
_chartType;
+                               }
+
+                               // nvd3 display Type
+                               // TODO: support type define
+
+                               // Define title
+                               if(_chartType !== 'pie') {
+                                       if(_config.xTitle) 
_chart.xAxis.axisLabel(_config.xTitle);
+                                       if(_config.yTitle) 
_chart.yAxis.axisLabel(_config.yTitle);
+                               }
+
+                               // Define label type
+                               var _tickMultiFormat = d3.time.format.multi([
+                                       ["%-I:%M%p", function(d) { return 
d.getMinutes(); }],
+                                       ["%-I%p", function(d) { return 
d.getHours(); }],
+                                       ["%b %-d", function(d) { return 
d.getDate() != 1; }],
+                                       ["%b %-d", function(d) { return 
d.getMonth(); }],
+                                       ["%Y", function() { return true; }]
+                               ]);
+
+                               function _defineLabelType(axis, type) {
+                                       if(!_chart) return;
+
+                                       var _axis = _chart[axis + "Axis"];
+                                       if(!_axis) return;
+
+                                       switch(type) {
+                                               case "decimal":
+                                               case "decimals":
+                                                       
_axis.tickFormat(d3.format('.02f'));
+                                                       break;
+                                               case "text":
+                                                       if(axis === "x") {
+                                                               
_chart.rotateLabels(10);
+                                                               
_chart.reduceXTicks(false).staggerLabels(true);
+                                                       }
+                                                       
_axis.tickFormat(function(d) {
+                                                               return d;
+                                                       });
+                                                       break;
+                                               case "time":
+                                                       if(_chartType !== 
'column') {
+                                                               _chart[axis + 
"Scale"](d3.time.scale());
+                                                               (function () {
+                                                                       var 
measureSeries = null;
+                                                                       
$.each($scope.nvd3 || [], function(i, series) {
+                                                                               
var _len = (series.values || []).length;
+                                                                               
if(_len === 0) return;
+                                                                               
if(measureSeries === null || measureSeries.values.length < _len) measureSeries 
= series;
+                                                                       });
+
+                                                                       var 
width = $element.width() - 35;// Use default nvd3 margin. Hard code.
+                                                                       
if(!measureSeries || width <= 0) return;
+                                                                       var 
count = Math.floor(width / 80);
+                                                                       var 
countDes = Math.floor(measureSeries.values.length / count);
+                                                                       var 
tickValues = [];
+                                                                       for(var 
loc = 0 ; loc < measureSeries.values.length ; loc += countDes) {
+                                                                               
tickValues.push(measureSeries.values[loc].x);
+                                                                       }
+                                                                       
_chart[axis + "Axis"].tickValues(tickValues);
+                                                               })();
+                                                       }
+                                                       
_axis.tickFormat(function(d) {
+                                                               return 
_tickMultiFormat(app.time.offset(d).toDate(true));
+                                                       });
+                                                       break;
+                                               case "number":
+                                               /* falls through */
+                                               default:
+                                                       
_axis.tickFormat(d3.format(',r'));
+                                       }
+                               }
+
+                               if(_chartType !== 'pie') {
+                                       _defineLabelType("x", _config.xType || 
"number");
+                                       _defineLabelType("y", _config.yType || 
"decimal");
+                               }
+
+                               // Global chart list update
+                               if(_preIndex === -1) {
+                                       nvd3.charts.push(_chart);
+                               } else {
+                                       nvd3.charts[_preIndex] = _chart;
+                               }
+
+                               // Use customize update function to update the 
view
+                               _chart.nvd3Update = function() {
+                                       if(_config.xType === "time") 
_defineLabelType("x", _config.xType);
+                                       _chart.update();
+                               };
+
+                               updateData();
+                       }
+
+                       // Update chart data
+                       function updateData() {
+                               var _min = null, _max = null;
+
+                               // Copy series to prevent Angular loop watching
+                               var _data = $.map($scope.nvd3 || [], 
function(series, i) {
+                                       var _series = $.extend(true, {}, 
series);
+                                       _series.color = _series.color || 
nvd3.colors[i % nvd3.colors.length];
+                                       return _series;
+                               });
+
+                               // Chart Y value
+                               if(($scope.chart || _config.chart) !== "pie") {
+                                       $.each(_data, function(i, series) {
+                                               $.each(series.values, 
function(j, unit) {
+                                                       if(_min === null || 
unit.y < _min) _min = unit.y;
+                                                       if(_max === null || 
unit.y > _max) _max = unit.y;
+                                               });
+                                       });
+
+                                       if(_min === 0 && _max === 0) {
+                                               _chart.forceY([0, 10]);
+                                       } else if(_config.yMin !== undefined || 
_config.yMax !== undefined) {
+                                               _chart.forceY([_config.yMin, 
_config.yMax]);
+                                       } else {
+                                               _chart.forceY([]);
+                                       }
+                               }
+
+                               // Update data
+                               d3.select(_chartCntr)                           
                //Select the <svg> element you want to render the chart in.
+                                       .datum(_data)                           
                        //Populate the <svg> element with chart data...
+                                       .call(_chart);                          
                        //Finally, render the chart!
+
+                               setTimeout(_chart.nvd3Update, 10);
+                       }
+
+                       // 
================================================================
+                       // =                           Watching                 
          =
+                       // 
================================================================
+                       // Ignore initial checking
+                       $timeout(function() {
+                               if ($scope.watching !== "false") {
+                                       $scope.$watch(function() {
+                                               if(!$scope.nvd3) return [];
+
+                                               var _hashList = 
$.map($scope.nvd3, function(series) {
+                                                       if(!series.values) 
return 0;
+                                                       var unit = {
+                                                               x: 0,
+                                                               y: 0
+                                                       };
+
+                                                       $.each(series.values, 
function(j, item) {
+                                                               unit.x += 
item.x;
+                                                               unit.y += 
item.y;
+                                                       });
+
+                                                       return unit;
+                                               });
+
+                                               return _hashList;
+                                       }, function() {
+                                               updateData();
+                                       }, true);
+
+                                       // All watching mode
+                                       if ($scope.watching === "true") {
+                                               $scope.$watch("[chart, 
config]", function(newValue, oldValue) {
+                                                       
if(angular.equals(newValue, oldValue)) return;
+                                                       initChart();
+                                               }, true);
+                                       }
+                               }
+                       });
+
+                       // Holder inject
+                       _holder_updateTimes = 0;
+                       _holder = {
+                               element: $element,
+                               refresh: function() {
+                                       setTimeout(function() {
+                                               updateData();
+                                       }, 0);
+                               },
+                               refreshAll: function() {
+                                       setTimeout(function() {
+                                               initChart();
+                                       }, 0);
+                               }
+                       };
+
+                       Object.defineProperty(_holder, 'chart', {
+                               get: function() {return _chart;}
+                       });
+
+                       $scope.$watch("holder", function() {
+                               // Holder times update
+                               setTimeout(function() {
+                                       _holder_updateTimes = 0;
+                               }, 0);
+                               _holder_updateTimes += 1;
+                               if(_holder_updateTimes > 100) throw "Holder 
conflict";
+
+                               $scope.holder = _holder;
+                       });
+
+                       // 
================================================================
+                       // =                           Start Up                 
          =
+                       // 
================================================================
+                       initChart();
+
+                       // 
================================================================
+                       // =                           Clean Up                 
          =
+                       // 
================================================================
+                       $scope.$on('$destroy', function() {
+                               destroy();
+                       });
+               },
+               template :
+               '<div>' +
+                       '<h3 title="{{title || config.title}}">{{title || 
config.title}}</h3>' +
+               '</div>',
+               replace: true
+       };
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/components/sortTable.js
----------------------------------------------------------------------
diff --git 
a/eagle-webservice/src/main/webapp/_app/public/js/components/sortTable.js 
b/eagle-webservice/src/main/webapp/_app/public/js/components/sortTable.js
new file mode 100644
index 0000000..bdcbca4
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/sortTable.js
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('sorttable', function($compile) {
+       'use strict';
+
+       return {
+               restrict : 'AE',
+               scope: {
+                       source: '=',
+                       search: '=?search',
+                       searchfunc: "=?searchfunc",
+
+                       orderKey: "@?sort",
+
+                       maxSize: "=?maxSize",
+               },
+               controller: function($scope, $element, $attrs) {
+                       // Initialization
+                       $scope.app = app;
+                       $scope.common = common;
+                       $scope._parent = $scope.$parent;
+
+                       $scope.pageNumber = $scope.pageNumber || 1;
+                       $scope.pageSize = $scope.pageSize || 10;
+
+                       $scope.maxSize = $scope.maxSize || 10;
+
+                       // Search box
+                       if($scope.search !== false) {
+                               var $search = $(
+                                       '<div style="overflow:hidden;">' +
+                                               '<div class="row">' +
+                                                       '<div 
class="col-xs-4">' +
+                                                               '<div 
class="search-box">' +
+                                                                       '<input 
type="search" class="form-control input-sm" placeholder="Search" 
ng-model="search" />' +
+                                                                       '<span 
class="fa fa-search"></span>' +
+                                                               '</div>' +
+                                                       '</div>' +
+                                               '</div>' +
+                                       '</div>'
+                               ).prependTo($element);
+                               $compile($search)($scope);
+                       }
+
+                       // List head
+                       $scope.doSort = function(path) {
+                               if($scope.orderKey === path) {
+                                       $scope.orderKey = "-" + path;
+                               } else {
+                                       $scope.orderKey = path;
+                               }
+                       };
+                       $scope.checkSortClass = function(key) {
+                               if($scope.orderKey === key) {
+                                       return "fa fa-sort-asc";
+                               } else if($scope.orderKey === "-" + key) {
+                                       return "fa fa-sort-desc";
+                               }
+                               return "fa fa-sort";
+                       };
+
+                       var $listHead = $element.find("thead > tr");
+                       $listHead.find("> th").each(function() {
+                               var $th = $(this);
+                               $th.addClass("noSelect");
+
+                               var _sortpath = $th.attr("sortpath");
+                               if(_sortpath) {
+                                       $th.attr("ng-click", "doSort('" + 
_sortpath + "')");
+                                       $th.prepend('<span 
ng-class="checkSortClass(\'' + _sortpath + '\')"></span>');
+                               }
+                       });
+                       $compile($listHead)($scope);
+
+                       // List body
+                       var $listBody = $element.find("tbody > tr");
+                       $listBody.attr("ng-repeat", 'item in (filteredList = 
(source | filter: ' + ($scope.searchfunc ? 'searchfunc' : 'search') + ' | 
orderBy: orderKey)).slice((pageNumber - 1) * pageSize, pageNumber * pageSize)');
+                       $compile($listBody)($scope);
+
+                       // Navigation
+                       var $navigation = $(
+                               '<div style="overflow:hidden;">' +
+                                       '<div class="row">' +
+                                               '<div class="col-xs-5">' +
+                                                       'show {{(pageNumber - 
1) * pageSize + 1}} to {{pageNumber * pageSize}} of {{filteredList.length}} 
items' +
+                                               '</div>' +
+                                               '<div class="col-xs-7 
text-right">' +
+                                                       '<uib-pagination 
total-items="filteredList.length" ng-model="pageNumber" boundary-links="true" 
items-per-page="pageSize" num-pages="numPages" 
max-size="maxSize"></uib-pagination>' +
+                                               '</div>' +
+                                       '</div>' +
+                               '</div>'
+                       ).appendTo($element);
+                       $compile($navigation)($scope);
+               },
+               replace: false
+       };
+});

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/components/sortable.js
----------------------------------------------------------------------
diff --git 
a/eagle-webservice/src/main/webapp/_app/public/js/components/sortable.js 
b/eagle-webservice/src/main/webapp/_app/public/js/components/sortable.js
new file mode 100644
index 0000000..c98c732
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/sortable.js
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('uieSortable', function($rootScope) {
+       'use strict';
+
+       var COLLECTION_MATCH = 
/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/;
+
+       var _move = false;
+       var _selectElement;
+       var _overElement;
+       var _mockElement;
+       var _offsetX, _offsetY;
+       var _mouseDownPageX, _mouseDownPageY;
+
+       function doMock(element, event) {
+               var _offset = element.offset();
+               if(_mockElement) _mockElement.remove();
+
+               // Create mock element
+               _mockElement = element.clone(false).appendTo("body");
+               _mockElement.addClass("sortable-mock-element");
+               _mockElement.css({
+                       display: "block",
+                       position: "absolute",
+                       "pointer-events": "none",
+                       "z-index": 10001,
+                       padding: element.css("padding"),
+                       margin: element.css("margin")
+               });
+               _mockElement.width(element.width());
+               _mockElement.height(element.height());
+
+               _mockElement.offset(_offset);
+               _offsetX = event.pageX - _offset.left;
+               _offsetY = event.pageY - _offset.top;
+       }
+
+       $(window).on("mousemove", function(event) {
+               if(!_move) return;
+               event.preventDefault();
+
+               _mockElement.offset({
+                       left: event.pageX - _offsetX,
+                       top: event.pageY - _offsetY
+               });
+       });
+
+       $(window).on("mouseup", function() {
+               if(!_move) {
+                       _overElement = null;
+                       _selectElement = null;
+                       _mockElement = null;
+                       return;
+               }
+               _move = false;
+
+               if(_overElement) {
+                       _overElement.removeClass("sortable-enter");
+
+                       if(_overElement[0] !== _selectElement[0]) {
+                               // Process switch
+                               var _oriHolder = _selectElement.holder;
+                               var _tgtHolder = _overElement.holder;
+                               var _oriSortableScope = _oriHolder.scope;
+                               var _tgtSortableScope = _tgtHolder.scope;
+                               var _oriScope = 
angular.element(_selectElement).scope();
+                               var _tgtScope = 
angular.element(_overElement).scope();
+
+                               var _oriRepeat = 
_selectElement.closest("[ng-repeat]").attr("ng-repeat");
+                               var _tgtRepeat = 
_overElement.closest("[ng-repeat]").attr("ng-repeat");
+                               var _oriMatch = 
_oriRepeat.match(COLLECTION_MATCH)[2];
+                               var _tgtMatch = 
_tgtRepeat.match(COLLECTION_MATCH)[2];
+                               var _oriCollection = 
_oriScope.$parent.$eval(_oriMatch);
+                               var _tgtCollection = 
_tgtScope.$parent.$eval(_tgtMatch);
+                               var _oriIndex = 
$.inArray(_oriCollection[_oriScope.$index], _oriSortableScope.ngModel);
+                               var _tgtIndex = 
$.inArray(_tgtCollection[_tgtScope.$index], _tgtSortableScope.ngModel);
+
+                               var _oriUnit = 
_oriSortableScope.ngModel[_oriIndex];
+                               var _tgtUnit = 
_tgtSortableScope.ngModel[_tgtIndex];
+                               _oriSortableScope.ngModel[_oriIndex] = _tgtUnit;
+                               _tgtSortableScope.ngModel[_tgtIndex] = _oriUnit;
+
+                               // Trigger event
+                               _oriHolder.change(_oriUnit, _tgtUnit);
+                               if (_oriHolder !== _tgtHolder) 
_tgtHolder.change(_oriUnit, _tgtUnit);
+
+                               $rootScope.$apply();
+                       }
+               }
+
+               if(_mockElement) _mockElement.remove();
+
+               _overElement = null;
+               _selectElement = null;
+               _mockElement = null;
+       });
+
+       return {
+               require: 'ngModel',
+               restrict : 'AE',
+               scope: {
+                       ngModel: "=",
+                       sortableEnabled: "=?sortableEnabled",
+                       sortableUpdateFunc: "=?sortableUpdateFunc"
+               },
+               link: function($scope, $element, $attrs, $ctrl) {
+                       var _holder = {
+                               scope: $scope,
+                               change: function(source, target) {
+                                       if($scope.sortableUpdateFunc) 
$scope.sortableUpdateFunc(source, target);
+                               }
+                       };
+
+                       $element.on("mousedown", ">", function(event) {
+                               if($scope.sortableEnabled === false) return;
+
+                               _selectElement = $(this);
+                               _selectElement.holder = _holder;
+
+                               _mouseDownPageX = event.pageX;
+                               _mouseDownPageY = event.pageY;
+
+                               event.preventDefault();
+                       });
+
+                       $element.on("mousemove", ">", function(event) {
+                               if(_selectElement && !_move && 
common.math.distance(_mouseDownPageX, _mouseDownPageY, event.pageX, 
event.pageY) > 10) {
+                                       _move = true;
+                                       _overElement = _selectElement;
+                                       _overElement.addClass("sortable-enter");
+
+                                       doMock(_selectElement, event);
+                               }
+                       });
+
+                       $element.on("mouseenter", ">", function() {
+                               if(!_move) return;
+                               _overElement = $(this);
+                               _overElement.holder = _holder;
+                               _overElement.addClass("sortable-enter");
+                       });
+                       $element.on("mouseleave", ">", function() {
+                               if(!_move) return;
+                               $(this).removeClass("sortable-enter");
+                               _overElement = null;
+                       });
+               },
+               replace: false
+       };
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/components/tabs.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/components/tabs.js 
b/eagle-webservice/src/main/webapp/_app/public/js/components/tabs.js
new file mode 100644
index 0000000..21c4a4a
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/tabs.js
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('tabs', function() {
+       'use strict';
+
+       return {
+               restrict: 'AE',
+               transclude: {
+                       'header': '?header',
+                       'pane': 'pane',
+                       'footer': '?footer'
+               },
+               scope : {
+                       title: "@?title",
+                       icon: "@",
+                       selected: "@?selected",
+                       holder: "=?holder",
+                       sortableModel: "=?sortableModel",
+
+                       menuList: "=?menu"
+               },
+
+               controller: function($scope, $element, $attrs, $timeout) {
+                       var transDuration = 
$.fn.tab.Constructor.TRANSITION_DURATION;
+                       var transTimer = null;
+                       var _holder, _holder_updateTimes;
+
+                       var $header, $footer;
+
+                       $scope.paneList = [];
+                       $scope.selectedPane = null;
+                       $scope.inPane = null;
+                       $scope.activePane = null;
+
+                       // ================== Function ==================
+                       $scope.getPaneList = function() {
+                               return !$scope.title ? $scope.paneList : 
$scope.paneList.slice().reverse();
+                       };
+
+                       $scope.setSelect = function(pane) {
+                               if(typeof pane === "string") {
+                                       pane = common.array.find(pane, 
$scope.paneList, "title");
+                               } else if(typeof pane === "number") {
+                                       pane = (pane + $scope.paneList.length) 
% $scope.paneList.length;
+                                       pane = $scope.paneList[pane];
+                               }
+
+                               $scope.activePane = $scope.selectedPane || pane;
+                               $scope.selectedPane = pane;
+                               $scope.inPane = null;
+
+                               if(transTimer) $timeout.cancel(transTimer);
+                               transTimer = $timeout(function() {
+                                       $scope.activePane = $scope.selectedPane;
+                                       $scope.inPane = $scope.selectedPane;
+                               }, transDuration);
+                       };
+
+                       $scope.getMenuList = function() {
+                               if($scope.selectedPane && 
$scope.selectedPane.menuList) {
+                                       return $scope.selectedPane.menuList;
+                               }
+                               return $scope.menuList;
+                       };
+
+                       $scope.tabSwitchUpdate = function (src, tgt) {
+                               var _srcIndex = $.inArray(src.data, 
$scope.sortableModel);
+                               var _tgtIndex = $.inArray(tgt.data, 
$scope.sortableModel);
+
+                               if(_srcIndex !== -1 && _tgtIndex !== -1) {
+                                       $scope.sortableModel[_srcIndex] = 
tgt.data;
+                                       $scope.sortableModel[_tgtIndex] = 
src.data;
+                               }
+                       };
+
+                       // =================== Linker ===================
+                       function _linkerProperties(pane) {
+                               Object.defineProperties(pane, {
+                                       selected: {
+                                               get: function () {
+                                                       return 
$scope.selectedPane === this;
+                                               }
+                                       },
+                                       active: {
+                                               get: function () {
+                                                       return 
$scope.activePane === this;
+                                               }
+                                       },
+                                       in: {
+                                               get: function () {
+                                                       return $scope.inPane 
=== this;
+                                               }
+                                       }
+                               });
+                       }
+
+                       this.addPane = function(pane) {
+                               $scope.paneList.push(pane);
+
+                               // Register properties
+                               _linkerProperties(pane);
+
+                               // Update select pane
+                               if(pane.title === $scope.selected || 
!$scope.selectedPane) {
+                                       $scope.setSelect(pane);
+                               }
+                       };
+
+                       this.deletePane = function(pane) {
+                               common.array.remove(pane, $scope.paneList);
+
+                               if($scope.selectedPane === pane) {
+                                       $scope.selectedPane = $scope.activePane 
= $scope.inPane = $scope.paneList[0];
+                               }
+                       };
+
+                       // ===================== UI =====================
+                       $header = $element.find("> .nav-tabs-custom > 
.box-body");
+                       $footer = $element.find("> .nav-tabs-custom > 
.box-footer");
+
+                       $scope.hasHeader = function() {
+                               return !!$header.children().length;
+                       };
+                       $scope.hasFooter = function() {
+                               return !!$footer.children().length;
+                       };
+
+                       // ================= Interface ==================
+                       _holder_updateTimes = 0;
+                       _holder = {
+                               scope: $scope,
+                               element: $element,
+                               setSelect: $scope.setSelect
+                       };
+
+                       Object.defineProperty(_holder, 'selectedPane', {
+                               get: function() {return $scope.selectedPane;}
+                       });
+
+                       $scope.$watch("holder", function(newValue, oldValue) {
+                               // Holder times update
+                               setTimeout(function() {
+                                       _holder_updateTimes = 0;
+                               }, 0);
+                               _holder_updateTimes += 1;
+                               if(_holder_updateTimes > 100) throw "Holder 
conflict";
+
+                               $scope.holder = _holder;
+                       });
+               },
+
+               template :
+                       '<div class="nav-tabs-custom">' +
+                               // Menu
+                               '<div class="box-tools pull-right" 
ng-if="getMenuList() && getMenuList().length">' +
+                                       '<div ng-repeat="menu in getMenuList() 
track by $index" class="inline">' +
+                                               // Button
+                                               '<button class="btn 
btn-box-tool" ng-click="menu.func($event)" ng-if="!menu.list"' +
+                                                       ' 
uib-tooltip="{{menu.title}}" tooltip-enable="menu.title" 
tooltip-append-to-body="true">' +
+                                                       '<span class="fa 
fa-{{menu.icon}}"></span>' +
+                                               '</button>' +
+
+                                               // Dropdown Group
+                                               '<div class="btn-group" 
ng-if="menu.list">' +
+                                                       '<button class="btn 
btn-box-tool dropdown-toggle" data-toggle="dropdown"' +
+                                                               ' 
uib-tooltip="{{menu.title}}" tooltip-enable="menu.title" 
tooltip-append-to-body="true">' +
+                                                               '<span 
class="fa fa-{{menu.icon}}"></span>' +
+                                                       '</button>' +
+                                                       '<ul 
class="dropdown-menu left" role="menu">' +
+                                                               '<li 
ng-repeat="item in menu.list track by $index" ng-class="{danger: item.danger, 
disabled: item.disabled}">' +
+                                                                       '<a 
ng-click="!item.disabled && item.func($event)" ng-class="{strong: 
item.strong}">' +
+                                                                               
'<span class="fa fa-{{item.icon}}"></span> {{item.title}}' +
+                                                                       '</a>' +
+                                                               '</li>' +
+                                                       '</ul>' +
+                                               '</div>' +
+                                       '</div>' +
+                               '</div>' +
+
+                               '<ul uie-sortable 
sortable-enabled="!!sortableModel" sortable-update-func="tabSwitchUpdate" 
ng-model="paneList" class="nav nav-tabs" ng-class="{\'pull-right\': title}">' +
+                                       // Tabs
+                                       '<li ng-repeat="pane in getPaneList() 
track by $index" ng-class="{active: selectedPane === pane}">' +
+                                               '<a 
ng-click="setSelect(pane);">{{pane.title}}</a>' +
+                                       '</li>' +
+
+                                       // Title
+                                       '<li class="pull-left header" 
ng-if="title">' +
+                                               '<i class="fa fa-{{icon}}" 
ng-if="icon"></i> {{title}}' +
+                                       '</li>' +
+
+                               '</ul>' +
+                               '<div class="box-body" ng-transclude="header" 
ng-show="paneList.length && hasHeader()"></div>' +
+                               '<div class="tab-content" 
ng-transclude="pane"></div>' +
+                               '<div class="box-footer" ng-transclude="footer" 
ng-show="paneList.length && hasFooter()"></div>' +
+                       '</div>'
+       };
+}).directive('pane', function() {
+       'use strict';
+
+       return {
+               require : '^tabs',
+               restrict : 'AE',
+               transclude : true,
+               scope : {
+                       title : '@',
+                       data: '=?data',
+                       menuList: "=?menu"
+               },
+               link : function(scope, element, attrs, tabsController) {
+                       tabsController.addPane(scope);
+                       scope.$on('$destroy', function() {
+                               tabsController.deletePane(scope);
+                       });
+               },
+               template : '<div class="tab-pane fade" ng-class="{active: 
active, in: in}" ng-transclude></div>',
+               replace : true
+       };
+}).directive('footer', function() {
+       'use strict';
+
+       return {
+               require : '^tabs',
+               restrict : 'AE',
+               transclude : true,
+               scope : {},
+               controller: function($scope, $element) {
+               },
+               template : '<div ng-transclude></div>',
+               replace : true
+       };
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/ctrl/authController.js
----------------------------------------------------------------------
diff --git 
a/eagle-webservice/src/main/webapp/_app/public/js/ctrl/authController.js 
b/eagle-webservice/src/main/webapp/_app/public/js/ctrl/authController.js
new file mode 100644
index 0000000..dbdb704
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/ctrl/authController.js
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+       'use strict';
+
+       var eagleControllers = angular.module('eagleControllers');
+       // =============================================================
+       // =                     User Profile List                     =
+       // =============================================================
+       eagleControllers.controller('authLoginCtrl', function (PageConfig, 
Site, Authorization, Application, $scope) {
+               PageConfig.hideSidebar = true;
+               PageConfig.hideApplication = true;
+               PageConfig.hideSite = true;
+               PageConfig.hideUser = true;
+
+               $scope.username = "";
+               $scope.password = "";
+               $scope.lock = false;
+               $scope.loginSuccess = false;
+
+               if(localStorage) {
+                       $scope.rememberUser = 
localStorage.getItem("rememberUser") !== "false";
+
+                       if($scope.rememberUser) {
+                               $scope.username = 
localStorage.getItem("username");
+                               $scope.password = 
localStorage.getItem("password");
+                       }
+               }
+
+               // UI
+               setTimeout(function () {
+                       $("#username").focus();
+               });
+
+               // Login
+               $scope.login = function (event, forceSubmit) {
+                       if ($scope.lock) return;
+
+                       if (event.which === 13 || forceSubmit) {
+                               $scope.lock = true;
+
+                               Authorization.login($scope.username, 
$scope.password).then(function (success) {
+                                       if (success) {
+                                               // Check user remember
+                                               
localStorage.setItem("rememberUser", $scope.rememberUser);
+                                               if($scope.rememberUser) {
+                                                       
localStorage.setItem("username", $scope.username);
+                                                       
localStorage.setItem("password", $scope.password);
+                                               } else {
+                                                       
localStorage.removeItem("username");
+                                                       
localStorage.removeItem("password");
+                                               }
+
+                                               // Initial environment
+                                               $scope.loginSuccess = true;
+                                               console.log("[Login] Login 
success! Reload data...");
+                                               
Authorization.reload().then(function() {}, function() {console.warn("Site 
error!");});
+                                               
Application.reload().then(function() {}, function() {console.warn("Site 
error!");});
+                                               Site.reload().then(function() 
{}, function() {console.warn("Site error!");});
+                                               Authorization.path(true);
+                                       } else {
+                                               $.dialog({
+                                                       title: "OPS",
+                                                       content: "User name or 
password not correct."
+                                               }).on("hidden.bs.modal", 
function () {
+                                                       $("#username").focus();
+                                               });
+                                       }
+                               }).finally(function () {
+                                       $scope.lock = false;
+                               });
+                       }
+               };
+       });
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/ctrl/configurationController.js
----------------------------------------------------------------------
diff --git 
a/eagle-webservice/src/main/webapp/_app/public/js/ctrl/configurationController.js
 
b/eagle-webservice/src/main/webapp/_app/public/js/ctrl/configurationController.js
new file mode 100644
index 0000000..e59198d
--- /dev/null
+++ 
b/eagle-webservice/src/main/webapp/_app/public/js/ctrl/configurationController.js
@@ -0,0 +1,377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+       'use strict';
+
+       var eagleControllers = angular.module('eagleControllers');
+
+       // =============================================================
+       // =                         Function                          =
+       // =============================================================
+       function watchEdit($scope, key) {
+               $scope.changed = false;
+               setTimeout(function() {
+                       var _func = $scope.$watch(key, function(newValue, 
oldValue) {
+                               if(angular.equals(newValue, oldValue)) return;
+                               $scope.changed = true;
+                               _func();
+                       }, true);
+               }, 100);
+       }
+
+       // =============================================================
+       // =                       Configuration                       =
+       // =============================================================
+       // ========================== Feature ==========================
+       eagleControllers.controller('configFeatureCtrl', function ($scope, 
PageConfig, Application, Entities, UI) {
+               PageConfig.hideApplication = true;
+               PageConfig.hideSite = true;
+               $scope._pageLock = false;
+
+               PageConfig
+                       .addNavPath("Home", "/")
+                       .addNavPath("Feature Config");
+
+               // ================== Feature ==================
+               // Current feature
+               $scope.feature = Application.featureList[0];
+               $scope.setFeature = function (feature) {
+                       $scope.feature = feature;
+               };
+
+               // Feature list
+               $scope.features = {};
+               $.each(Application.featureList, function(i, feature) {
+                       $scope.features[feature.tags.feature] = $.extend({}, 
feature, true);
+               });
+
+               // Create feature
+               $scope.newFeature = function() {
+                       UI.createConfirm("Feature", {}, [
+                               {name: "Feature Name", field: "name"}
+                       ], function(entity) {
+                               if(entity.name && $.map($scope.features, 
function(feature, name) {
+                                               return name.toUpperCase() === 
entity.name.toUpperCase() ? true : null;
+                                       }).length) {
+                                       return "Feature name conflict!";
+                               }
+                       }).then(null, null, function(holder) {
+                               Entities.updateEntity(
+                                       "FeatureDescService",
+                                       {tags: {feature: holder.entity.name}},
+                                       {timestamp: false}
+                               )._promise.then(function() {
+                                       holder.closeFunc();
+                                       location.reload();
+                               });
+                       });
+               };
+
+               // Delete feature
+               $scope.deleteFeature = function(feature) {
+                       UI.deleteConfirm(feature.tags.feature).then(null, null, 
function(holder) {
+                               Entities.delete("FeatureDescService", {feature: 
feature.tags.feature})._promise.then(function() {
+                                       holder.closeFunc();
+                                       location.reload();
+                               });
+                       });
+               };
+
+               // Save feature
+               $scope.saveAll = function() {
+                       $scope._pageLock = true;
+                       var _list = $.map($scope.features, function(feature) {
+                               return feature;
+                       });
+                       Entities.updateEntity("FeatureDescService", _list, 
{timestamp: false})._promise.success(function() {
+                               location.reload();
+                       }).finally(function() {
+                               $scope._pageLock = false;
+                       });
+               };
+
+               // Watch config update
+               watchEdit($scope, "features");
+       });
+
+       // ======================== Application ========================
+       eagleControllers.controller('configApplicationCtrl', function ($scope, 
$timeout, PageConfig, Application, Entities, Feature, UI) {
+               PageConfig.hideApplication = true;
+               PageConfig.hideSite = true;
+               $scope._pageLock = false;
+
+               PageConfig
+                       .addNavPath("Home", "/")
+                       .addNavPath("Application Config");
+
+               // ================ Application ================
+               // Current application
+               $scope.application = Application.list[0];
+               $scope.setApplication = function (application) {
+                       $scope.application = application;
+               };
+
+               // Application list
+               $scope.applications = {};
+               $.each(Application.list, function(i, application) {
+                       var _application = 
$scope.applications[application.tags.application] = $.extend({}, application, 
{features: application.features.slice()}, true);
+                       _application.optionalFeatures = 
$.map(Application.featureList, function(feature) {
+                               var featurePlugin = 
Feature.get(feature.tags.feature);
+                               if(featurePlugin.config.global) return null;
+                               if(!common.array.find(feature.tags.feature, 
_application.features)) {
+                                       return feature.tags.feature;
+                               }
+                       });
+               });
+
+               // Create application
+               $scope.newApplication = function() {
+                       UI.createConfirm("Application", {}, [
+                               {name: "Application Name", field: "name"}
+                       ], function(entity) {
+                               if(entity.name && $.map($scope.applications, 
function(application, name) {
+                                               return name.toUpperCase() === 
entity.name.toUpperCase() ? true : null;
+                                       }).length) {
+                                       return "Application name conflict!";
+                               }
+                       }).then(null, null, function(holder) {
+                               Entities.updateEntity(
+                                       "ApplicationDescService",
+                                       {tags: {application: 
holder.entity.name}},
+                                       {timestamp: false}
+                               )._promise.then(function() {
+                                       holder.closeFunc();
+                                       location.reload();
+                               });
+                       });
+               };
+
+               // Delete application
+               $scope.deleteApplication = function(application) {
+                       
UI.deleteConfirm(application.tags.application).then(null, null, 
function(holder) {
+                               Entities.delete("ApplicationDescService", 
{application: application.tags.application})._promise.then(function() {
+                                       holder.closeFunc();
+                                       location.reload();
+                               });
+                       });
+               };
+
+               // ================= Function ==================
+               // Configuration check
+               $scope.configCheck = function(config) {
+                       if(config && !common.parseJSON(config, false)) {
+                               return "Invalid JSON format";
+                       }
+               };
+
+               // Feature
+               $scope._feature = "";
+               function highlightFeature(feature) {
+                       $scope._feature = feature;
+
+                       $timeout(function() {
+                               $scope._feature = "";
+                       }, 100);
+               }
+
+               $scope.addFeature = function(feature, application) {
+                       application.features.push(feature);
+                       common.array.remove(feature, 
application.optionalFeatures);
+                       highlightFeature(feature);
+                       $scope.changed = true;
+               };
+
+               $scope.removeFeature = function(feature, application) {
+                       application.optionalFeatures.push(feature);
+                       common.array.remove(feature, application.features);
+                       $scope.changed = true;
+               };
+
+               $scope.moveFeature = function(feature, list, offset) {
+                       common.array.moveOffset(feature, list, offset);
+                       highlightFeature(feature);
+                       $scope.changed = true;
+               };
+
+               // Save feature
+               $scope.saveAll = function() {
+                       $scope._pageLock = true;
+
+                       var _list = $.map($scope.applications, 
function(application) {
+                               return application;
+                       });
+                       Entities.updateEntity("ApplicationDescService", _list, 
{timestamp: false})._promise.success(function() {
+                               location.reload();
+                       }).finally(function() {
+                               $scope._pageLock = false;
+                       });
+               };
+
+               // Watch config update
+               watchEdit($scope, "applications");
+       });
+
+       // ============================ Site ===========================
+       eagleControllers.controller('configSiteCtrl', function ($scope, 
$timeout, PageConfig, Site, Application, Entities, UI) {
+               PageConfig.hideApplication = true;
+               PageConfig.hideSite = true;
+               $scope._pageLock = false;
+
+               PageConfig
+                       .addNavPath("Home", "/")
+                       .addNavPath("Site Config");
+
+               // =================== Site ====================
+               // Current site
+               $scope.site = Site.list[0];
+               $scope.setSite = function (site) {
+                       $scope.site = site;
+               };
+
+
+               // Site list
+               $scope.sites = {};
+               $.each(Site.list, function(i, site) {
+                       var _site = $scope.sites[site.tags.site] = $.extend({}, 
site, true);
+                       var _applications = [];
+                       var _optionalApplications = [];
+
+                       Object.defineProperties(_site, {
+                               applications: {
+                                       get: function() {return _applications;}
+                               },
+                               optionalApplications: {
+                                       get: function() {return 
_optionalApplications;}
+                               }
+                       });
+
+                       $.each(Application.list, function(i, application) {
+                               var _application = 
site.applicationList.set[application.tags.application];
+                               if(_application && _application.enabled) {
+                                       _site.applications.push(_application);
+                               } else {
+                                       if(_application) {
+                                               
_site.optionalApplications.push(_application);
+                                       } else {
+                                               
_site.optionalApplications.push({
+                                                       prefix: 
"eagleSiteApplication",
+                                                       config: "",
+                                                       enabled: false,
+                                                       tags: {
+                                                               application: 
application.tags.application,
+                                                               site: 
site.tags.site
+                                                       }
+                                               });
+                                       }
+                               }
+                       });
+               });
+
+               // Create site
+               $scope.newSite = function() {
+                       UI.createConfirm("Site", {}, [
+                               {name: "Site Name", field: "name"}
+                       ], function(entity) {
+                               if(entity.name && $.map($scope.sites, 
function(site, name) {
+                                               return name.toUpperCase() === 
entity.name.toUpperCase() ? true : null;
+                                       }).length) {
+                                       return "Site name conflict!";
+                               }
+                       }).then(null, null, function(holder) {
+                               Entities.updateEntity(
+                                       "SiteDescService",
+                                       {enabled: true, tags: {site: 
holder.entity.name}},
+                                       {timestamp: false}
+                               )._promise.then(function() {
+                                       holder.closeFunc();
+                                       location.reload();
+                               });
+                       });
+               };
+
+               // Delete site
+               $scope.deleteSite = function(site) {
+                       UI.deleteConfirm(site.tags.site).then(null, null, 
function(holder) {
+                               Entities.delete("SiteDescService", {site: 
site.tags.site})._promise.then(function() {
+                                       holder.closeFunc();
+                                       location.reload();
+                               });
+                       });
+               };
+
+               // ================= Function ==================
+               $scope._application = "";
+               function highlightApplication(application) {
+                       $scope._application = application;
+
+                       $timeout(function() {
+                               $scope._application = "";
+                       }, 100);
+               }
+
+               $scope.addApplication = function(application, site) {
+                       site.applications.push(application);
+                       common.array.remove(application, 
site.optionalApplications);
+                       application.enabled = true;
+                       highlightApplication(application);
+                       $scope.changed = true;
+               };
+
+               $scope.removeApplication = function(application, site) {
+                       site.optionalApplications.push(application);
+                       common.array.remove(application, site.applications);
+                       application.enabled = false;
+                       $scope.changed = true;
+               };
+
+               $scope.setApplication = function(application) {
+                       var _oriConfig = application.config;
+                       UI.updateConfirm("Application", {config: _oriConfig}, [
+                               {name: "Configuration", field: "config", type: 
"blob"}
+                       ], function(entity) {
+                               if(entity.config !== "" && 
!common.properties.check(entity.config)) {
+                                       return "Invalid Properties format";
+                               }
+                       }).then(null, null, function(holder) {
+                               application.config = holder.entity.config;
+                               holder.closeFunc();
+                               if(_oriConfig !== application.config) 
$scope.changed = true;
+                       });
+               };
+
+               // Save feature
+               $scope.saveAll = function() {
+                       $scope._pageLock = true;
+
+                       var _list = $.map($scope.sites, function(site) {
+                               var _clone = $.extend({applications: 
site.applications.concat(site.optionalApplications)}, site);
+                               return _clone;
+                       });
+
+                       Entities.updateEntity("SiteDescService", _list, 
{timestamp: false, hook: true})._promise.success(function() {
+                               location.reload();
+                       }).finally(function() {
+                               $scope._pageLock = false;
+                       });
+               };
+
+               // Watch config update
+               watchEdit($scope, "sites");
+       });
+})();
\ No newline at end of file

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

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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..187adb4
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/srv/applicationSrv.js
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+       'use strict';
+
+       var serviceModule = angular.module('eagle.service');
+       var eagleApp = angular.module('eagleApp');
+
+       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, reload) {
+                       if(arguments.length && _current !== app) {
+                               var _prev = _current;
+                               _current = app;
+
+                               if(sessionStorage && _current) {
+                                       sessionStorage.setItem("application", 
_current.tags.application);
+                               }
+
+                               if(_prev && reload !== false) {
+                                       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?_=" + eagleApp._TRS();
+                                       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
+                                                       group: {
+                                                               get: function 
() {
+                                                                       return 
this.groupName || "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

Reply via email to