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