<commit_before>/**
 * 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 isIgnoredJmxKeys = function (key) {
        return key == 'name' || key == 'modelerType' || key == "$$hashKey" ||
            key.match(/tag.*/);
    };
    angular.module('ozone', ['nvd3', 'ngRoute']);
    angular.module('ozone').config(function ($routeProvider) {
        $routeProvider
            .when("/", {
                templateUrl: "main.html"
            })
            .when("/metrics/rpc", {
                template: "<rpc-metrics></rpc-metrics>"
            })
            .when("/config", {
                template: "<config></config>"
            })
    });
    angular.module('ozone').component('overview', {
        templateUrl: 'static/templates/overview.html',
        transclude: true,
        controller: function ($http) {
            var ctrl = this;
            $http.get("/jmx?qry=Hadoop:service=*,name=*,component=ServerRuntime")
                .then(function (result) {
                    ctrl.jmx = result.data.beans[0]
                })
        }
    });
    angular.module('ozone').component('jvmParameters', {
        templateUrl: 'static/templates/jvm.html',
        controller: function ($http) {
            var ctrl = this;
            $http.get("/jmx?qry=java.lang:type=Runtime")
                .then(function (result) {
                    ctrl.jmx = result.data.beans[0];

                    //convert array to a map
                    var systemProperties = {};
                    for (var idx in ctrl.jmx.SystemProperties) {
                        var item = ctrl.jmx.SystemProperties[idx];
                        systemProperties[item.key.replace(/\./g, "_")] = item.value;
                    }
                    ctrl.jmx.SystemProperties = systemProperties;
                })
        }
    });

    angular.module('ozone').component('rpcMetrics', {
        template: '
Rpc metrics
<tabs>' +
        '<pane ng-repeat="metric in $ctrl.metrics" ' +
        'title="Port {{metric[\'tag.port\']}}">' +
        '<rpc-metric jmxdata="metric"></rpc-metric></pane>' +
        '</tabs>',
        controller: function ($http) {
            var ctrl = this;
            $http.get("/jmx?qry=Hadoop:service=*,name=RpcActivityForPort*")
                .then(function (result) {
                    ctrl.metrics = result.data.beans;
                })
        }
    });
    angular.module('ozone').component('rpcMetric', {
        bindings: {
            jmxdata: '<'
        },
        templateUrl: 'static/templates/rpc-metrics.html',
        controller: function () {
            var ctrl = this;

            ctrl.percentileGraphOptions = {
                chart: {
                    type: 'discreteBarChart',
                    height: 450,
                    margin: {
                        top: 20,
                        right: 20,
                        bottom: 50,
                        left: 55
                    },
                    x: function (d) {
                        return d.label;
                    },
                    y: function (d) {
                        return d.value;
                    },
                    showValues: true,
                    valueFormat: function (d) {
                        return d3.format(',.1f')(d);
                    },
                    duration: 500,
                    xAxis: {
                        axisLabel: 'Percentage'
                    },
                    yAxis: {
                        axisLabel: 'Latency (ms)',
                        axisLabelDistance: -10
                    }
                }
            };

            ctrl.$onChanges = function (data) {
                var groupedMetrics = {}

                var createPercentageMetrics = function (metricName, window) {
                    groupedMetrics.percentiles = groupedMetrics['percentiles'] || {}
                    groupedMetrics.percentiles[window] = groupedMetrics.percentiles[window] || {};
                    groupedMetrics.percentiles[window][metricName] = groupedMetrics.percentiles[window][metricName] || {
                            graphdata: [{
                                key: window,
                                values: []
                            }], numOps: 0
                        };

                };
                var metrics = ctrl.jmxdata;
                for (var key in metrics) {
                    var percentile = key.match(/(.*Time)(\d+s)(\d+th)PercentileLatency/);
                    var percentileNumOps = key.match(/(.*Time)(\d+s)NumOps/);
                    var successFailures = key.match(/(.*)(Success|Failures)/);
                    var numAverages = key.match(/(.*Time)(NumOps|AvgTime)/);
                    if (percentile) {
                        var metricName = percentile[1];
                        var window = percentile[2];
                        var percentage = percentile[3]
                        createPercentageMetrics(metricName, window);

                        groupedMetrics.percentiles[window][metricName].graphdata[0]
                            .values.push({
                            label: percentage,
                            value: metrics[key]
                        })
                    } else if (successFailures) {
                        var metricName = successFailures[1];
                        groupedMetrics.successfailures = groupedMetrics['successfailures'] || {}
                        groupedMetrics.successfailures[metricName] = groupedMetrics.successfailures[metricName] || {
                                success: 0,
                                failures: 0
                            };
                        if (successFailures[2] == 'Success') {
                            groupedMetrics.successfailures[metricName].success = metrics[key];
                        } else {
                            groupedMetrics.successfailures[metricName].failures = metrics[key];
                        }

                    } else if (numAverages) {
                        var metricName = numAverages[1];
                        groupedMetrics.numavgs = groupedMetrics['numavgs'] || {}
                        groupedMetrics.numavgs[metricName] = groupedMetrics.numavgs[metricName] || {
                                numOps: 0,
                                avgTime: 0
                            };
                        if (numAverages[2] == 'NumOps') {
                            groupedMetrics.numavgs[metricName].numOps = metrics[key];
                        } else {
                            groupedMetrics.numavgs[metricName].avgTime = metrics[key];
                        }

                    } else if (percentileNumOps) {
                        var metricName = percentileNumOps[1];
                        var window = percentileNumOps[2];
                        createPercentageMetrics(metricName, window);
                        groupedMetrics.percentiles[window][metricName].numOps = metrics[key];
                    } else if (isIgnoredJmxKeys(key)) {
                        //ignore
                    } else {
                        groupedMetrics.others = groupedMetrics.others || [];
                        groupedMetrics.others.push({
                            'key': key,
                            'value': metrics[key]
                        });
                    }

                }
                ctrl.metrics = groupedMetrics;
            };

        }
    });
    angular.module('ozone')
        .component('tabs', {
            transclude: true,
            controller: function ($scope) {
                var ctrl = this;
                var panes = this.panes = [];
                this.select = function (pane) {
                    angular.forEach(panes, function (pane) {
                        pane.selected = false;
                    });
                    pane.selected = true;
                };
                this.addPane = function (pane) {
                    if (panes.length === 0) {
                        this.select(pane);
                    }
                    panes.push(pane);
                };
                this.click = function(pane) {
                    ctrl.select(pane);
                }
            },
            template: '

' +
            '
' +
            '{{pane.title}} ' +
            '

'
        })
        .component('pane', {
            transclude: true,
            require: {
                tabsCtrl: '^tabs'
            },
            bindings: {
                title: '@'
            },
            controller: function () {
                this.$onInit = function () {
                    this.tabsCtrl.addPane(this);
                };
            },
            template: '

'
        });

    angular.module('ozone').component('navmenu', {
        bindings: {
            metrics: '<'
        },
        templateUrl: '/static/templates/menu.html',
        controller: function ($http) {
            var ctrl = this;
            ctrl.docs = false;
            $http.head("/docs/index.html")
                .then(function (result) {
                    ctrl.docs = true;
                },function(){
                    ctrl.docs = false;
                });
        }
    });

    angular.module('ozone').component('config', {
        templateUrl: '/static/templates/config.html',
        controller: function ($scope, $http) {
            var ctrl = this;
            ctrl.selectedTags = [];

            $http.get("/conf?cmd=getOzoneTags&group=ozone")
                .then(function (response) {
                    ctrl.tags = response.data;

                    var excludedTags = ['CBLOCK', 'KSM', 'SCM'];
                    for (var i = 0; i < excludedTags.length; i++) {
                        var idx = ctrl.tags.indexOf(excludedTags[i]);
                        // Remove CBLOCK related properties
                        if (idx > -1) {
                            ctrl.tags.splice(idx, 1);
                        }
                    }
                    ctrl.loadAll();
                });

            ctrl.loadAll = function () {
                console.log("Displaying all configs");
                $http.get("/conf?cmd=getPropertyByTag&tags=" + ctrl.tags + "&group=ozone").then(function (response) {
                    ctrl.configs = response.data;
                    console.log(ctrl.configs)
                    for (var idx in ctrl.configs) {
                        var tags = []
                        var parsedTags = ctrl.configs[idx].tag.split(",");
                        for (var t in parsedTags) {
                            tags.push(parsedTags[t].trim())
                        }
                        ctrl.configs[idx].tag = tags;

                    };
                    ctrl.sortBy('name');
                });
            };

            ctrl.tagFilter = function (value, index, array) {
                if (!ctrl.selectedTags) {
                    return true;
                }
                var selected = true;
                for (var idx in ctrl.selectedTags) {
                    selected = selected && (value.tag.indexOf(ctrl.selectedTags[idx]) > -1);
                }
                return selected;
            };
            ctrl.configFilter = function (config) {
                return false;
            };
            ctrl.selected = function (tag) {
                return ctrl.selectedTags.includes(tag);
            };

            ctrl.allSelected = function () {
                return ctrl.selectedTags.indexOf('SCM') == -1
                    && ctrl.selectedTags.indexOf('KSM') == -1
            };

            ctrl.switchto = function (tag) {
                var tags = ctrl.selectedTags.filter(function (item) {
                    return item != 'KSM' && item != 'SCM';
                });
                if (tag) {
                    tags.push(tag);
                }
                ctrl.selectedTags = tags;
            };

            ctrl.select = function (tag) {
                var tagIdx = ctrl.selectedTags.indexOf(tag);
                if (tagIdx > -1) {
                    ctrl.selectedTags = ctrl.selectedTags.filter(function (item) {
                        return item != tag;
                    });
                } else {
                    ctrl.selectedTags.push(tag);
                }
                console.log("Tags selected:" + ctrl.selectedTags);
            };

            ctrl.sortBy = function (propertyName) {
                ctrl.reverse = (ctrl.propertyName === propertyName) ? !ctrl.reverse : false;
                ctrl.propertyName = propertyName;
            };

        }
    });

})();
<commit_msg>HDFS-12786. Ozone: add port/service names to the ksm/scm web ui. Contributed by Elek Marton.<commit_after>/**
 * 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 isIgnoredJmxKeys = function (key) {
        return key == 'name' || key == 'modelerType' || key == "$$hashKey" ||
            key.match(/tag.*/);
    };
    angular.module('ozone', ['nvd3', 'ngRoute']);
    angular.module('ozone').config(function ($routeProvider) {
        $routeProvider
            .when("/", {
                templateUrl: "main.html"
            })
            .when("/metrics/rpc", {
                template: "<rpc-metrics></rpc-metrics>"
            })
            .when("/config", {
                template: "<config></config>"
            })
    });
    angular.module('ozone').component('overview', {
        templateUrl: 'static/templates/overview.html',
        transclude: true,
        controller: function ($http) {
            var ctrl = this;
            $http.get("/jmx?qry=Hadoop:service=*,name=*,component=ServerRuntime")
                .then(function (result) {
                    ctrl.jmx = result.data.beans[0]
                })
        }
    });
    angular.module('ozone').component('jvmParameters', {
        templateUrl: 'static/templates/jvm.html',
        controller: function ($http) {
            var ctrl = this;
            $http.get("/jmx?qry=java.lang:type=Runtime")
                .then(function (result) {
                    ctrl.jmx = result.data.beans[0];

                    //convert array to a map
                    var systemProperties = {};
                    for (var idx in ctrl.jmx.SystemProperties) {
                        var item = ctrl.jmx.SystemProperties[idx];
                        systemProperties[item.key.replace(/\./g, "_")] = item.value;
                    }
                    ctrl.jmx.SystemProperties = systemProperties;
                })
        }
    });

    angular.module('ozone').component('rpcMetrics', {
        template: '
Rpc metrics
<tabs>' +
        '<pane ng-repeat="metric in $ctrl.metrics" ' +
        'title="{{metric[\'tag.serverName\']}} ({{metric[\'tag.port\']}})">' +
        '<rpc-metric jmxdata="metric"></rpc-metric></pane>' +
        '</tabs>',
        controller: function ($http) {
            var ctrl = this;
            $http.get("/jmx?qry=Hadoop:service=*,name=RpcActivityForPort*")
                .then(function (result) {
                    ctrl.metrics = result.data.beans;
                })
        }
    });
    angular.module('ozone').component('rpcMetric', {
        bindings: {
            jmxdata: '<'
        },
        templateUrl: 'static/templates/rpc-metrics.html',
        controller: function () {
            var ctrl = this;

            ctrl.percentileGraphOptions = {
                chart: {
                    type: 'discreteBarChart',
                    height: 450,
                    margin: {
                        top: 20,
                        right: 20,
                        bottom: 50,
                        left: 55
                    },
                    x: function (d) {
                        return d.label;
                    },
                    y: function (d) {
                        return d.value;
                    },
                    showValues: true,
                    valueFormat: function (d) {
                        return d3.format(',.1f')(d);
                    },
                    duration: 500,
                    xAxis: {
                        axisLabel: 'Percentage'
                    },
                    yAxis: {
                        axisLabel: 'Latency (ms)',
                        axisLabelDistance: -10
                    }
                }
            };

            ctrl.$onChanges = function (data) {
                var groupedMetrics = {}

                var createPercentageMetrics = function (metricName, window) {
                    groupedMetrics.percentiles = groupedMetrics['percentiles'] || {}
                    groupedMetrics.percentiles[window] = groupedMetrics.percentiles[window] || {};
                    groupedMetrics.percentiles[window][metricName] = groupedMetrics.percentiles[window][metricName] || {
                            graphdata: [{
                                key: window,
                                values: []
                            }], numOps: 0
                        };

                };
                var metrics = ctrl.jmxdata;
                for (var key in metrics) {
                    var percentile = key.match(/(.*Time)(\d+s)(\d+th)PercentileLatency/);
                    var percentileNumOps = key.match(/(.*Time)(\d+s)NumOps/);
                    var successFailures = key.match(/(.*)(Success|Failures)/);
                    var numAverages = key.match(/(.*Time)(NumOps|AvgTime)/);
                    if (percentile) {
                        var metricName = percentile[1];
                        var window = percentile[2];
                        var percentage = percentile[3]
                        createPercentageMetrics(metricName, window);

                        groupedMetrics.percentiles[window][metricName].graphdata[0]
                            .values.push({
                            label: percentage,
                            value: metrics[key]
                        })
                    } else if (successFailures) {
                        var metricName = successFailures[1];
                        groupedMetrics.successfailures = groupedMetrics['successfailures'] || {}
                        groupedMetrics.successfailures[metricName] = groupedMetrics.successfailures[metricName] || {
                                success: 0,
                                failures: 0
                            };
                        if (successFailures[2] == 'Success') {
                            groupedMetrics.successfailures[metricName].success = metrics[key];
                        } else {
                            groupedMetrics.successfailures[metricName].failures = metrics[key];
                        }

                    } else if (numAverages) {
                        var metricName = numAverages[1];
                        groupedMetrics.numavgs = groupedMetrics['numavgs'] || {}
                        groupedMetrics.numavgs[metricName] = groupedMetrics.numavgs[metricName] || {
                                numOps: 0,
                                avgTime: 0
                            };
                        if (numAverages[2] == 'NumOps') {
                            groupedMetrics.numavgs[metricName].numOps = metrics[key];
                        } else {
                            groupedMetrics.numavgs[metricName].avgTime = metrics[key];
                        }

                    } else if (percentileNumOps) {
                        var metricName = percentileNumOps[1];
                        var window = percentileNumOps[2];
                        createPercentageMetrics(metricName, window);
                        groupedMetrics.percentiles[window][metricName].numOps = metrics[key];
                    } else if (isIgnoredJmxKeys(key)) {
                        //ignore
                    } else {
                        groupedMetrics.others = groupedMetrics.others || [];
                        groupedMetrics.others.push({
                            'key': key,
                            'value': metrics[key]
                        });
                    }

                }
                ctrl.metrics = groupedMetrics;
            };

        }
    });
    angular.module('ozone')
        .component('tabs', {
            transclude: true,
            controller: function ($scope) {
                var ctrl = this;
                var panes = this.panes = [];
                this.select = function (pane) {
                    angular.forEach(panes, function (pane) {
                        pane.selected = false;
                    });
                    pane.selected = true;
                };
                this.addPane = function (pane) {
                    if (panes.length === 0) {
                        this.select(pane);
                    }
                    panes.push(pane);
                };
                this.click = function(pane) {
                    ctrl.select(pane);
                }
            },
            template: '

' +
            '
' +
            '{{pane.title}} ' +
            '

'
        })
        .component('pane', {
            transclude: true,
            require: {
                tabsCtrl: '^tabs'
            },
            bindings: {
                title: '@'
            },
            controller: function () {
                this.$onInit = function () {
                    this.tabsCtrl.addPane(this);
                };
            },
            template: '

'
        });

    angular.module('ozone').component('navmenu', {
        bindings: {
            metrics: '<'
        },
        templateUrl: '/static/templates/menu.html',
        controller: function ($http) {
            var ctrl = this;
            ctrl.docs = false;
            $http.head("/docs/index.html")
                .then(function (result) {
                    ctrl.docs = true;
                },function(){
                    ctrl.docs = false;
                });
        }
    });

    angular.module('ozone').component('config', {
        templateUrl: '/static/templates/config.html',
        controller: function ($scope, $http) {
            var ctrl = this;
            ctrl.selectedTags = [];

            $http.get("/conf?cmd=getOzoneTags&group=ozone")
                .then(function (response) {
                    ctrl.tags = response.data;

                    var excludedTags = ['CBLOCK', 'KSM', 'SCM'];
                    for (var i = 0; i < excludedTags.length; i++) {
                        var idx = ctrl.tags.indexOf(excludedTags[i]);
                        // Remove CBLOCK related properties
                        if (idx > -1) {
                            ctrl.tags.splice(idx, 1);
                        }
                    }
                    ctrl.loadAll();
                });

            ctrl.loadAll = function () {
                console.log("Displaying all configs");
                $http.get("/conf?cmd=getPropertyByTag&tags=" + ctrl.tags + "&group=ozone").then(function (response) {
                    ctrl.configs = response.data;
                    console.log(ctrl.configs)
                    for (var idx in ctrl.configs) {
                        var tags = []
                        var parsedTags = ctrl.configs[idx].tag.split(",");
                        for (var t in parsedTags) {
                            tags.push(parsedTags[t].trim())
                        }
                        ctrl.configs[idx].tag = tags;

                    };
                    ctrl.sortBy('name');
                });
            };

            ctrl.tagFilter = function (value, index, array) {
                if (!ctrl.selectedTags) {
                    return true;
                }
                var selected = true;
                for (var idx in ctrl.selectedTags) {
                    selected = selected && (value.tag.indexOf(ctrl.selectedTags[idx]) > -1);
                }
                return selected;
            };
            ctrl.configFilter = function (config) {
                return false;
            };
            ctrl.selected = function (tag) {
                return ctrl.selectedTags.includes(tag);
            };

            ctrl.allSelected = function () {
                return ctrl.selectedTags.indexOf('SCM') == -1
                    && ctrl.selectedTags.indexOf('KSM') == -1
            };

            ctrl.switchto = function (tag) {
                var tags = ctrl.selectedTags.filter(function (item) {
                    return item != 'KSM' && item != 'SCM';
                });
                if (tag) {
                    tags.push(tag);
                }
                ctrl.selectedTags = tags;
            };

            ctrl.select = function (tag) {
                var tagIdx = ctrl.selectedTags.indexOf(tag);
                if (tagIdx > -1) {
                    ctrl.selectedTags = ctrl.selectedTags.filter(function (item) {
                        return item != tag;
                    });
                } else {
                    ctrl.selectedTags.push(tag);
                }
                console.log("Tags selected:" + ctrl.selectedTags);
            };

            ctrl.sortBy = function (propertyName) {
                ctrl.reverse = (ctrl.propertyName === propertyName) ? !ctrl.reverse : false;
                ctrl.propertyName = propertyName;
            };

        }
    });

})();
<|endoftext|>


<commit_before>/*

Fetchflow jQuery plugin
=======================

Copyright (c) 2012 Fetchnotes, LLC

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

// Start editing an element
jQuery.fn.fetchflowEdit = function(options) {

	// Default options
	if (!options) options = {};

	if (typeof options.fromHTML === 'undefined') {
		options.fromHTML = function(html) {
			html = html.replace(/
/g, '\n').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
			var lastNewline = html.lastIndexOf('\n');
			if (lastNewline === -1)
				return html;
			return html.substring(0, lastNewline);
		};
	}

	if (typeof options.toHTML === 'undefined') {
		options.toHTML = function(text) {
			text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '
');
			return text + '
';
		};
	}

	// For every element we've selected...
	return this.each(function() {

		// Get me
		var $this = jQuery(this);

		// Start editing if we aren't already
		if ($this.data('focused')) return;
		$this.data('focused', true);

		// Build a textarea and put it on top of me
		var textarea = document.createElement('textarea');
		var $textarea = jQuery(textarea);
		var offset = $this.offset();
		var padding = $this.css('padding-top') + ' ' + $this.css('padding-right') + ' ' + $this.css('padding-bottom') + ' ' + $this.css('padding-left');
		$textarea.css({
			'position': 'absolute',
			'top': offset.top + 1,
			'left': offset.left + 1,
			'margin': '0',
			'padding': padding,
			'border-width': '0',
			'font': $this.css('font'),
			'color': 'inherit',
			'background-color': 'transparent',
			'outline': 'none',
			'resize': 'none',
			'overflow': 'hidden'
		});
		$textarea.val(options.fromHTML($this.html()));
		$textarea.data('div', this);
		$this.data('textarea', textarea);
		document.body.appendChild(textarea);

		// Focus the textarea
		$textarea.trigger('focus');

		// Hide my text
		$this.data('oldColor', $this.css('color'));
		$this.css('color', 'transparent');

		// When I edit, change the HTML
		$textarea.on('keydown keyup', function() {
			var $this = jQuery(this);
			var $div = jQuery($this.data('div'));
			$div.html(options.toHTML($this.val()));
			$this.width($div.width());
			$this.height($div.height());
			var offset = $div.offset();
			$this.css({
				'top': offset.top + 1,
				'left': offset.left + 1
			});
		});

		// Make sure the textarea is the right size
		$textarea.width($this.width());
		$textarea.height($this.height());

	});

};

// Quit editing an element
jQuery.fn.fetchflowUnedit = function() {

	return this.each(function() {

		var $this = jQuery(this);
		var $textarea = jQuery($this.data('textarea'));
		$this.data('focused', false);
		if ($this.data('oldColor'))
			$this.css('color', $this.data('oldColor'));
		if ($textarea)
			$textarea.remove();

	});

};

// Do some simple bindings for edit and unedit
jQuery.fn.fetchflowEditable = function(options) {

	return this.each(function() {

		var $this = jQuery(this);

		$this.on('click', function() {

			// Edit me when I'm clicked...
			$this.fetchflowEdit(options);

			// ...and unedit when you unfocus the textarea.
			$textarea = jQuery($this.data('textarea'));
			$textarea.on('blur', function() {
				var $div = jQuery($(this).data('div'));
				$div.fetchflowUnedit();
			});

		});

	});

};
<commit_msg>Improve "readme" at top of file<commit_after>/*

Fetchflow jQuery plugin
=======================

Fetchflow is a better version of `contenteditable`. Here's how to use it:

To start editing an element, just do this:

    $('#foo').fetchflowEdit();

To stop editing it:

	$('#foo').fetchflowUnedit();

If you want to make it editable (i.e., bind clicks and blurs to the above):

    $('#foo').fetchflowEditable();

Enjoy!

********************************************************************************

Fetchflow jQuery plugin
Copyright (c) 2012 Fetchnotes, LLC

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

// Start editing an element
jQuery.fn.fetchflowEdit = function(options) {

	// Default options
	if (!options) options = {};

	if (typeof options.fromHTML === 'undefined') {
		options.fromHTML = function(html) {
			html = html.replace(/
/g, '\n').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
			var lastNewline = html.lastIndexOf('\n');
			if (lastNewline === -1)
				return html;
			return html.substring(0, lastNewline);
		};
	}

	if (typeof options.toHTML === 'undefined') {
		options.toHTML = function(text) {
			text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '
');
			return text + '
';
		};
	}

	// For every element we've selected...
	return this.each(function() {

		// Get me
		var $this = jQuery(this);

		// Start editing if we aren't already
		if ($this.data('focused')) return;
		$this.data('focused', true);

		// Build a textarea and put it on top of me
		var textarea = document.createElement('textarea');
		var $textarea = jQuery(textarea);
		var offset = $this.offset();
		var padding = $this.css('padding-top') + ' ' + $this.css('padding-right') + ' ' + $this.css('padding-bottom') + ' ' + $this.css('padding-left');
		$textarea.css({
			'position': 'absolute',
			'top': offset.top + 1,
			'left': offset.left + 1,
			'margin': '0',
			'padding': padding,
			'border-width': '0',
			'font': $this.css('font'),
			'color': 'inherit',
			'background-color': 'transparent',
			'outline': 'none',
			'resize': 'none',
			'overflow': 'hidden'
		});
		$textarea.val(options.fromHTML($this.html()));
		$textarea.data('div', this);
		$this.data('textarea', textarea);
		document.body.appendChild(textarea);

		// Focus the textarea
		$textarea.trigger('focus');

		// Hide my text
		$this.data('oldColor', $this.css('color'));
		$this.css('color', 'transparent');

		// When I edit, change the HTML
		$textarea.on('keydown keyup', function() {
			var $this = jQuery(this);
			var $div = jQuery($this.data('div'));
			$div.html(options.toHTML($this.val()));
			$this.width($div.width());
			$this.height($div.height());
			var offset = $div.offset();
			$this.css({
				'top': offset.top + 1,
				'left': offset.left + 1
			});
		});

		// Make sure the textarea is the right size
		$textarea.width($this.width());
		$textarea.height($this.height());

	});

};

// Quit editing an element
jQuery.fn.fetchflowUnedit = function() {

	return this.each(function() {

		var $this = jQuery(this);
		var $textarea = jQuery($this.data('textarea'));
		$this.data('focused', false);
		if ($this.data('oldColor'))
			$this.css('color', $this.data('oldColor'));
		if ($textarea)
			$textarea.remove();

	});

};

// Do some simple bindings for edit and unedit
jQuery.fn.fetchflowEditable = function(options) {

	return this.each(function() {

		var $this = jQuery(this);

		$this.on('click', function() {

			// Edit me when I'm clicked...
			$this.fetchflowEdit(options);

			// ...and unedit when you unfocus the textarea.
			$textarea = jQuery($this.data('textarea'));
			$textarea.on('blur', function() {
				var $div = jQuery($(this).data('div'));
				$div.fetchflowUnedit();
			});

		});

	});

};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed 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 Promise = require('q').Promise;

var arrayContaining = jasmine.arrayContaining;
var objectContaining = jasmine.objectContaining;
var stringMatching = jasmine.stringMatching;

describe('places client library', function() {
  var googleMaps = require('./service');

  it('gets places for a text search query', function(done) {
    googleMaps.places({
      query: 'fast food',
      language: 'en',
      location: [-33.865, 151.038],
      radius: 5000,
      minprice: 1,
      maxprice: 4,
      opennow: true,
      type: 'restaurant'
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.results).toEqual(
          arrayContaining([
            objectContaining({
              name: stringMatching('McDonald\'s')
            })
          ]));
    })
    .then(done, fail);
  });

  it('gets places for a nearby search query', function(done) {
    googleMaps.placesNearby({
      language: 'en',
      location: [-33.865, 151.038],
      radius: 5000,
      minprice: 1,
      maxprice: 4,
      opennow: true,
      type: 'restaurant'
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.results).toEqual(
          arrayContaining([
            objectContaining({
              name: stringMatching('McDonald\'s')
            })
          ]));
    })
    .then(done, fail);
  });

  it('gets places for a nearby search query ranked by distance', function(done) {
    googleMaps.placesNearby({
      language: 'en',
      location: [-33.865, 151.038],
      rankby: 'distance',
      minprice: 1,
      maxprice: 4,
      opennow: true,
      type: 'restaurant'
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.results).toEqual(
          arrayContaining([
            objectContaining({
              name: stringMatching('McDonald\'s')
            })
          ]));
    })
    .then(done, fail);
  });

  it('can page through results', function(done) {
    googleMaps.places({
      query: 'restaurant',
      language: 'en',
      location: [-33.86746, 151.207090],
      radius: 5000
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.next_page_token).not.toBeFalsy();
      function getNextPage() {
        return googleMaps.places({
          pagetoken: response.json.next_page_token
        }).asPromise();
      }
      return getNextPage()
          .then(function repeatWhileInvalid(nextResponse) {
            if (nextResponse.json.status !== 'INVALID_REQUEST') {
              return nextResponse;
            }

            // Wait one second, and try again.
            return new Promise(function(resolve) {
              setTimeout(resolve, 2000);
            })
            .then(getNextPage)
            .then(repeatWhileInvalid);
          });
    })
    .then(function(nextResponse) {
      expect(nextResponse.json.status).toBe('OK');
      expect(nextResponse.json.results.length).not.toBeFalsy();
    })
    .then(done, fail);
  }, 10000);

  it('gets details for a place', function(done) {
    googleMaps.place({
      placeid: 'ChIJc6EceWquEmsRmBVAjzjXM-g',
      language: 'fr'
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.result).toEqual(
          objectContaining({
            name: 'Spice Temple'
          }));
    })
    .then(done, fail);
  });

  it('gets a places photo', function(done) {
    googleMaps.placesPhoto({
      photoreference: 'CnRvAAAAwMpdHeWlXl-lH0vp7lez4znKPIWSWvgvZFISdKx45AwJVP1Qp37YOrH7sqHMJ8C-vBDC546decipPHchJhHZL94RcTUfPa1jWzo-rSHaTlbNtjh-N68RkcToUCuY9v2HNpo5mziqkir37WU8FJEqVBIQ4k938TI3e7bf8xq-uwDZcxoUbO_ZJzPxremiQurAYzCTwRhE_V0',
      maxwidth: 100,
      maxheight: 100
    })
    .asPromise()
    .then(function(response) {
      expect(response.headers['content-type']).toBe('image/jpeg');
    })
    .then(done, fail);
  });

  it('gets autocomplete predictions for places', function(done) {
    googleMaps.placesAutoComplete({
      input: 'pizza',
      language: 'en',
      location: [40.724, -74.013],
      radius: 5000,
      components: {country: 'us'}
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.predictions).toEqual(
          arrayContaining([
            objectContaining({
              terms: arrayContaining([
                objectContaining({
                  value: 'NY'
                })
              ])
            })
          ]));
    })
    .then(done, fail);
  });

  it('gets autocomplete predictions for a query', function(done) {
    googleMaps.placesQueryAutoComplete({
      input: 'pizza near New York',
      language: 'en',
      location: [40.724, -74.013],
      radius: 5000
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.predictions).toEqual(
          arrayContaining([
            objectContaining({
              description: 'pizza near New York, NY, USA'
            })
          ]));
    })
    .then(done, fail);
  });

});
<commit_msg>Removes recursive pagination from paces test<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed 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 Promise = require('q').Promise;

var arrayContaining = jasmine.arrayContaining;
var objectContaining = jasmine.objectContaining;
var stringMatching = jasmine.stringMatching;

describe('places client library', function() {
  var googleMaps = require('./service');

  it('gets places for a text search query', function(done) {
    googleMaps.places({
      query: 'fast food',
      language: 'en',
      location: [-33.865, 151.038],
      radius: 5000,
      minprice: 1,
      maxprice: 4,
      opennow: true,
      type: 'restaurant'
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.results).toEqual(
          arrayContaining([
            objectContaining({
              name: stringMatching('McDonald\'s')
            })
          ]));
    })
    .then(done, fail);
  });

  it('gets places for a nearby search query', function(done) {
    googleMaps.placesNearby({
      language: 'en',
      location: [-33.865, 151.038],
      radius: 5000,
      minprice: 1,
      maxprice: 4,
      opennow: true,
      type: 'restaurant'
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.results).toEqual(
          arrayContaining([
            objectContaining({
              name: stringMatching('McDonald\'s')
            })
          ]));
    })
    .then(done, fail);
  });

  it('gets places for a nearby search query ranked by distance', function(done) {
    googleMaps.placesNearby({
      language: 'en',
      location: [-33.865, 151.038],
      rankby: 'distance',
      minprice: 1,
      maxprice: 4,
      opennow: true,
      type: 'restaurant'
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.results).toEqual(
          arrayContaining([
            objectContaining({
              name: stringMatching('McDonald\'s')
            })
          ]));
    })
    .then(done, fail);
  });

  it('can page through results', function(done) {
    googleMaps.places({
      query: 'restaurant',
      language: 'en',
      location: [-33.86746, 151.207090],
      radius: 5000
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.next_page_token).not.toBeFalsy();
      function getNextPage() {
        return googleMaps.places({
          pagetoken: response.json.next_page_token
        }).asPromise();
      }
      return getNextPage()
          .then(function repeatWhileInvalid(nextResponse) {
            if (nextResponse.json.status !== 'INVALID_REQUEST') {
              return nextResponse;
            }

            // Wait one second, and try again.
            return new Promise(function(resolve) {
              setTimeout(resolve, 2000);
            })
            .then(getNextPage)
            // .then(repeatWhileInvalid);
          });
    })
    .then(function(nextResponse) {
      expect(nextResponse.json.status).toBe('OK');
      expect(nextResponse.json.results.length).not.toBeFalsy();
    })
    .then(done, fail);
  }, 10000);

  it('gets details for a place', function(done) {
    googleMaps.place({
      placeid: 'ChIJc6EceWquEmsRmBVAjzjXM-g',
      language: 'fr'
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.result).toEqual(
          objectContaining({
            name: 'Spice Temple'
          }));
    })
    .then(done, fail);
  });

  it('gets a places photo', function(done) {
    googleMaps.placesPhoto({
      photoreference: 'CnRvAAAAwMpdHeWlXl-lH0vp7lez4znKPIWSWvgvZFISdKx45AwJVP1Qp37YOrH7sqHMJ8C-vBDC546decipPHchJhHZL94RcTUfPa1jWzo-rSHaTlbNtjh-N68RkcToUCuY9v2HNpo5mziqkir37WU8FJEqVBIQ4k938TI3e7bf8xq-uwDZcxoUbO_ZJzPxremiQurAYzCTwRhE_V0',
      maxwidth: 100,
      maxheight: 100
    })
    .asPromise()
    .then(function(response) {
      expect(response.headers['content-type']).toBe('image/jpeg');
    })
    .then(done, fail);
  });

  it('gets autocomplete predictions for places', function(done) {
    googleMaps.placesAutoComplete({
      input: 'pizza',
      language: 'en',
      location: [40.724, -74.013],
      radius: 5000,
      components: {country: 'us'}
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.predictions).toEqual(
          arrayContaining([
            objectContaining({
              terms: arrayContaining([
                objectContaining({
                  value: 'NY'
                })
              ])
            })
          ]));
    })
    .then(done, fail);
  });

  it('gets autocomplete predictions for a query', function(done) {
    googleMaps.placesQueryAutoComplete({
      input: 'pizza near New York',
      language: 'en',
      location: [40.724, -74.013],
      radius: 5000
    })
    .asPromise()
    .then(function(response) {
      expect(response.json.predictions).toEqual(
          arrayContaining([
            objectContaining({
              description: 'pizza near New York, NY, USA'
            })
          ]));
    })
    .then(done, fail);
  });

});
<|endoftext|>


<commit_before>import '../form/layout';

frappe.provide('frappe.ui');

frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({
	init: function(opts) {
		$.extend(this, opts);
		this.dirty = false;
		this._super();
		$.each(this.fields || [], function(i, f) {
			if(!f.fieldname && f.label) {
				f.fieldname = f.label.replace(/ /g, "_").toLowerCase();
			}
		})
		if(this.values) {
			this.set_values(this.values);
		}
	},
	make: function() {
		var me = this;
		if(this.fields) {
			this._super();
			this.refresh();
			// set default
			$.each(this.fields_list, function(i, field) {
				if (field.df["default"]) {
					let def_value = field.df["default"];

					if (def_value == 'Today' && field.df["fieldtype"] == 'Date') {
						def_value = frappe.datetime.get_today();
					}

					field.set_input(def_value);
					// if default and has depends_on, render its fields.
					me.refresh_dependency();
				}
			})

			if(!this.no_submit_on_enter) {
				this.catch_enter_as_submit();
			}

			$(this.wrapper).find('input, select').on('change', () => {
				this.dirty = true;

				frappe.run_serially([
					() => frappe.timeout(0.1),
					() => me.refresh_dependency()
				]);
			});

		}
	},
	first_button: false,
	focus_on_first_input: function() {
		if(this.no_focus) return;
		$.each(this.fields_list, function(i, f) {
			if(!in_list(['Date', 'Datetime', 'Time', 'Check'], f.df.fieldtype) && f.set_focus) {
				f.set_focus();
				return false;
			}
		});
	},
	catch_enter_as_submit: function() {
		var me = this;
		$(this.body).find('input[type="text"], input[type="password"], select').keypress(function(e) {
			if(e.which==13) {
				if(me.has_primary_action) {
					e.preventDefault();
					me.get_primary_btn().trigger("click");
				}
			}
		});
	},
	get_input: function(fieldname) {
		var field = this.fields_dict[fieldname];
		return $(field.txt ? field.txt : field.input);
	},
	get_field: function(fieldname) {
		return this.fields_dict[fieldname];
	},
	get_values: function(ignore_errors) {
		var ret = {};
		var errors = [];
		for (var key in this.fields_dict) {
			var f = this.fields_dict[key];
			if (f.get_value) {
				var v = f.get_value();
				if(f.df.reqd && is_null(strip_html(v)))
					errors.push(__(f.df.label));

				if (f.df.reqd
					&& f.df.fieldtype === 'Text Editor'
					&& is_null(strip_html(cstr(v))))
					errors.push(__(f.df.label));

				if (!is_null(v)) ret[f.df.fieldname] = v;
			}
		}
		if (errors.length && !ignore_errors) {
			frappe.msgprint({
				title: __('Missing Values Required'),
				message: __('Following fields have missing values:') +
					'

' + errors.join('
') + '
',
				indicator: 'orange'
			});
			return null;
		}
		return ret;
	},
	get_value: function(key) {
		var f = this.fields_dict[key];
		return f && (f.get_value ? f.get_value() : null);
	},
	set_value: function(key, val){
		return new Promise(resolve => {
			var f = this.fields_dict[key];
			if(f) {
				f.set_value(val).then(() => {
					f.set_input(val);
					this.refresh_dependency();
					resolve();
				});
			} else {
				resolve();
			}
		});
	},
	set_input: function(key, val) {
		return this.set_value(key, val);
	},
	set_values: function(dict) {
		let promises = [];
		for(var key in dict) {
			if(this.fields_dict[key]) {
				promises.push(this.set_value(key, dict[key]));
			}
		}

		return Promise.all(promises);
	},
	clear: function() {
		for(var key in this.fields_dict) {
			var f = this.fields_dict[key];
			if(f && f.set_input) {
				f.set_input(f.df['default'] || '');
			}
		}
	},
	set_df_property: function (fieldname, prop, value) {
		const field    = this.get_field(fieldname);
		field.df[prop] = value;
		field.refresh();
	}
});
<commit_msg>chore: add space after if keyword<commit_after>import '../form/layout';

frappe.provide('frappe.ui');

frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({
	init: function(opts) {
		$.extend(this, opts);
		this.dirty = false;
		this._super();
		$.each(this.fields || [], function(i, f) {
			if(!f.fieldname && f.label) {
				f.fieldname = f.label.replace(/ /g, "_").toLowerCase();
			}
		})
		if(this.values) {
			this.set_values(this.values);
		}
	},
	make: function() {
		var me = this;
		if(this.fields) {
			this._super();
			this.refresh();
			// set default
			$.each(this.fields_list, function(i, field) {
				if (field.df["default"]) {
					let def_value = field.df["default"];

					if (def_value == 'Today' && field.df["fieldtype"] == 'Date') {
						def_value = frappe.datetime.get_today();
					}

					field.set_input(def_value);
					// if default and has depends_on, render its fields.
					me.refresh_dependency();
				}
			})

			if(!this.no_submit_on_enter) {
				this.catch_enter_as_submit();
			}

			$(this.wrapper).find('input, select').on('change', () => {
				this.dirty = true;

				frappe.run_serially([
					() => frappe.timeout(0.1),
					() => me.refresh_dependency()
				]);
			});

		}
	},
	first_button: false,
	focus_on_first_input: function() {
		if(this.no_focus) return;
		$.each(this.fields_list, function(i, f) {
			if(!in_list(['Date', 'Datetime', 'Time', 'Check'], f.df.fieldtype) && f.set_focus) {
				f.set_focus();
				return false;
			}
		});
	},
	catch_enter_as_submit: function() {
		var me = this;
		$(this.body).find('input[type="text"], input[type="password"], select').keypress(function(e) {
			if(e.which==13) {
				if(me.has_primary_action) {
					e.preventDefault();
					me.get_primary_btn().trigger("click");
				}
			}
		});
	},
	get_input: function(fieldname) {
		var field = this.fields_dict[fieldname];
		return $(field.txt ? field.txt : field.input);
	},
	get_field: function(fieldname) {
		return this.fields_dict[fieldname];
	},
	get_values: function(ignore_errors) {
		var ret = {};
		var errors = [];
		for (var key in this.fields_dict) {
			var f = this.fields_dict[key];
			if (f.get_value) {
				var v = f.get_value();
				if (f.df.reqd && is_null(strip_html(v)))
					errors.push(__(f.df.label));

				if (f.df.reqd
					&& f.df.fieldtype === 'Text Editor'
					&& is_null(strip_html(cstr(v))))
					errors.push(__(f.df.label));

				if (!is_null(v)) ret[f.df.fieldname] = v;
			}
		}
		if (errors.length && !ignore_errors) {
			frappe.msgprint({
				title: __('Missing Values Required'),
				message: __('Following fields have missing values:') +
					'

' + errors.join('
') + '
',
				indicator: 'orange'
			});
			return null;
		}
		return ret;
	},
	get_value: function(key) {
		var f = this.fields_dict[key];
		return f && (f.get_value ? f.get_value() : null);
	},
	set_value: function(key, val){
		return new Promise(resolve => {
			var f = this.fields_dict[key];
			if(f) {
				f.set_value(val).then(() => {
					f.set_input(val);
					this.refresh_dependency();
					resolve();
				});
			} else {
				resolve();
			}
		});
	},
	set_input: function(key, val) {
		return this.set_value(key, val);
	},
	set_values: function(dict) {
		let promises = [];
		for(var key in dict) {
			if(this.fields_dict[key]) {
				promises.push(this.set_value(key, dict[key]));
			}
		}

		return Promise.all(promises);
	},
	clear: function() {
		for(var key in this.fields_dict) {
			var f = this.fields_dict[key];
			if(f && f.set_input) {
				f.set_input(f.df['default'] || '');
			}
		}
	},
	set_df_property: function (fieldname, prop, value) {
		const field    = this.get_field(fieldname);
		field.df[prop] = value;
		field.refresh();
	}
});
<|endoftext|>


<commit_before>(function( $ ) {

	var methods = {
		refresh : function( ) {
			return this.each( function() {

				var $this = $(this),
					data = $this.data( 'iconfield' ),
					image = new Image();

				image.onload = function( ) {

					var style_map = {
						'background-image'		:	'url(' + data['image-url'] + ')',
						'background-repeat'		:	'no-repeat',
						'background-position'	:	data['horizontal-padding'] + 'px center',
						'padding-left'			:	image.width + data['horizontal-padding'] * 2,
					};

					// calculate the minimal height this textfield should take,
					// taking into account the image's height and paddings beneath and above it
					var minimal_height = (image.height + data['vertical-padding'] * 2);
					var current_height = Number($this.css( 'height' ).slice(0, -2))

					// add padding if the field's current height is not high enough to include the image
					if( current_height < minimal_height ) {
						var padding = (minimal_height - current_height) / 2;
						$.extend( style_map, {
							'padding-top'		:	padding,
							'padding-bottom'	:	padding,
						} );
					}

					// update the field's style
					$this.css( style_map );
				}

				image.src = data['image-url'];

			} );

		},

		init : function( options ) {

			return this.each( function() {

				var $this = $(this);

				// initialize the element's iconfield data with the defaults
				$this.data( 'iconfield', new Object() );
				$this.iconfield( 'option', $.extend( {
					'horizontal-padding'	:	2,
					'vertical-padding'		:	4
				}, options ) );

				// refresh the styling
				$this.iconfield( 'refresh' )

			} );

		},

		option : function( name, value ) {

			return $.access( this, function( elem, name, value ) {
				var $this = $(elem),
					data = $this.data('iconfield');

				if( value !== undefined ) {
					data[name] = value;
				} else {
					return data[name];
				}
			}, name, value, arguments.length > 1 );

		}
	}

	$.fn.iconfield = function( method ) {

		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ) );
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		}
		else {
			$.error( 'Method ' + method + ' does not exist on jQuery.iconfield' );
		}
	}

})(jQuery);
<commit_msg>finished the code? documentation still needed<commit_after>(function( $ ) {

	var methods = {
		isEventOnIcon : function( e ) {
			var $this = $(this),
				x = e.pageX - $this.position().left,
				y = e.pageY - $this.position().top,
				horizontalPadding = $this.iconfield( 'option', 'horizontal-padding' ),
				verticalPadding = $this.iconfield( 'option', 'vertical-padding' ),
				imageWidth = $this.iconfield( 'option', 'image-width' ),
				imageHeight = $this.iconfield( 'option', 'image-height' );

				return (
					(x >= horizontalPadding && x <= horizontalPadding + imageWidth)
					&&
					(y >= verticalPadding && y <= verticalPadding + imageHeight)
				);
		},

		refresh : function( ) {
			return this.each( function() {

				var $this = $(this),
					data = $this.data( 'iconfield' ),
					image = new Image();

				image.onload = function( ) {

					var style_map = {
						'background-image'		:	'url(' + data['image-url'] + ')',
						'background-repeat'		:	'no-repeat',
						'background-position'	:	data['horizontal-padding'] + 'px center',
						'padding-left'			:	image.width + data['horizontal-padding'] * 2,
					};

					// calculate the minimal height this textfield should take,
					// taking into account the image's height and paddings beneath and above it
					var minimal_height = (image.height + data['vertical-padding'] * 2);
					var current_height = Number($this.css( 'height' ).slice(0, -2))

					// add padding if the field's current height is not high enough to include the image
					// (+ vertical padding)
					if( current_height < minimal_height ) {
						var padding = (minimal_height - current_height) / 2;
						$.extend( style_map, {
							'padding-top'		:	padding,
							'padding-bottom'	:	padding,
						} );
					}

					// add image dimensions to the data stored for this element
					$.extend( data, {
						'image-width'	:	image.width,
						'image-height'	:	image.height
					} );
					$this.data( 'iconfield', data);

					// update the field's style
					$this.css( style_map );
				}

				image.src = data['image-url'];

			} );

		},

		init : function( options ) {

			return this.each( function() {

				var $this = $(this);

				// initialize the element's iconfield data with the defaults
				$this.data( 'iconfield', new Object() );
				$this.iconfield( 'option', $.extend( {
					'horizontal-padding'	:	2,
					'vertical-padding'		:	4,
					'icon-cursor'			:	'auto'
				}, options ) );

				var events = [ 'click' ];

				// create a custom event that is triggered when the icon is clicked
				$this.on( 'click', function( e ) {
					if($this.iconfield( 'isEventOnIcon', e ) ) {
						$this.trigger( 'iconfield.click' );
					}
				} );

				// bind to mousemove for simulating mouseenter and mouseleave events
				$this.on( 'mousemove', function( e ) {
					var data = $this.data( 'iconfield' ),
						wasOnIcon = data['was-on-icon'] ? data['was-on-icon'] : false,
						curOnIcon = $this.iconfield( 'isEventOnIcon', e );

					// this is obvious
					$this.trigger( 'iconfield.mousemove' );

					// if the mouse is on the icon and wasn't before, or vice versa,
					// trigger the relevant event and store the current position of
					// the mouse
					if( curOnIcon != wasOnIcon ) {
						if( curOnIcon && !wasOnIcon ) {
							$this.trigger( 'iconfield.mouseenter' );
						}
						else if( !curOnIcon && wasOnIcon ) {
							$this.trigger( 'iconfield.mouseleave' );
						}

						data['was-on-icon'] = curOnIcon;
						$this.data( 'iconfield', data );
					}
				} );

				// bind to our simulated mouseenter event to change the cursor
				// when the mouse is hovering over the icon
				$this.on( 'iconfield.mouseenter', function( e ) {
					var data = $this.data( 'iconfield' ),
						curCursor = $this.css( 'cursor' );

					// 'auto' means that no special cursor was specified by the user
					if( data['icon-cursor'] != 'auto' &&
						data['icon-cursor'] != curCursor) {
						// save the cursor before it's changed
						data['old-cursor'] = curCursor ? curCursor : 'auto';
						$this.data( 'iconfield', data );

						// change the cursor
						$this.css( 'cursor', data['icon-cursor'] );
					}
				} );

				// change the icon back to normal
				$this.on( 'iconfield.mouseleave', function( e ) {
					var data = $this.data( 'iconfield' );

					// 'auto' means that no special cursor was specified by the user,
					// meaning that there is nothing to change back to
					if( data['icon-cursor'] != 'auto' ) {
						// if no old cursor was stored (meaning that the mouse was never on the icon)
						// change back to 'auto'
						$this.css( 'cursor', data['old-cursor'] ? data['old-cursor'] : 'auto' );
					}
				} );

				// refresh the styling
				$this.iconfield( 'refresh' )

			} );

		},

		option : function( name, value ) {

			return $.access( this, function( elem, name, value ) {
				var $this = $(elem),
					data = $this.data('iconfield');

				if( value !== undefined ) {
					data[name] = value;
				} else {
					return data[name];
				}
			}, name, value, arguments.length > 1 );

		}
	}

	$.fn.iconfield = function( method ) {

		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ) );
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		}
		else {
			$.error( 'Method ' + method + ' does not exist on jQuery.iconfield' );
		}
	}

})(jQuery);
<|endoftext|>


<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

let instance = null;

const log = require('./logger.js')(__filename);

const SysManager = require('./SysManager.js');
const sysManager = new SysManager('info');

const rclient = require('../util/redis_manager.js').getRedisClient()

const IntelTool = require('./IntelTool');
const intelTool = new IntelTool();

const Whois = require('../util/Whois');
const IpInfo = require('../util/IpInfo');

const A_WEEK = 3600 * 24 * 7;

/* malware, botnet, spam, phishing, malicious activity, blacklist, dnsbl */
const IGNORED_TAGS = ['dnsbl', 'spam'];

module.exports = class {
  constructor() {
    if (instance == null) {
      instance = this;
    }
    return instance;
  }

  async cacheLookup(ip, origin) {
    let result;

    let key = "cache.intel:" + origin + ":" + ip;

    try {
      result = await rclient.getAsync(key);
    } catch (err) {
      // null
    }

    if (result && (result === "{}" || Object.keys(result).length === 0)) {
      result = null;
    }

    log.debug("Cache lookup for", ip, ", origin", origin, ", result:", result);
    return result;
  }

  async cacheAdd(ip, origin, value) {
    if (value == null || value === "{}") {
      value = "none";
    }

    let key = "cache.intel:" + origin + ":" + ip;

    log.debug("Add into cache.intel, key:", key, ", value:", value);

    try {
      await rclient.setAsync(key, value)
      await rclient.expireatAsync(key, this.currentTime() + A_WEEK)
    } catch(err) {
      log.warn(`Error when add ip ${ip} from ${origin} to cache`, err);
    }
  }

  currentTime() {
    return Math.round(Date.now() / 1000);
  }

  async lookupDomain(domain, ip, flowObj) {
    if (!domain || domain === "firewalla.com") {
      return;
    }

    log.debug("FlowObj:", flowObj);

    let intelObj = flowObj.intel;

    if (!intelObj.category) {
      log.info("No intel for domain", domain, "look up from cloud...");
      intelObj = await this._lookupDomainInBone(domain, ip);
    } else {
      log.info("Intel for domain", domain, " exists in flowObj");
      if (intelObj.cc) {
        try {
          let json = JSON.parse(intelObj.cc)
          if(Array.isArray(json)) {
            // HACK, excluding attackpage
            json = json.filter(x => x !== "attackpage")
            intelObj.cc = json[0]
          }
        } catch (err) {
          log.warn("Error when parsing info.cc:", intelObj.cc, err);
        }
      }
    }

    if(intelObj.category === 'intel' && intelObj.s && Number(intelObj.s) === 0) {
      log.info("Intel ignored, severity score is zero", intelObj);
      return;
    }

    if (!intelObj.whois) {
      intelObj.whois = await this.whois(domain);
    }

    intelObj.from = "firewalla";

    log.info(`Intel for domain ${domain} is`, intelObj);

    return intelObj;
  }

  async lookupIp(ip, flowIntel) {
    if (!ip || ip === "8.8.8.8" || sysManager.isLocalIP(ip)) {
      return;
    }

    let data = await rclient.hgetallAsync("intel:action:" + ip);
    if (data && data.ignore) {
      log.info("Intel:Lookup:Ignored", ip);
      return;
    }

    let intelObj = this.addFlowIntel(ip, {}, flowIntel);

    log.debug("IntelObj:", intelObj);

    return intelObj;
  }

  async whois(target) {
    log.info("Looking whois:", target);

    let cached = await this.cacheLookup(target, "whois");

    if (cached === "none") {
      return null;
    }

    if (cached) {
      try {
        return JSON.parse(cached);
      } catch (err) {
        log.error("Error when parse cache:", cached, err);
      }
    }

    let whois = await Whois.lookup(target, {useOwnParser: true});

    if (whois) {
      this.cacheAdd(target, "whois", JSON.stringify(whois));
    }

    return whois;
  }

  async _lookupDomainInBone(domain, ip) {
    let cloudIntel;
    try {
      // TODO: save this to intel:ip
      cloudIntel = await intelTool.checkIntelFromCloud([ip], [domain], 'out');
    } catch (err) {
      log.info("Error when check intel from cloud", err);
    }
    log.info("Cloud intel for ", domain, "is: ", cloudIntel);

    return this.processCloudIntel(cloudIntel[0]);
  }

  processCloudIntel(cloudIntel) {
    if (!cloudIntel) {
      return;
    }

    let intel = {};
    // check if the host matches the result from cloud
    // FIXME: ignore IP check because intel result from cloud does
    // NOT have "ip" all the time.
    if (cloudIntel.apps) {
      intel.apps = JSON.stringify(cloudIntel.apps);
      let keys = Object.keys(cloudIntel.apps);
      if (keys && keys[0]) {
        intel.app = keys[0];
      }
    }

    if (cloudIntel.c) {
      intel.category = cloudIntel.c;
    }

    if (cloudIntel.action && cloudIntel.action.block) {
      intel.action = "block"
    }

    if (cloudIntel.s) {
      intel.s = cloudIntel.s;
    }

    if (cloudIntel.t) {
      intel.t = cloudIntel.t;
    }

    if (cloudIntel.cc) {
      try {
        cloudIntel.cc = JSON.parse(cloudIntel.cc);
        intel.cc = cloudIntel.cc[0];
      } catch (err) {
        intel.cc = cloudIntel.cc;
        log.warn("Error when parsing info.cc:", cloudIntel.cc, err);
      }
    }
    return intel;
  }

  addFlowIntel(ip, intelObj, intel) {
    let weburl = "https://intel.firewalla.com/";
    log.debug("IntelManger:addFlowIntel:", ip, intel);
    if (intel == null) {
      return null;
    }
    if (intel.t) {
      intelObj.count = Math.abs(intel.t / 10);
    } else {
      intelObj.count = 4;
    }
    if (intel.s) {
      intelObj.severityscore = intel.s;
    } else {
      intelObj.severityscore = 20;
    }
    intelObj.summary = "";
    intelObj.weburl = weburl;
    intelObj.from = "firewalla";
    if (intel.cc) {
      try {
        intelObj.tags = JSON.parse(intel.cc);
      } catch (e) {
      }
    }
    log.debug("IntelManger:addFlowIntel:Done", ip);
    return intelObj;
  }

  /* curl ipinfo.io/98.124.243.43/
  {
  "ip": "98.124.243.43",
  "hostname": "No Hostname",
  "city": "Kirkland",
  "region": "Washington",
  "country": "US",
  "loc": "47.6727,-122.1873",
  "org": "AS21740 eNom, Incorporated",
  "postal": "98033"
  }
  */
  async ipinfo(ip) {
    log.debug("Looking up location:", ip);

    let cached = await this.cacheLookup(ip, "ipinfo");

    if (cached === "none") {
      return null;
    }

    let ipinfo;
    if (cached && cached !== 'null') {
      try {
        ipinfo = JSON.parse(cached);
      } catch (err) {
        log.error("Error when parse cache:", cached, err);
      }
      if (ipinfo) {
        log.debug("Cached ip info:", ip);
        return ipinfo;
      }
    }

    ipinfo = await IpInfo.get(ip);

    if (ipinfo) {
      this.cacheAdd(ip, "ipinfo", JSON.stringify(ipinfo));
    }

    return ipinfo;
  }
}
<commit_msg>reduce ttl for whois/ipinfo intel<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

let instance = null;

const log = require('./logger.js')(__filename);

const SysManager = require('./SysManager.js');
const sysManager = new SysManager('info');

const rclient = require('../util/redis_manager.js').getRedisClient()

const IntelTool = require('./IntelTool');
const intelTool = new IntelTool();

const Whois = require('../util/Whois');
const IpInfo = require('../util/IpInfo');

const A_DAY = 3600 * 24;

/* malware, botnet, spam, phishing, malicious activity, blacklist, dnsbl */
const IGNORED_TAGS = ['dnsbl', 'spam'];

module.exports = class {
  constructor() {
    if (instance == null) {
      instance = this;
    }
    return instance;
  }

  async cacheLookup(ip, origin) {
    let result;

    let key = "cache.intel:" + origin + ":" + ip;

    try {
      result = await rclient.getAsync(key);
    } catch (err) {
      // null
    }

    if (result && (result === "{}" || Object.keys(result).length === 0)) {
      result = null;
    }

    log.debug("Cache lookup for", ip, ", origin", origin, ", result:", result);
    return result;
  }

  async cacheAdd(ip, origin, value) {
    if (value == null || value === "{}") {
      value = "none";
    }

    let key = "cache.intel:" + origin + ":" + ip;

    log.debug("Add into cache.intel, key:", key, ", value:", value);

    try {
      await rclient.setAsync(key, value)
      await rclient.expireatAsync(key, this.currentTime() + A_DAY)
    } catch(err) {
      log.warn(`Error when add ip ${ip} from ${origin} to cache`, err);
    }
  }

  currentTime() {
    return Math.round(Date.now() / 1000);
  }

  async lookupDomain(domain, ip, flowObj) {
    if (!domain || domain === "firewalla.com") {
      return;
    }

    log.debug("FlowObj:", flowObj);

    let intelObj = flowObj.intel;

    if (!intelObj.category) {
      log.info("No intel for domain", domain, "look up from cloud...");
      intelObj = await this._lookupDomainInBone(domain, ip);
    } else {
      log.info("Intel for domain", domain, " exists in flowObj");
      if (intelObj.cc) {
        try {
          let json = JSON.parse(intelObj.cc)
          if(Array.isArray(json)) {
            // HACK, excluding attackpage
            json = json.filter(x => x !== "attackpage")
            intelObj.cc = json[0]
          }
        } catch (err) {
          log.warn("Error when parsing info.cc:", intelObj.cc, err);
        }
      }
    }

    if(intelObj.category === 'intel' && intelObj.s && Number(intelObj.s) === 0) {
      log.info("Intel ignored, severity score is zero", intelObj);
      return;
    }

    if (!intelObj.whois) {
      intelObj.whois = await this.whois(domain);
    }

    intelObj.from = "firewalla";

    log.info(`Intel for domain ${domain} is`, intelObj);

    return intelObj;
  }

  async lookupIp(ip, flowIntel) {
    if (!ip || ip === "8.8.8.8" || sysManager.isLocalIP(ip)) {
      return;
    }

    let data = await rclient.hgetallAsync("intel:action:" + ip);
    if (data && data.ignore) {
      log.info("Intel:Lookup:Ignored", ip);
      return;
    }

    let intelObj = this.addFlowIntel(ip, {}, flowIntel);

    log.debug("IntelObj:", intelObj);

    return intelObj;
  }

  async whois(target) {
    log.info("Looking whois:", target);

    let cached = await this.cacheLookup(target, "whois");

    if (cached === "none") {
      return null;
    }

    if (cached) {
      try {
        return JSON.parse(cached);
      } catch (err) {
        log.error("Error when parse cache:", cached, err);
      }
    }

    let whois = await Whois.lookup(target, {useOwnParser: true});

    if (whois) {
      this.cacheAdd(target, "whois", JSON.stringify(whois));
    }

    return whois;
  }

  async _lookupDomainInBone(domain, ip) {
    let cloudIntel;
    try {
      // TODO: save this to intel:ip
      cloudIntel = await intelTool.checkIntelFromCloud([ip], [domain], 'out');
    } catch (err) {
      log.info("Error when check intel from cloud", err);
    }
    log.info("Cloud intel for ", domain, "is: ", cloudIntel);

    return this.processCloudIntel(cloudIntel[0]);
  }

  processCloudIntel(cloudIntel) {
    if (!cloudIntel) {
      return;
    }

    let intel = {};
    // check if the host matches the result from cloud
    // FIXME: ignore IP check because intel result from cloud does
    // NOT have "ip" all the time.
    if (cloudIntel.apps) {
      intel.apps = JSON.stringify(cloudIntel.apps);
      let keys = Object.keys(cloudIntel.apps);
      if (keys && keys[0]) {
        intel.app = keys[0];
      }
    }

    if (cloudIntel.c) {
      intel.category = cloudIntel.c;
    }

    if (cloudIntel.action && cloudIntel.action.block) {
      intel.action = "block"
    }

    if (cloudIntel.s) {
      intel.s = cloudIntel.s;
    }

    if (cloudIntel.t) {
      intel.t = cloudIntel.t;
    }

    if (cloudIntel.cc) {
      try {
        cloudIntel.cc = JSON.parse(cloudIntel.cc);
        intel.cc = cloudIntel.cc[0];
      } catch (err) {
        intel.cc = cloudIntel.cc;
        log.warn("Error when parsing info.cc:", cloudIntel.cc, err);
      }
    }
    return intel;
  }

  addFlowIntel(ip, intelObj, intel) {
    let weburl = "https://intel.firewalla.com/";
    log.debug("IntelManger:addFlowIntel:", ip, intel);
    if (intel == null) {
      return null;
    }
    if (intel.t) {
      intelObj.count = Math.abs(intel.t / 10);
    } else {
      intelObj.count = 4;
    }
    if (intel.s) {
      intelObj.severityscore = intel.s;
    } else {
      intelObj.severityscore = 20;
    }
    intelObj.summary = "";
    intelObj.weburl = weburl;
    intelObj.from = "firewalla";
    if (intel.cc) {
      try {
        intelObj.tags = JSON.parse(intel.cc);
      } catch (e) {
      }
    }
    log.debug("IntelManger:addFlowIntel:Done", ip);
    return intelObj;
  }

  /* curl ipinfo.io/98.124.243.43/
  {
  "ip": "98.124.243.43",
  "hostname": "No Hostname",
  "city": "Kirkland",
  "region": "Washington",
  "country": "US",
  "loc": "47.6727,-122.1873",
  "org": "AS21740 eNom, Incorporated",
  "postal": "98033"
  }
  */
  async ipinfo(ip) {
    log.debug("Looking up location:", ip);

    let cached = await this.cacheLookup(ip, "ipinfo");

    if (cached === "none") {
      return null;
    }

    let ipinfo;
    if (cached && cached !== 'null') {
      try {
        ipinfo = JSON.parse(cached);
      } catch (err) {
        log.error("Error when parse cache:", cached, err);
      }
      if (ipinfo) {
        log.debug("Cached ip info:", ip);
        return ipinfo;
      }
    }

    ipinfo = await IpInfo.get(ip);

    if (ipinfo) {
      this.cacheAdd(ip, "ipinfo", JSON.stringify(ipinfo));
    }

    return ipinfo;
  }
}
<|endoftext|>