diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..bf26cd7a --- /dev/null +++ b/.coveragerc @@ -0,0 +1,30 @@ +[report] + +include = + *glances* + +omit = + setup.py + glances/outputs/* + glances/exports/* + glances/compat.py + glances/autodiscover.py + glances/client_browser.py + glances/config.py + glances/history.py + glances/monitored_list.py + glances/outdated.py + glances/password*.py + glances/snmp.py + glances/static_list.py + +exclude_lines = + pragma: no cover + if PY3: + if __name__ == .__main__.: + if sys.platform.startswith + except ImportError: + raise NotImplementedError + if WINDOWS + if OSX + if BSD diff --git a/.gitattributes b/.gitattributes index cd8bbde4..9d598c00 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -glances/outputs/static/public/js/*.js -diff +glances/outputs/static/public/* -diff linguist-vendored diff --git a/NEWS b/NEWS index 8adeddbd..f33def1d 100644 --- a/NEWS +++ b/NEWS @@ -5,16 +5,35 @@ Glances Version 2 Version 2.8 =========== +Deprecated: + + * Deprecate Windows Curse UI: automaticaly open Web Browser for the standalone mode (issue #946) + +Changes: + + * IRQ plugin off by default. '--disable-irq' option replaced by '--enable-irq'. + Enhancements and new features: + * Add ZeroMQ exporter (issue #939) * Add CouchDB exporter (issue #928) + * Add hotspot Wifi informations (issue #937) + * Add default interface speed and automatic rate thresolds (issue #718) * Highlight max stats in the processes list (issue #878) + * Docker alerts and actions (issue #875) * Glances API returns the processes PPID (issue #926) * Configure server cached time from the command line --cached-time (issue #901) + * Make the log logger configurable (issue #900) + * System uptime in export (issue #890) + * Refactor the --disable-* options (issue #948) + * PID column too small if kernel.pid_max is > 99999 (issue #959) Bugs corrected: * Glances RAID plugin Traceback (issue #927) + * Default AMP crashes when 'command' given (issue #933) + * Default AMP ignores `enable` setting (issue #932) + * /proc/interrupts not found in an OpenVZ container (issue #947) Version 2.7.1 ============= diff --git a/README.rst b/README.rst index fdbdf568..e7ec368c 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,7 @@ Optional dependencies: - ``bernhard`` (for the Riemann export module) - ``py-cpuinfo`` (for the Quicklook CPU info module) - ``scandir`` (for the Folders plugin) [Only for Python < 3.5] +- ``wifi`` (for the wifi plugin) [Linux-only] *Note for Python 2.6 users* @@ -109,14 +110,14 @@ features (like the Web interface, exports modules, sensors...): .. code-block:: console - pip install bottle requests batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra-driver scandir + pip install bottle requests batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra-driver scandir pyzmq To upgrade Glances to the latest version: .. code-block:: console pip install --upgrade glances - pip install --upgrade bottle requests batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra-driver scandir + pip install --upgrade bottle requests batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra-driver scandir pyzmq If you need to install Glances in a specific user location, use: diff --git a/conf/glances.conf b/conf/glances.conf index a89a6815..990da6ec 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -96,11 +96,20 @@ careful=50 warning=70 critical=90 -#[network] +[network] +# Default bitrate thresholds in % of the network interface speed +# Default values if not defined: 70/80/90 +rx_careful=70 +rx_warning=80 +rx_critical=90 +tx_careful=70 +tx_warning=80 +tx_critical=90 # Define the list of hidden network interfaces (comma-separated regexp) #hide=docker.*,lo # WLAN 0 alias #wlan0_alias=Wireless IF +# It is possible to overwrite the bitrate thresholds per interface # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate #wlan0_rx_careful=4000000 #wlan0_rx_warning=5000000 @@ -111,6 +120,15 @@ critical=90 #wlan0_tx_critical=1000000 #wlan0_tx_log=True +[wifi] +# Define the list of hidden wireless network interfaces (comma-separated regexp) +hide=lo,docker.* +# Define SIGNAL thresholds in db (lower is better...) +# Based on: http://serverfault.com/questions/501025/industry-standard-for-minimum-wifi-signal-strength +careful=-65 +warning=-75 +critical=-85 + #[diskio] # Define the list of hidden disks (comma-separated regexp) #hide=sda2,sda5,loop.* @@ -207,6 +225,19 @@ port_default_gateway=True #port_4_port=80 #port_4_rtt_warning=1000 +[docker] +# Thresholds for CPU and MEM (in %) +#cpu_careful=50 +#cpu_warning=70 +#cpu_critical=90 +#mem_careful=20 +#mem_warning=50 +#mem_critical=70 +# Per container thresholds +#containername_cpu_careful=10 +#containername_cpu_warning=20 +#containername_cpu_critical=30 + ############################################################################## # Client/server ############################################################################## @@ -306,6 +337,18 @@ db=glances #user=root #password=root +[zeromq] +# Configuration for the --export-zeromq option +# http://www.zeromq.org +# Use * to bind on all interfaces +host=* +port=5678 +# Glances envelopes the stats in a publish message with two frames: +# - First frame containing the following prefix (STRING) +# - Second frame with the Glances plugin name (STRING) +# - Third frame with the Glances plugin stats (JSON) +prefix=G + ############################################################################## # AMPS # * enable: Enable (true) or disable (false) the AMP @@ -352,10 +395,10 @@ status_url=http://localhost/nginx_status [amp_systemd] # Use the Systemd AMP enable=true -regex=\/usr\/lib\/systemd\/systemd +regex=\/lib\/systemd\/systemd refresh=30 one_line=true -systemctl_cmd=/usr/bin/systemctl --plain +systemctl_cmd=/bin/systemctl --plain [amp_systemv] # Use the Systemv AMP diff --git a/Dockerfile b/docker/devel/Dockerfile similarity index 100% rename from Dockerfile rename to docker/devel/Dockerfile diff --git a/docker/master/Dockerfile b/docker/master/Dockerfile new file mode 100644 index 00000000..c907b327 --- /dev/null +++ b/docker/master/Dockerfile @@ -0,0 +1,25 @@ +# +# Glances Dockerfile +# +# https://github.com/nicolargo/glances +# + +# Pull base image. +FROM ubuntu + +# Install Glances (develop branch) +RUN apt-get update && apt-get -y install curl && rm -rf /var/lib/apt/lists/* +RUN curl -L https://raw.githubusercontent.com/nicolargo/glancesautoinstall/master/install.sh | /bin/bash && rm -rf /var/lib/apt/lists/* + + +# Define working directory. +WORKDIR /glances + +# EXPOSE PORT (For XMLRPC) +EXPOSE 61209 + +# EXPOSE PORT (For Web UI) +EXPOSE 61208 + +# Define default command. +CMD python -m glances -C /glances/conf/glances.conf $GLANCES_OPT diff --git a/docs/_static/wifi.png b/docs/_static/wifi.png new file mode 100644 index 00000000..328c556e Binary files /dev/null and b/docs/_static/wifi.png differ diff --git a/docs/aoa/cpu.rst b/docs/aoa/cpu.rst index 9af80ff8..c8acde26 100644 --- a/docs/aoa/cpu.rst +++ b/docs/aoa/cpu.rst @@ -16,13 +16,21 @@ displayed. CPU stats description: * user: percent time spent in user space +> User CPU time is time spent on the processor running your program's code (or code in libraries) * system: percent time spent in kernel space +> System CPU time is the time spent running code in the operating system kernel * idle: percent of CPU used by any program +> Every program or task that runs on a computer system occupies a certain amount of processing time on the CPU. If the CPU has completed all tasks it is idle. * nice: percent time occupied by user level processes with a positive nice value +> The time the CPU has spent running users' processes that have been "niced" * irq: percent time spent servicing/handling hardware/software interrupts +> Time servicing interrupts (hardware + software) * iowait: percent time spent in wait (on disk) -* steal: percent time in involuntary wait by virtual cpu while hypervisor is servicing another processor/virtual machine +> Time spent by the CPU waiting for a IO operations to complete +* steal: percent time in involuntary wait by virtual CPU +> Steal time is the percentage of time a virtual CPU waits for a real CPU while the hypervisor is servicing another virtual processor * ctx_sw: number of context switches (voluntary + involuntary) per second +> A context switch is a procedure that a computer's CPU (central processing unit) follows to change from one task (or process) to another while ensuring that the tasks do not conflict * inter: number of interrupts per second * sw_inter: number of software interrupts per second. Always set to 0 on Windows and SunOS. * syscal: number of system calls per second. Do not displayed on Linux (always 0). diff --git a/docs/aoa/docker.rst b/docs/aoa/docker.rst index b5bcd047..c4585e75 100644 --- a/docs/aoa/docker.rst +++ b/docs/aoa/docker.rst @@ -8,4 +8,25 @@ Glances uses the Docker API through the `docker-py`_ library. .. image:: ../_static/docker.png +It is possible to define limits and actions from the configuration file +under the ``[docker]`` section: + +.. code-block:: ini + + [docker] + # Global containers' thresholds for CPU and MEM (in %) + cpu_careful=50 + cpu_warning=70 + cpu_critical=90 + mem_careful=20 + mem_warning=50 + mem_critical=70 + # Per container thresholds + containername_cpu_careful=10 + containername_cpu_warning=20 + containername_cpu_critical=30 + containername_cpu_critical_action=echo {{Image}} {{Id}} {{cpu}} > /tmp/container_{{name}}.alert + +You can use all the variables ({{foo}}) available in the Docker plugin. + .. _docker-py: https://github.com/docker/docker-py diff --git a/docs/aoa/index.rst b/docs/aoa/index.rst index adccf082..e9526756 100644 --- a/docs/aoa/index.rst +++ b/docs/aoa/index.rst @@ -26,6 +26,7 @@ Legend: load memory network + wifi ports disk fs diff --git a/docs/aoa/irq.rst b/docs/aoa/irq.rst index 8df73c1e..6969bdc6 100644 --- a/docs/aoa/irq.rst +++ b/docs/aoa/irq.rst @@ -8,6 +8,8 @@ IRQ Glances displays the top 5 interrupts rate. This plugin is only available on GNU/Linux machine (stats are grabbed from the /proc/interrupts file). +Note: /proc/interrupts file did not exist inside OpenVZ containers. + How to read the informations: * The first Column is the IRQ number / name diff --git a/docs/aoa/network.rst b/docs/aoa/network.rst index 56e8c7f8..c27aba50 100644 --- a/docs/aoa/network.rst +++ b/docs/aoa/network.rst @@ -8,16 +8,20 @@ Network Glances displays the network interface bit rate. The unit is adapted dynamically (bit/s, kbit/s, Mbit/s, etc). -Alerts are only set if the maximum speed per network interface is -available (see sample in the configuration file). +If the interface speed is detected (not on all systems), the defaults +thresholds are applied (70% for careful, 80% warning and 90% critical). +It is possible to define this percents thresholds form the configuration +file. It is also possible to define per interface bit rate thresholds. +In this case thresholds values are define in bps. -It's also possibile to define: +Additionally, you can define: - a list of network interfaces to hide - per-interface limit values - aliases for interface name -in the ``[network]`` section of the configuration file. +The configuration should be done in the ``[network]`` section of the +Glances configuration file. For example, if you want to hide the loopback interface (lo) and all the virtual docker interface (docker0, docker1, ...): @@ -25,4 +29,25 @@ virtual docker interface (docker0, docker1, ...): .. code-block:: ini [network] - hide=lo,docker.* + # Default bitrate thresholds in % of the network interface speed + # Default values if not defined: 70/80/90 + rx_careful=70 + rx_warning=80 + rx_critical=90 + tx_careful=70 + tx_warning=80 + tx_critical=90 + # Define the list of hidden network interfaces (comma-separated regexp) + hide=docker.*,lo + # WLAN 0 alias + wlan0_alias=Wireless IF + # It is possible to overwrite the bitrate thresholds per interface + # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate + wlan0_rx_careful=4000000 + wlan0_rx_warning=5000000 + wlan0_rx_critical=6000000 + wlan0_rx_log=True + wlan0_tx_careful=700000 + wlan0_tx_warning=900000 + wlan0_tx_critical=1000000 + wlan0_tx_log=True diff --git a/docs/aoa/ps.rst b/docs/aoa/ps.rst index 3d8b5c4c..a1da1575 100644 --- a/docs/aoa/ps.rst +++ b/docs/aoa/ps.rst @@ -52,23 +52,25 @@ Columns display ``VIRT`` Virtual Memory Size The total amount of virtual memory used by the - process + process. It includes all code, data and shared + libraries plus pages that have been swapped out + and pages that have been mapped but not used. ``RES`` Resident Memory Size The non-swapped physical memory a process is - using + using (what's currently in the physical memory). ``PID`` Process ID ``USER`` User ID ``NI`` Nice level of the process ``S`` Process status The status of the process: - - - ``R``: running - - ``S``: sleeping (may be interrupted) - - ``D``: disk sleep (may not be interrupted) - - ``T``: traced/stopped - - ``Z``: zombie + - ``R``: running or runnable (on run queue) + - ``S``: interruptible sleep (waiting for an event) + - ``D``: uninterruptible sleep (usually IO) + - ``Z``: defunct ("zombie") process + - ``T``: traced/stopped by job control signal + - ``X``: dead (should never be seen) ``TIME+`` Cumulative CPU time used by the process ``R/s`` Per process I/O read rate in B/s diff --git a/docs/aoa/wifi.rst b/docs/aoa/wifi.rst new file mode 100644 index 00000000..69012735 --- /dev/null +++ b/docs/aoa/wifi.rst @@ -0,0 +1,31 @@ +.. _wifi: + +Wifi +===== + +*Availability: Linux* + +.. image:: ../_static/wifi.png + +Glances displays the Wifi hotspots' name and signal quality. +If Glances is ran as root, then all the available hotspots are displayed. + +In the configuration file, you can define signal quality thresholds. +"Poor" quality is between -100 and -85dBm, "Good" quality between -85 +and -60dBm, and "Excellent" between -60 and -40dBm. + +It's also possible to disable the scan on a specific interface from the +configuration file (``[wifi]`` section). For example, if you want to +hide the loopback interface (lo) and all the virtual docker interfaces: + +.. code-block:: ini + + [wifi] + hide=lo,docker.* + # Define SIGNAL thresholds in dBm (lower is better...) + careful=-65 + warning=-75 + critical=-85 + +You can disable this plugin using the --disable-wifi option or by heating +the 'W' from the user interface. diff --git a/docs/cmds.rst b/docs/cmds.rst index 9b9fd2b0..43cb7ac9 100644 --- a/docs/cmds.rst +++ b/docs/cmds.rst @@ -16,12 +16,101 @@ Command-Line Options .. option:: -d, --debug - enable debug mode + enable debug mode. The debugging output is saved to /tmp/glances.log. .. option:: -C CONF_FILE, --config CONF_FILE path to the configuration file +.. option:: --disable-alert + + disable alert/log module + +.. option:: --disable-amps + + disable application monitoring process module + +.. option:: --disable-cpu + + disable CPU module + +.. option:: --disable-diskio + + disable disk I/O module + +.. option:: --disable-docker + + disable Docker module + +.. option:: --disable-folders + + disable folders module + +.. option:: --disable-fs + + disable file system module + +.. option:: --disable-hddtemp + + disable HD temperature module + +.. option:: --disable-ip + + disable IP module + +.. option:: --disable-irq + + disable IRQ module + +.. option:: --disable-load + + disable load module + +.. option:: --disable-mem + + disable memory module + +.. option:: --disable-memswap + + disable memory swap module + +.. option:: --disable-network + + disable network module + +.. option:: --disable-ports + + disable Ports module + +.. option:: --disable-process + + disable process module + +.. option:: --disable-raid + + disable RAID module + +.. option:: --disable-sensors + + disable sensors module + +.. option:: --disable-wifi + + disable Wifi module + +.. option:: -0, --disable-irix + + task's CPU usage will be divided by the total number of CPUs + +.. option:: -1, --percpu + + start Glances in per CPU mode + +.. option:: -2, --disable-left-sidebar + + disable network, disk I/O, FS and sensors modules (py3sensors lib + needed) + .. option:: -3, --disable-quicklook disable quick look module @@ -30,86 +119,13 @@ Command-Line Options disable all but quick look and load -.. option:: --disable-cpu - - disable CPU module - -.. option:: --disable-mem - - disable memory module - -.. option:: --disable-swap - - disable swap module - -.. option:: --disable-load - - disable load module - -.. option:: --disable-network - - disable network module - -.. option:: --disable-ip - - disable IP module - -.. option:: --disable-diskio - - disable disk I/O module - -.. option:: --disable-fs - - disable filesystem module - -.. option:: --disable-folder - - disable folder module - -.. option:: --disable-irq - - disable IRQ module - -.. option:: --disable-sensors - - disable sensors module - -.. option:: --disable-hddtemp - - disable HD temperature module - -.. option:: --disable-raid - - disable RAID module - -.. option:: --disable-docker - - disable Docker module - -.. option:: --disable-ports - - disable Ports module - .. option:: -5, --disable-top disable top menu (QuickLook, CPU, MEM, SWAP and LOAD) -.. option:: -2, --disable-left-sidebar +.. option:: --enable-history - disable network, disk I/O, FS and sensors modules (py3sensors lib - needed) - -.. option:: --disable-process - - disable process module - -.. option:: --disable-amps - - disable application monitoring process module - -.. option:: --disable-log - - disable log module + enable the history mode (matplotlib lib needed) .. option:: --disable-bold @@ -123,11 +139,11 @@ Command-Line Options enable extended stats on top process -.. option:: --enable-history +.. option:: --export-graph - enable the history mode (matplotlib lib needed) + export stats to graph -.. option:: --path-history PATH_HISTORY +.. option:: --path-graph PATH_GRAPH set the export path for graph history @@ -135,37 +151,41 @@ Command-Line Options export stats to a CSV file -.. option:: --export-influxdb - - export stats to an InfluxDB server (influxdb lib needed) - .. option:: --export-cassandra export stats to a Cassandra/Scylla server (cassandra lib needed) -.. option:: --export-opentsdb +.. option:: --export-couchdb - export stats to an OpenTSDB server (potsdb lib needed) - -.. option:: --export-statsd - - export stats to a StatsD server (statsd lib needed) - -.. option:: --export-rabbitmq - - export stats to RabbitMQ broker (pika lib needed) - -.. option:: --export-riemann - - export stats to Riemann server (bernhard lib needed) + export stats to a CouchDB server (couchdb lib needed) .. option:: --export-elasticsearch export stats to an Elasticsearch server (elasticsearch lib needed) -.. option:: --export-couchdb +.. option:: --export-influxdb - export stats to a CouchDB server (couchdb lib needed) + export stats to an InfluxDB server (influxdb lib needed) + +.. option:: --export-opentsdb + + export stats to an OpenTSDB server (potsdb lib needed) + +.. option:: --export-rabbitmq + + export stats to RabbitMQ broker (pika lib needed) + +.. option:: --export-statsd + + export stats to a StatsD server (statsd lib needed) + +.. option:: --export-riemann + + export stats to Riemann server (bernhard lib needed) + +.. option:: --export-zeromq + + export stats to a ZeroMQ server (zmq lib needed) .. option:: -c CLIENT, --client CLIENT @@ -231,6 +251,14 @@ Command-Line Options run Glances in web server mode (bottle lib needed) +.. option:: --cached-time CACHED_TIME + + set the server cache time [default: 1 sec] + +.. option:: open-web-browser + + try to open the Web UI in the default Web browser + .. option:: -q, --quiet do not display the curses interface @@ -243,10 +271,6 @@ Command-Line Options force short name for processes name -.. option:: -0, --disable-irix - - task's CPU usage will be divided by the total number of CPUs - .. option:: --hide-kernel-threads hide kernel threads in process list @@ -271,10 +295,6 @@ Command-Line Options display temperature in Fahrenheit (default is Celsius) -.. option:: -1, --percpu - - start Glances in per CPU mode - .. option:: --fs-free-space display FS free space instead of used @@ -283,6 +303,10 @@ Command-Line Options optimize display colors for white background +.. option:: --disable-check-update + + disable online Glances version ckeck + Interactive Commands -------------------- @@ -398,6 +422,9 @@ The following commands (key pressed) are supported while in Glances: ``w`` Delete finished warning log messages +``W`` + Show/hide Wifi module + ``x`` Delete finished warning and critical log messages diff --git a/docs/config.rst b/docs/config.rst index a72f5be7..5e1060c9 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -110,3 +110,76 @@ By default, the ``glances.log`` file is under the temporary directory: If ``glances.log`` is not writable, a new file will be created and returned to the user console. + +If you want to use another system path or change the log message, you can use +your own logger configuration. First of all you have to create a glances.json +file with (for example) the following content (JSON format): + +.. code-block:: json + + { + "version": 1, + "disable_existing_loggers": "False", + "root": { + "level": "INFO", + "handlers": ["file", "console"] + }, + "formatters": { + "standard": { + "format": "%(asctime)s -- %(levelname)s -- %(message)s" + }, + "short": { + "format": "%(levelname)s: %(message)s" + }, + "free": { + "format": "%(message)s" + } + }, + "handlers": { + "file": { + "level": "DEBUG", + "class": "logging.handlers.RotatingFileHandler", + "formatter": "standard", + "filename": "/var/tmp/glances.log" + }, + "console": { + "level": "CRITICAL", + "class": "logging.StreamHandler", + "formatter": "free" + } + }, + "loggers": { + "debug": { + "handlers": ["file", "console"], + "level": "DEBUG" + }, + "verbose": { + "handlers": ["file", "console"], + "level": "INFO" + }, + "standard": { + "handlers": ["file"], + "level": "INFO" + }, + "requests": { + "handlers": ["file", "console"], + "level": "ERROR" + }, + "elasticsearch": { + "handlers": ["file", "console"], + "level": "ERROR" + }, + "elasticsearch.trace": { + "handlers": ["file", "console"], + "level": "ERROR" + } + } + } + +and start Glances using the following command line: + +.. code-block:: console + + LOG_CFG=/glances.json glances + +Note: Replace by the folder where your glances.json file is hosted. diff --git a/docs/gw/index.rst b/docs/gw/index.rst index b868863e..fb372ccc 100644 --- a/docs/gw/index.rst +++ b/docs/gw/index.rst @@ -10,10 +10,11 @@ to providing stats to multiple services (see list below). :maxdepth: 2 csv - influxdb cassandra - opentsdb - statsd - rabbitmq elastic + influxdb + opentsdb + rabbitmq riemann + statsd + zeromq diff --git a/docs/gw/zeromq.rst b/docs/gw/zeromq.rst new file mode 100644 index 00000000..5da68080 --- /dev/null +++ b/docs/gw/zeromq.rst @@ -0,0 +1,57 @@ +.. _zeromq: + +ZeroMQ +====== + +You can export statistics to a ``ZeroMQ`` server. + +The connection should be defined in the Glances configuration file as +following: + +.. code-block:: ini + + [zeromq] + host=127.0.0.1 + port=5678 + prefix=G + +Note: Glances `envelopes`_ the stats before publishing it. +The message is composed of three frames. + +- first frame containing the prefix configured in the [zeromq] section (as STRING) +- second frame with the Glances plugin name (as STRING) +- third frame with the Glances plugin stats (as JSON) + +Run Glances with: + +.. code-block:: console + + $ glances --export-zeromq -C /glances.conf + +Following is a simple Python client to subscribe to the Glances stats: + +.. code-block:: python + + # -*- coding: utf-8 -*- + # + # ZeroMQ subscriber for Glances + # + + import json + import zmq + + context = zmq.Context() + + subscriber = context.socket(zmq.SUB) + subscriber.setsockopt(zmq.SUBSCRIBE, 'G') + subscriber.connect("tcp://127.0.0.1:5678") + + while True: + _, plugin, data_raw = subscriber.recv_multipart() + data = json.loads(data_raw) + print('{} => {}'.format(plugin, data)) + + subscriber.close() + context.term() + +.. _envelopes: http://zguide.zeromq.org/page:all#Pub-Sub-Message-Envelopes diff --git a/docs/install.rst b/docs/install.rst index e67fc01a..cc36c12b 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -22,12 +22,13 @@ features (like the Web interface, exports modules, sensors...): .. code-block:: console - pip install bottle requests batinfo py3sensors zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra scandir couchdb + pip install bottle requests batinfo py3sensors zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra scandir couchdb pyzmq wifi -To upgrade Glances to the latest version: +To upgrade Glances and all its dependencies to the latests versions: .. code-block:: console + pip install --upgrade bottle requests batinfo py3sensors zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra scandir couchdb pyzmq wifi pip install --upgrade glances For additionnal installation methods, read the official `README`_ file. diff --git a/docs/man/glances.1 b/docs/man/glances.1 index 4e65242d..3a768dba 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "GLANCES" "1" "Sep 24, 2016" "2.8_DEVELOP" "Glances" +.TH "GLANCES" "1" "Nov 21, 2016" "2.8_DEVELOP" "Glances" .SH NAME glances \- An eye on your system . @@ -59,7 +59,7 @@ show program\(aqs version number and exit .INDENT 0.0 .TP .B \-d, \-\-debug -enable debug mode +enable debug mode. The debugging output is saved to /tmp/glances.log. .UNINDENT .INDENT 0.0 .TP @@ -68,6 +68,117 @@ path to the configuration file .UNINDENT .INDENT 0.0 .TP +.B \-\-disable\-alert +disable alert/log module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-amps +disable application monitoring process module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-cpu +disable CPU module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-diskio +disable disk I/O module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-docker +disable Docker module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-folders +disable folders module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-fs +disable file system module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-hddtemp +disable HD temperature module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-ip +disable IP module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-irq +disable IRQ module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-load +disable load module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-mem +disable memory module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-memswap +disable memory swap module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-network +disable network module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-ports +disable Ports module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-process +disable process module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-raid +disable RAID module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-sensors +disable sensors module +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-wifi +disable Wifi module +.UNINDENT +.INDENT 0.0 +.TP +.B \-0, \-\-disable\-irix +task\(aqs CPU usage will be divided by the total number of CPUs +.UNINDENT +.INDENT 0.0 +.TP +.B \-1, \-\-percpu +start Glances in per CPU mode +.UNINDENT +.INDENT 0.0 +.TP +.B \-2, \-\-disable\-left\-sidebar +disable network, disk I/O, FS and sensors modules (py3sensors lib +needed) +.UNINDENT +.INDENT 0.0 +.TP .B \-3, \-\-disable\-quicklook disable quick look module .UNINDENT @@ -78,104 +189,13 @@ disable all but quick look and load .UNINDENT .INDENT 0.0 .TP -.B \-\-disable\-cpu -disable CPU module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-mem -disable memory module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-swap -disable swap module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-load -disable load module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-network -disable network module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-ip -disable IP module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-diskio -disable disk I/O module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-fs -disable filesystem module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-folder -disable folder module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-irq -disable IRQ module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-sensors -disable sensors module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-hddtemp -disable HD temperature module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-raid -disable RAID module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-docker -disable Docker module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-ports -disable Ports module -.UNINDENT -.INDENT 0.0 -.TP .B \-5, \-\-disable\-top disable top menu (QuickLook, CPU, MEM, SWAP and LOAD) .UNINDENT .INDENT 0.0 .TP -.B \-2, \-\-disable\-left\-sidebar -disable network, disk I/O, FS and sensors modules (py3sensors lib -needed) -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-process -disable process module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-amps -disable application monitoring process module -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-disable\-log -disable log module +.B \-\-enable\-history +enable the history mode (matplotlib lib needed) .UNINDENT .INDENT 0.0 .TP @@ -194,12 +214,12 @@ enable extended stats on top process .UNINDENT .INDENT 0.0 .TP -.B \-\-enable\-history -enable the history mode (matplotlib lib needed) +.B \-\-export\-graph +export stats to graph .UNINDENT .INDENT 0.0 .TP -.B \-\-path\-history PATH_HISTORY +.B \-\-path\-graph PATH_GRAPH set the export path for graph history .UNINDENT .INDENT 0.0 @@ -209,33 +229,13 @@ export stats to a CSV file .UNINDENT .INDENT 0.0 .TP -.B \-\-export\-influxdb -export stats to an InfluxDB server (influxdb lib needed) -.UNINDENT -.INDENT 0.0 -.TP .B \-\-export\-cassandra export stats to a Cassandra/Scylla server (cassandra lib needed) .UNINDENT .INDENT 0.0 .TP -.B \-\-export\-opentsdb -export stats to an OpenTSDB server (potsdb lib needed) -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-export\-statsd -export stats to a StatsD server (statsd lib needed) -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-export\-rabbitmq -export stats to RabbitMQ broker (pika lib needed) -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-export\-riemann -export stats to Riemann server (bernhard lib needed) +.B \-\-export\-couchdb +export stats to a CouchDB server (couchdb lib needed) .UNINDENT .INDENT 0.0 .TP @@ -244,8 +244,33 @@ export stats to an Elasticsearch server (elasticsearch lib needed) .UNINDENT .INDENT 0.0 .TP -.B \-\-export\-couchdb -export stats to a CouchDB server (couchdb lib needed) +.B \-\-export\-influxdb +export stats to an InfluxDB server (influxdb lib needed) +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-export\-opentsdb +export stats to an OpenTSDB server (potsdb lib needed) +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-export\-rabbitmq +export stats to RabbitMQ broker (pika lib needed) +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-export\-statsd +export stats to a StatsD server (statsd lib needed) +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-export\-riemann +export stats to Riemann server (bernhard lib needed) +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-export\-zeromq +export stats to a ZeroMQ server (zmq lib needed) .UNINDENT .INDENT 0.0 .TP @@ -329,6 +354,16 @@ run Glances in web server mode (bottle lib needed) .UNINDENT .INDENT 0.0 .TP +.B \-\-cached\-time CACHED_TIME +set the server cache time [default: 1 sec] +.UNINDENT +.INDENT 0.0 +.TP +.B open\-web\-browser +try to open the Web UI in the default Web browser +.UNINDENT +.INDENT 0.0 +.TP .B \-q, \-\-quiet do not display the curses interface .UNINDENT @@ -344,11 +379,6 @@ force short name for processes name .UNINDENT .INDENT 0.0 .TP -.B \-0, \-\-disable\-irix -task\(aqs CPU usage will be divided by the total number of CPUs -.UNINDENT -.INDENT 0.0 -.TP .B \-\-hide\-kernel\-threads hide kernel threads in process list .UNINDENT @@ -379,11 +409,6 @@ display temperature in Fahrenheit (default is Celsius) .UNINDENT .INDENT 0.0 .TP -.B \-1, \-\-percpu -start Glances in per CPU mode -.UNINDENT -.INDENT 0.0 -.TP .B \-\-fs\-free\-space display FS free space instead of used .UNINDENT @@ -392,6 +417,11 @@ display FS free space instead of used .B \-\-theme\-white optimize display colors for white background .UNINDENT +.INDENT 0.0 +.TP +.B \-\-disable\-check\-update +disable online Glances version ckeck +.UNINDENT .SH INTERACTIVE COMMANDS .sp The following commands (key pressed) are supported while in Glances: @@ -511,6 +541,9 @@ View cumulative network I/O .B \fBw\fP Delete finished warning log messages .TP +.B \fBW\fP +Show/hide Wifi module +.TP .B \fBx\fP Delete finished warning and critical log messages .TP diff --git a/glances/__init__.py b/glances/__init__.py index 0a3e54fa..f8079e95 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -20,13 +20,13 @@ """Init the Glances software.""" +# Import system libs import locale import platform import signal import sys # Global name -__appname__ = 'glances' __version__ = '2.8_DEVELOP' __author__ = 'Nicolas Hennion ' __license__ = 'LGPL' @@ -42,7 +42,9 @@ except ImportError: # Note: others Glances libs will be imported optionally from glances.logger import logger from glances.main import GlancesMain +from glances.globals import WINDOWS +# Check locale try: locale.setlocale(locale.LC_ALL, '') except locale.Error: @@ -93,6 +95,111 @@ def end(): sys.exit(0) +def start_standalone(config, args): + """Start the standalone mode""" + logger.info("Start standalone mode") + + # Share global var + global standalone + + # Import the Glances standalone module + from glances.standalone import GlancesStandalone + + # Init the standalone mode + standalone = GlancesStandalone(config=config, args=args) + + # Start the standalone (CLI) loop + standalone.serve_forever() + + +def start_clientbrowser(config, args): + """Start the browser client mode""" + logger.info("Start client mode (browser)") + + # Share global var + global client + + # Import the Glances client browser module + from glances.client_browser import GlancesClientBrowser + + # Init the client + client = GlancesClientBrowser(config=config, args=args) + + # Start the client loop + client.serve_forever() + + # Shutdown the client + client.end() + + +def start_client(config, args): + """Start the client mode""" + logger.info("Start client mode") + + # Share global var + global client + + # Import the Glances client browser module + from glances.client import GlancesClient + + # Init the client + client = GlancesClient(config=config, args=args) + + # Test if client and server are in the same major version + if not client.login(): + logger.critical("The server version is not compatible with the client") + sys.exit(2) + + # Start the client loop + client.serve_forever() + + # Shutdown the client + client.end() + + +def start_server(config, args): + """Start the server mode""" + logger.info("Start server mode") + + # Share global var + global server + + # Import the Glances server module + from glances.server import GlancesServer + + server = GlancesServer(cached_time=args.cached_time, + config=config, + args=args) + print('Glances server is running on {}:{}'.format(args.bind_address, args.port)) + + # Set the server login/password (if -P/--password tag) + if args.password != "": + server.add_user(args.username, args.password) + + # Start the server loop + server.serve_forever() + + # Shutdown the server? + server.server_close() + + +def start_webserver(config, args): + """Start the Web server mode""" + logger.info("Start web server mode") + + # Share global var + global webserver + + # Import the Glances web server module + from glances.webserver import GlancesWebServer + + # Init the web server mode + webserver = GlancesWebServer(config=config, args=args) + + # Start the web server loop + webserver.serve_forever() + + def main(): """Main entry point for Glances. @@ -107,92 +214,29 @@ def main(): psutil_version)) # Share global var - global core, standalone, client, server, webserver + global core # Create the Glances main instance core = GlancesMain() + config = core.get_config() + args = core.get_args() # Catch the CTRL-C signal signal.signal(signal.SIGINT, __signal_handler) # Glances can be ran in standalone, client or server mode - if core.is_standalone(): - logger.info("Start standalone mode") - - # Import the Glances standalone module - from glances.standalone import GlancesStandalone - - # Init the standalone mode - standalone = GlancesStandalone(config=core.get_config(), - args=core.get_args()) - - # Start the standalone (CLI) loop - standalone.serve_forever() - - elif core.is_client(): + if core.is_standalone() and not WINDOWS: + start_standalone(config=config, args=args) + elif core.is_client() and not WINDOWS: if core.is_client_browser(): - logger.info("Start client mode (browser)") - - # Import the Glances client browser module - from glances.client_browser import GlancesClientBrowser - - # Init the client - client = GlancesClientBrowser(config=core.get_config(), - args=core.get_args()) - + start_clientbrowser(config=config, args=args) else: - logger.info("Start client mode") - - # Import the Glances client module - from glances.client import GlancesClient - - # Init the client - client = GlancesClient(config=core.get_config(), - args=core.get_args()) - - # Test if client and server are in the same major version - if not client.login(): - logger.critical("The server version is not compatible with the client") - sys.exit(2) - - # Start the client loop - client.serve_forever() - - # Shutdown the client - client.end() - + start_client(config=config, args=args) elif core.is_server(): - logger.info("Start server mode") - - # Import the Glances server module - from glances.server import GlancesServer - - args = core.get_args() - print args.cached_time - - server = GlancesServer(cached_time=args.cached_time, - config=core.get_config(), - args=args) - print('Glances server is running on {}:{}'.format(args.bind_address, args.port)) - # Set the server login/password (if -P/--password tag) - if args.password != "": - server.add_user(args.username, args.password) - - # Start the server loop - server.serve_forever() - - # Shutdown the server? - server.server_close() - - elif core.is_webserver(): - logger.info("Start web server mode") - - # Import the Glances web server module - from glances.webserver import GlancesWebServer - - # Init the web server mode - webserver = GlancesWebServer(config=core.get_config(), - args=core.get_args()) - - # Start the web server loop - webserver.serve_forever() + start_server(config=config, args=args) + elif core.is_webserver() or (core.is_standalone() and WINDOWS): + # Web server mode replace the standalone mode on Windows OS + # In this case, try to start the web browser mode automaticaly + if core.is_standalone() and WINDOWS: + args.open_web_browser = True + start_webserver(config=config, args=args) diff --git a/glances/amps/glances_default.py b/glances/amps/glances_default.py index 7392d796..8b388c46 100644 --- a/glances/amps/glances_default.py +++ b/glances/amps/glances_default.py @@ -35,7 +35,7 @@ one_line=false command=foo status """ -from subprocess import check_output, STDOUT +from subprocess import check_output, STDOUT, CalledProcessError from glances.compat import u, to_ascii from glances.logger import logger @@ -66,8 +66,11 @@ class Amp(GlancesAmp): logger.debug('{}: Error while executing service ({})'.format(self.NAME, e)) else: if res is not None: - msg = u(check_output(res.split(), stderr=STDOUT)) - self.set_result(to_ascii(msg.rstrip())) + try: + msg = u(check_output(res.split(), stderr=STDOUT)) + self.set_result(to_ascii(msg.rstrip())) + except CalledProcessError as e: + self.set_result(e.output) else: # Set the default message if command return None # Default sum of CPU and MEM for the matching regex diff --git a/glances/amps_list.py b/glances/amps_list.py index c9020717..cbb0fe22 100644 --- a/glances/amps_list.py +++ b/glances/amps_list.py @@ -106,9 +106,11 @@ class AmpsList(object): """Update the command result attributed.""" # Search application monitored processes by a regular expression processlist = glances_processes.getalllist() - # Iter upon the AMPs dict for k, v in iteritems(self.get()): + if not v.enable(): + # Do not update if the enable tag is set + continue try: amps_list = [p for p in processlist for c in p['cmdline'] if re.search(v.regex(), c) is not None] except TypeError: diff --git a/glances/autodiscover.py b/glances/autodiscover.py index 3e501253..ab2aa12f 100644 --- a/glances/autodiscover.py +++ b/glances/autodiscover.py @@ -22,7 +22,6 @@ import socket import sys -from glances import __appname__ from glances.globals import BSD from glances.logger import logger @@ -48,8 +47,8 @@ if zeroconf_tag: # Global var # Recent versions of the zeroconf python package doesnt like a zeroconf type that ends with '._tcp.'. -# Correct issue: zeroconf problem with zeroconf_type = "_%s._tcp." % __appname__ #888 -zeroconf_type = "_%s._tcp.local." % __appname__ +# Correct issue: zeroconf problem with zeroconf_type = "_%s._tcp." % 'glances' #888 +zeroconf_type = "_%s._tcp.local." % 'glances' class AutoDiscovered(object): diff --git a/glances/client_browser.py b/glances/client_browser.py index 23c7509e..ed922ab6 100644 --- a/glances/client_browser.py +++ b/glances/client_browser.py @@ -21,6 +21,7 @@ import json import socket +import threading from glances.compat import Fault, ProtocolError, ServerProxy from glances.autodiscover import GlancesAutoDiscoverServer @@ -90,144 +91,150 @@ class GlancesClientBrowser(object): else: return 'http://{}:{}'.format(server['ip'], server['port']) + def __update_stats(self, server): + """ + Update stats for the given server (picked from the server list) + """ + # Get the server URI + uri = self.__get_uri(server) + + # Try to connect to the server + t = GlancesClientTransport() + t.set_timeout(3) + + # Get common stats + try: + s = ServerProxy(uri, transport=t) + except Exception as e: + logger.warning( + "Client browser couldn't create socket {}: {}".format(uri, e)) + else: + # Mandatory stats + try: + # CPU% + cpu_percent = 100 - json.loads(s.getCpu())['idle'] + server['cpu_percent'] = '{:.1f}'.format(cpu_percent) + # MEM% + server['mem_percent'] = json.loads(s.getMem())['percent'] + # OS (Human Readable name) + server['hr_name'] = json.loads(s.getSystem())['hr_name'] + except (socket.error, Fault, KeyError) as e: + logger.debug( + "Error while grabbing stats form {}: {}".format(uri, e)) + server['status'] = 'OFFLINE' + except ProtocolError as e: + if e.errcode == 401: + # Error 401 (Authentication failed) + # Password is not the good one... + server['password'] = None + server['status'] = 'PROTECTED' + else: + server['status'] = 'OFFLINE' + logger.debug("Cannot grab stats from {} ({} {})".format(uri, e.errcode, e.errmsg)) + else: + # Status + server['status'] = 'ONLINE' + + # Optional stats (load is not available on Windows OS) + try: + # LOAD + load_min5 = json.loads(s.getLoad())['min5'] + server['load_min5'] = '{:.2f}'.format(load_min5) + except Exception as e: + logger.warning( + "Error while grabbing stats form {}: {}".format(uri, e)) + + return server + + def __display_server(self, server): + """ + Connect and display the given server + """ + # Display the Glances client for the selected server + logger.debug("Selected server: {}".format(server)) + + # Connection can take time + # Display a popup + self.screen.display_popup( + 'Connect to {}:{}'.format(server['name'], server['port']), duration=1) + + # A password is needed to access to the server's stats + if server['password'] is None: + # First of all, check if a password is available in the [passwords] section + clear_password = self.password.get_password(server['name']) + if (clear_password is None or self.get_servers_list() + [self.screen.active_server]['status'] == 'PROTECTED'): + # Else, the password should be enter by the user + # Display a popup to enter password + clear_password = self.screen.display_popup( + 'Password needed for {}: '.format(server['name']), is_input=True) + # Store the password for the selected server + if clear_password is not None: + self.set_in_selected('password', self.password.sha256_hash(clear_password)) + + # Display the Glance client on the selected server + logger.info("Connect Glances client to the {} server".format(server['key'])) + + # Init the client + args_server = self.args + + # Overwrite connection setting + args_server.client = server['ip'] + args_server.port = server['port'] + args_server.username = server['username'] + args_server.password = server['password'] + client = GlancesClient(config=self.config, args=args_server, return_to_browser=True) + + # Test if client and server are in the same major version + if not client.login(): + self.screen.display_popup( + "Sorry, cannot connect to '{}'\n" + "See 'glances.log' for more details".format(server['name'])) + + # Set the ONLINE status for the selected server + self.set_in_selected('status', 'OFFLINE') + else: + # Start the client loop + # Return connection type: 'glances' or 'snmp' + connection_type = client.serve_forever() + + try: + logger.debug("Disconnect Glances client from the {} server".format(server['key'])) + except IndexError: + # Server did not exist anymore + pass + else: + # Set the ONLINE status for the selected server + if connection_type == 'snmp': + self.set_in_selected('status', 'SNMP') + else: + self.set_in_selected('status', 'ONLINE') + + # Return to the browser (no server selected) + self.screen.active_server = None + def __serve_forever(self): """Main client loop.""" - while True: - # No need to update the server list - # It's done by the GlancesAutoDiscoverListener class (autodiscover.py) - # Or define staticaly in the configuration file (module static_list.py) - # For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...) - # logger.debug(self.get_servers_list()) - try: - for v in self.get_servers_list(): - # Do not retreive stats for statics server - # Why ? Because for each offline servers, the timeout will be reached - # So ? The curse interface freezes - if v['type'] == 'STATIC' and v['status'] in ['UNKNOWN', 'SNMP', 'OFFLINE']: - continue + # No need to update the server list + # It's done by the GlancesAutoDiscoverListener class (autodiscover.py) + # Or define staticaly in the configuration file (module static_list.py) + # For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...) - # Get the server URI - uri = self.__get_uri(v) + logger.debug("Iter through the following server list: {}".format(self.get_servers_list())) + for v in self.get_servers_list(): + thread = threading.Thread(target=self.__update_stats, args=[v]) + thread.start() - # Try to connect to the server - t = GlancesClientTransport() - t.set_timeout(3) + # Update the screen (list or Glances client) + if self.screen.active_server is None: + # Display the Glances browser + self.screen.update(self.get_servers_list()) + else: + # Display the active server + self.__display_server(self.get_servers_list()[self.screen.active_server]) - # Get common stats - try: - s = ServerProxy(uri, transport=t) - except Exception as e: - logger.warning( - "Client browser couldn't create socket {}: {}".format(uri, e)) - else: - # Mandatory stats - try: - # CPU% - cpu_percent = 100 - json.loads(s.getCpu())['idle'] - v['cpu_percent'] = '{:.1f}'.format(cpu_percent) - # MEM% - v['mem_percent'] = json.loads(s.getMem())['percent'] - # OS (Human Readable name) - v['hr_name'] = json.loads(s.getSystem())['hr_name'] - except (socket.error, Fault, KeyError) as e: - logger.debug( - "Error while grabbing stats form {}: {}".format(uri, e)) - v['status'] = 'OFFLINE' - except ProtocolError as e: - if e.errcode == 401: - # Error 401 (Authentication failed) - # Password is not the good one... - v['password'] = None - v['status'] = 'PROTECTED' - else: - v['status'] = 'OFFLINE' - logger.debug("Cannot grab stats from {} ({} {})".format(uri, e.errcode, e.errmsg)) - else: - # Status - v['status'] = 'ONLINE' - - # Optional stats (load is not available on Windows OS) - try: - # LOAD - load_min5 = json.loads(s.getLoad())['min5'] - v['load_min5'] = '{:.2f}'.format(load_min5) - except Exception as e: - logger.warning( - "Error while grabbing stats form {}: {}".format(uri, e)) - # List can change size during iteration... - except RuntimeError: - logger.debug( - "Server list dictionnary change inside the loop (wait next update)") - - # Update the screen (list or Glances client) - if self.screen.active_server is None: - # Display the Glances browser - self.screen.update(self.get_servers_list()) - else: - # Display the Glances client for the selected server - logger.debug("Selected server: {}".format(self.get_servers_list()[self.screen.active_server])) - - # Connection can take time - # Display a popup - self.screen.display_popup( - 'Connect to {}:{}'.format(v['name'], v['port']), duration=1) - - # A password is needed to access to the server's stats - if self.get_servers_list()[self.screen.active_server]['password'] is None: - # First of all, check if a password is available in the [passwords] section - clear_password = self.password.get_password(v['name']) - if (clear_password is None or self.get_servers_list() - [self.screen.active_server]['status'] == 'PROTECTED'): - # Else, the password should be enter by the user - # Display a popup to enter password - clear_password = self.screen.display_popup( - 'Password needed for {}: '.format(v['name']), is_input=True) - # Store the password for the selected server - if clear_password is not None: - self.set_in_selected('password', self.password.sha256_hash(clear_password)) - - # Display the Glance client on the selected server - logger.info("Connect Glances client to the {} server".format( - self.get_servers_list()[self.screen.active_server]['key'])) - - # Init the client - args_server = self.args - - # Overwrite connection setting - args_server.client = self.get_servers_list()[self.screen.active_server]['ip'] - args_server.port = self.get_servers_list()[self.screen.active_server]['port'] - args_server.username = self.get_servers_list()[self.screen.active_server]['username'] - args_server.password = self.get_servers_list()[self.screen.active_server]['password'] - client = GlancesClient(config=self.config, args=args_server, return_to_browser=True) - - # Test if client and server are in the same major version - if not client.login(): - self.screen.display_popup( - "Sorry, cannot connect to '{}'\n" - "See 'glances.log' for more details".format(v['name'])) - - # Set the ONLINE status for the selected server - self.set_in_selected('status', 'OFFLINE') - else: - # Start the client loop - # Return connection type: 'glances' or 'snmp' - connection_type = client.serve_forever() - - try: - logger.debug("Disconnect Glances client from the {} server".format( - self.get_servers_list()[self.screen.active_server]['key'])) - except IndexError: - # Server did not exist anymore - pass - else: - # Set the ONLINE status for the selected server - if connection_type == 'snmp': - self.set_in_selected('status', 'SNMP') - else: - self.set_in_selected('status', 'ONLINE') - - # Return to the browser (no server selected) - self.screen.active_server = None + # Loop + self.__serve_forever() def serve_forever(self): """Wrapper to the serve_forever function. diff --git a/glances/compat.py b/glances/compat.py index cbc74a06..5a1f7b83 100644 --- a/glances/compat.py +++ b/glances/compat.py @@ -28,12 +28,6 @@ import types PY3 = sys.version_info[0] == 3 - -def to_ascii(s): - """Convert the unicode 's' to a ASCII string - Usefull to remove accent (diacritics)""" - return unicodedata.normalize('NFKD', s).encode('ASCII', 'ignore') - if PY3: import queue from configparser import ConfigParser, NoOptionError, NoSectionError @@ -54,6 +48,11 @@ if PY3: viewvalues = operator.methodcaller('values') viewitems = operator.methodcaller('items') + def to_ascii(s): + """Convert the bytes string to a ASCII string + Usefull to remove accent (diacritics)""" + return str(s, 'utf-8') + def listitems(d): return list(d.items()) @@ -104,6 +103,11 @@ else: viewvalues = operator.methodcaller('viewvalues') viewitems = operator.methodcaller('viewitems') + def to_ascii(s): + """Convert the unicode 's' to a ASCII string + Usefull to remove accent (diacritics)""" + return unicodedata.normalize('NFKD', s).encode('ASCII', 'ignore') + def listitems(d): return d.items() diff --git a/glances/config.py b/glances/config.py index 309e257d..7b2d3c7f 100644 --- a/glances/config.py +++ b/glances/config.py @@ -24,7 +24,6 @@ import sys import multiprocessing from io import open -from glances import __appname__ from glances.compat import ConfigParser, NoOptionError from glances.globals import BSD, LINUX, OSX, WINDOWS, sys_prefix from glances.logger import logger @@ -71,22 +70,18 @@ class Config(object): paths.append( os.path.join(os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), - __appname__, self.config_filename)) + 'glances', self.config_filename)) if BSD: - paths.append( - os.path.join(sys.prefix, 'etc', __appname__, self.config_filename)) + paths.append(os.path.join(sys.prefix, 'etc', 'glances', self.config_filename)) else: - paths.append( - os.path.join('/etc', __appname__, self.config_filename)) + paths.append(os.path.join('/etc/glances', self.config_filename)) elif OSX: paths.append( - os.path.join(os.path.expanduser('~/Library/Application Support/'), - __appname__, self.config_filename)) - paths.append( - os.path.join(sys_prefix, 'etc', __appname__, self.config_filename)) + os.path.join(os.path.expanduser('~/Library/Application Support/glances'), + self.config_filename)) + paths.append(os.path.join(sys_prefix, 'etc', 'glances', self.config_filename)) elif WINDOWS: - paths.append( - os.path.join(os.environ.get('APPDATA'), __appname__, self.config_filename)) + paths.append(os.path.join(os.environ.get('APPDATA'), 'glances', self.config_filename)) return paths @@ -172,6 +167,16 @@ class Config(object): self.set_default('memswap', 'warning', '70') self.set_default('memswap', 'critical', '90') + # NETWORK + if not self.parser.has_section('network'): + self.parser.add_section('network') + self.set_default('network', 'rx_careful', '70') + self.set_default('network', 'rx_warning', '80') + self.set_default('network', 'rx_critical', '90') + self.set_default('network', 'tx_careful', '70') + self.set_default('network', 'tx_warning', '80') + self.set_default('network', 'tx_critical', '90') + # FS if not self.parser.has_section('fs'): self.parser.add_section('fs') @@ -207,6 +212,15 @@ class Config(object): """Return the loaded configuration file.""" return self._loaded_config_file + def as_dict(self): + """Return the configuration as a dict""" + dictionary = {} + for section in self.parser.sections(): + dictionary[section] = {} + for option in self.parser.options(section): + dictionary[section][option] = self.parser.get(section, option) + return dictionary + def sections(self): """Return a list of all sections.""" return self.parser.sections() @@ -231,6 +245,13 @@ class Config(object): except NoOptionError: return default + def get_int_value(self, section, option, default=0): + """Get the int value of an option, if it exists.""" + try: + return self.parser.getint(section, option) + except NoOptionError: + return int(default) + def get_float_value(self, section, option, default=0.0): """Get the float value of an option, if it exists.""" try: diff --git a/glances/exports/glances_csv.py b/glances/exports/glances_csv.py index 833c6da6..b854e51b 100644 --- a/glances/exports/glances_csv.py +++ b/glances/exports/glances_csv.py @@ -68,7 +68,8 @@ class Export(GlancesExport): plugins = stats.getAllPlugins() # Init data with timestamp (issue#708) - csv_header = ['timestamp'] + if self.first_line: + csv_header = ['timestamp'] csv_data = [time.strftime('%Y-%m-%d %H:%M:%S')] # Loop over available plugin diff --git a/glances/exports/glances_export.py b/glances/exports/glances_export.py index a0472810..96994395 100644 --- a/glances/exports/glances_export.py +++ b/glances/exports/glances_export.py @@ -64,7 +64,8 @@ class GlancesExport(object): 'system', 'uptime', 'sensors', - 'docker'] + 'docker', + 'uptime'] def get_item_key(self, item): """Return the value of the item 'key'.""" diff --git a/glances/exports/glances_zeromq.py b/glances/exports/glances_zeromq.py new file mode 100644 index 00000000..5a98179b --- /dev/null +++ b/glances/exports/glances_zeromq.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2016 Nicolargo +# +# Glances is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Glances 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +"""ZeroMQ interface class.""" + +import sys +import json +import zmq +from zmq.utils.strtypes import asbytes + +from glances.compat import NoOptionError, NoSectionError, b +from glances.logger import logger +from glances.exports.glances_export import GlancesExport + + +class Export(GlancesExport): + + """This class manages the ZeroMQ export module.""" + + def __init__(self, config=None, args=None): + """Init the ZeroMQ export IF.""" + super(Export, self).__init__(config=config, args=args) + + # Load the ZeroMQ configuration file section ([export_zeromq]) + self.host = None + self.port = None + self.export_enable = self.load_conf() + if not self.export_enable: + sys.exit(2) + + # Init the ZeroMQ context + self.context = None + self.client = self.init() + + def load_conf(self, section="zeromq"): + """Load the ZeroMQ configuration in the Glances configuration file.""" + if self.config is None: + return False + try: + self.host = self.config.get_value(section, 'host') + self.port = self.config.get_value(section, 'port') + self.prefix = str(self.config.get_value(section, 'prefix')) + except NoSectionError: + logger.critical("No ZeroMQ configuration found") + return False + except NoOptionError as e: + logger.critical("Error in the ZeroMQ configuration (%s)" % e) + return False + else: + logger.debug("Load ZeroMQ from the Glances configuration file") + + return True + + def init(self): + """Init the connection to the CouchDB server.""" + if not self.export_enable: + return None + + server_uri = 'tcp://{}:{}'.format(self.host, self.port) + + try: + self.context = zmq.Context() + publisher = self.context.socket(zmq.PUB) + publisher.bind(server_uri) + except Exception as e: + logger.critical("Cannot connect to ZeroMQ server %s (%s)" % (server_uri, e)) + sys.exit(2) + else: + logger.info("Connected to the ZeroMQ server %s" % server_uri) + + return publisher + + def exit(self): + """Close the socket and context""" + if self.client is not None: + self.client.close() + if self.context is not None: + self.context.destroy() + + def export(self, name, columns, points): + """Write the points to the ZeroMQ server.""" + logger.debug("Export {} stats to ZeroMQ".format(name)) + + # Create DB input + data = dict(zip(columns, points)) + + # Do not publish empty stats + if data == {}: + return False + + # Glances envelopes the stats in a publish message with two frames: + # - First frame containing the following prefix (STRING) + # - Second frame with the Glances plugin name (STRING) + # - Third frame with the Glances plugin stats (JSON) + message = [b(self.prefix), + b(name), + asbytes(json.dumps(data))] + + # Write data to the ZeroMQ bus + # Result can be view: tcp://host:port + try: + self.client.send_multipart(message) + except Exception as e: + logger.error("Cannot export {} stats to ZeroMQ ({})".format(name, e)) + + return True diff --git a/glances/logger.py b/glances/logger.py index df23d7da..3063382d 100644 --- a/glances/logger.py +++ b/glances/logger.py @@ -22,66 +22,66 @@ import logging import os import tempfile +import json from logging.config import dictConfig # Define the logging configuration LOGGING_CFG = { - 'version': 1, - 'disable_existing_loggers': False, - 'root': { - 'level': 'INFO', - 'handlers': ['file', 'console'] + "version": 1, + "disable_existing_loggers": "False", + "root": { + "level": "INFO", + "handlers": ["file", "console"] }, - 'formatters': { - 'standard': { - 'format': '%(asctime)s -- %(levelname)s -- %(message)s' + "formatters": { + "standard": { + "format": "%(asctime)s -- %(levelname)s -- %(message)s" }, - 'short': { - 'format': '%(levelname)s: %(message)s' + "short": { + "format": "%(levelname)s: %(message)s" }, - 'free': { - 'format': '%(message)s' + "free": { + "format": "%(message)s" } }, - 'handlers': { - 'file': { - 'level': 'DEBUG', - 'class': 'logging.handlers.RotatingFileHandler', - 'formatter': 'standard', - # http://stackoverflow.com/questions/847850/cross-platform-way-of-getting-temp-directory-in-python + "handlers": { + "file": { + "level": "DEBUG", + "class": "logging.handlers.RotatingFileHandler", + "formatter": "standard", 'filename': os.path.join(tempfile.gettempdir(), 'glances.log') }, - 'console': { - 'level': 'CRITICAL', - 'class': 'logging.StreamHandler', - 'formatter': 'free' + "console": { + "level": "CRITICAL", + "class": "logging.StreamHandler", + "formatter": "free" } }, - 'loggers': { - 'debug': { - 'handlers': ['file', 'console'], - 'level': 'DEBUG', + "loggers": { + "debug": { + "handlers": ["file", "console"], + "level": "DEBUG" }, - 'verbose': { - 'handlers': ['file', 'console'], - 'level': 'INFO' + "verbose": { + "handlers": ["file", "console"], + "level": "INFO" }, - 'standard': { - 'handlers': ['file'], - 'level': 'INFO' + "standard": { + "handlers": ["file"], + "level": "INFO" }, - 'requests': { - 'handlers': ['file', 'console'], - 'level': 'ERROR', + "requests": { + "handlers": ["file", "console"], + "level": "ERROR" }, - 'elasticsearch': { - 'handlers': ['file', 'console'], - 'level': 'ERROR', - }, - 'elasticsearch.trace': { - 'handlers': ['file', 'console'], - 'level': 'ERROR', + "elasticsearch": { + "handlers": ["file", "console"], + "level": "ERROR" }, + "elasticsearch.trace": { + "handlers": ["file", "console"], + "level": "ERROR" + } } } @@ -98,12 +98,31 @@ def tempfile_name(): return ret -def glances_logger(): - """Build and return the logger.""" - temp_path = tempfile_name() +def glances_logger(env_key='LOG_CFG'): + """Build and return the logger. + + env_key define the env var where a path to a specific JSON logger + could be defined + + :return: logger -- Logger instance + """ _logger = logging.getLogger() - LOGGING_CFG['handlers']['file']['filename'] = temp_path - dictConfig(LOGGING_CFG) + + # Overwrite the default logger file + LOGGING_CFG['handlers']['file']['filename'] = tempfile_name() + + # By default, use the LOGGING_CFG lgger configuration + config = LOGGING_CFG + + # Check if a specific configuration is available + user_file = os.getenv(env_key, None) + if user_file and os.path.exists(user_file): + # A user file as been defined. Use it... + with open(user_file, 'rt') as f: + config = json.load(f) + + # Load the configuration + dictConfig(config) return _logger diff --git a/glances/logs.py b/glances/logs.py index 4c81aeab..0e3d952e 100644 --- a/glances/logs.py +++ b/glances/logs.py @@ -147,7 +147,7 @@ class GlancesLogs(object): 1, # COUNT [], # TOP 3 PROCESS LIST proc_desc, # MONITORED PROCESSES DESC - 'cpu_percent'] # TOP PROCESS SORTKEY + glances_processes.sort_key] # TOP PROCESS SORTKEY # Add the item to the list self.logs_list.insert(0, item) diff --git a/glances/main.py b/glances/main.py index d710e740..64e7a90f 100644 --- a/glances/main.py +++ b/glances/main.py @@ -2,7 +2,7 @@ # # This file is part of Glances. # -# Copyright (C) 2015 Nicolargo +# Copyright (C) 2016 Nicolargo # # Glances is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by @@ -24,8 +24,8 @@ import os import sys import tempfile -from glances import __appname__, __version__, psutil_version -from glances.compat import input, NoOptionError, NoSectionError +from glances import __version__, psutil_version +from glances.compat import input from glances.config import Config from glances.globals import LINUX, WINDOWS from glances.logger import logger @@ -61,7 +61,7 @@ Monitor local machine with the Web interface (Web UI):\n\ Glances web server started on http://0.0.0.0:61208/\n\ \n\ Monitor local machine and export stats to a CSV file (standalone mode):\n\ - $ glances --export-csv\n\ + $ glances --export-csv /tmp/glances.csv\n\ \n\ Monitor local machine and export stats to a InfluxDB server with 5s refresh time (standalone mode):\n\ $ glances -t 5 --export-influxdb\n\ @@ -88,7 +88,7 @@ Start the client browser (browser mode):\n\ """Init all the command line arguments.""" version = "Glances v" + __version__ + " with psutil v" + psutil_version parser = argparse.ArgumentParser( - prog=__appname__, + prog='glances', conflict_handler='resolve', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=self.example_of_use) @@ -99,58 +99,64 @@ Start the client browser (browser mode):\n\ parser.add_argument('-C', '--config', dest='conf_file', help='path to the configuration file') # Enable or disable option on startup - parser.add_argument('-3', '--disable-quicklook', action='store_true', default=False, - dest='disable_quicklook', help='disable quick look module') - parser.add_argument('-4', '--full-quicklook', action='store_true', default=False, - dest='full_quicklook', help='disable all but quick look and load') + parser.add_argument('--disable-alert', action='store_true', default=False, + dest='disable_alert', help='disable alert module') + parser.add_argument('--disable-amps', action='store_true', default=False, + dest='disable_amps', help='disable applications monitoring process (AMP) module') parser.add_argument('--disable-cpu', action='store_true', default=False, dest='disable_cpu', help='disable CPU module') - parser.add_argument('--disable-mem', action='store_true', default=False, - dest='disable_mem', help='disable memory module') - parser.add_argument('--disable-swap', action='store_true', default=False, - dest='disable_swap', help='disable swap module') + parser.add_argument('--disable-diskio', action='store_true', default=False, + dest='disable_diskio', help='disable disk I/O module') + parser.add_argument('--disable-docker', action='store_true', default=False, + dest='disable_docker', help='disable Docker module') + parser.add_argument('--disable-folders', action='store_true', default=False, + dest='disable_folders', help='disable folder module') + parser.add_argument('--disable-fs', action='store_true', default=False, + dest='disable_fs', help='disable filesystem module') + parser.add_argument('--disable-hddtemp', action='store_true', default=False, + dest='disable_hddtemp', help='disable HD temperature module') + parser.add_argument('--disable-ip', action='store_true', default=False, + dest='disable_ip', help='disable IP module') parser.add_argument('--disable-load', action='store_true', default=False, dest='disable_load', help='disable load module') + parser.add_argument('--disable-mem', action='store_true', default=False, + dest='disable_mem', help='disable memory module') + parser.add_argument('--disable-memswap', action='store_true', default=False, + dest='disable_memswap', help='disable memory swap module') parser.add_argument('--disable-network', action='store_true', default=False, dest='disable_network', help='disable network module') parser.add_argument('--disable-ports', action='store_true', default=False, dest='disable_ports', help='disable ports scanner module') - parser.add_argument('--disable-ip', action='store_true', default=False, - dest='disable_ip', help='disable IP module') - parser.add_argument('--disable-diskio', action='store_true', default=False, - dest='disable_diskio', help='disable disk I/O module') - parser.add_argument('--disable-irq', action='store_true', default=False, - dest='disable_irq', help='disable IRQ module'), - parser.add_argument('--disable-fs', action='store_true', default=False, - dest='disable_fs', help='disable filesystem module') - parser.add_argument('--disable-folder', action='store_true', default=False, - dest='disable_folder', help='disable folder module') - parser.add_argument('--disable-sensors', action='store_true', default=False, - dest='disable_sensors', help='disable sensors module') - parser.add_argument('--disable-hddtemp', action='store_true', default=False, - dest='disable_hddtemp', help='disable HD temperature module') + parser.add_argument('--disable-process', action='store_true', default=False, + dest='disable_process', help='disable process module') parser.add_argument('--disable-raid', action='store_true', default=False, dest='disable_raid', help='disable RAID module') - parser.add_argument('--disable-docker', action='store_true', default=False, - dest='disable_docker', help='disable Docker module') - parser.add_argument('-5', '--disable-top', action='store_true', - default=False, dest='disable_top', - help='disable top menu (QL, CPU, MEM, SWAP and LOAD)') + parser.add_argument('--disable-sensors', action='store_true', default=False, + dest='disable_sensors', help='disable sensors module') + parser.add_argument('--disable-wifi', action='store_true', default=False, + dest='disable_wifi', help='disable wifi module') + parser.add_argument('-0', '--disable-irix', action='store_true', default=False, + dest='disable_irix', help='task\'s cpu usage will be divided by the total number of CPUs') + parser.add_argument('-1', '--percpu', action='store_true', default=False, + dest='percpu', help='start Glances in per CPU mode') parser.add_argument('-2', '--disable-left-sidebar', action='store_true', default=False, dest='disable_left_sidebar', help='disable network, disk I/O, FS and sensors modules') - parser.add_argument('--disable-process', action='store_true', default=False, - dest='disable_process', help='disable process module') - parser.add_argument('--disable-amps', action='store_true', default=False, - dest='disable_amps', help='disable applications monitoring process (AMP) module') - parser.add_argument('--disable-log', action='store_true', default=False, - dest='disable_log', help='disable log module') + parser.add_argument('-3', '--disable-quicklook', action='store_true', default=False, + dest='disable_quicklook', help='disable quick look module') + parser.add_argument('-4', '--full-quicklook', action='store_true', default=False, + dest='full_quicklook', help='disable all but quick look and load') + parser.add_argument('-5', '--disable-top', action='store_true', + default=False, dest='disable_top', + help='disable top menu (QL, CPU, MEM, SWAP and LOAD)') parser.add_argument('--disable-history', action='store_true', default=False, dest='disable_history', help='disable stats history') parser.add_argument('--disable-bold', action='store_true', default=False, dest='disable_bold', help='disable bold mode in the terminal') parser.add_argument('--disable-bg', action='store_true', default=False, dest='disable_bg', help='disable background colors in the terminal') + parser.add_argument('--enable-irq', action='store_true', default=False, + dest='enable_irq', help='enable IRQ module'), parser.add_argument('--enable-process-extended', action='store_true', default=False, dest='enable_process_extended', help='enable extended stats on top process') # Export modules feature @@ -176,6 +182,8 @@ Start the client browser (browser mode):\n\ dest='export_riemann', help='export stats to riemann broker (bernhard lib needed)') parser.add_argument('--export-couchdb', action='store_true', default=False, dest='export_couchdb', help='export stats to a CouchDB server (couch lib needed)') + parser.add_argument('--export-zeromq', action='store_true', default=False, + dest='export_zeromq', help='export stats to a ZeroMQ server (pyzmq lib needed)') # Client/Server option parser.add_argument('-c', '--client', dest='client', help='connect to a Glances server by IPv4/IPv6 address or hostname') @@ -211,6 +219,8 @@ Start the client browser (browser mode):\n\ dest='webserver', help='run Glances in web server mode (bottle needed)') parser.add_argument('--cached-time', default=self.cached_time, type=int, dest='cached_time', help='set the server cache time [default: {} sec]'.format(self.cached_time)) + parser.add_argument('--open-web-browser', action='store_true', default=False, + dest='open_web_browser', help='try to open the Web UI in the default Web browser') # Display options parser.add_argument('-q', '--quiet', default=False, action='store_true', dest='quiet', help='do not display the curses interface') @@ -218,8 +228,6 @@ Start the client browser (browser mode):\n\ dest='process_filter', help='set the process filter pattern (regular expression)') parser.add_argument('--process-short-name', action='store_true', default=False, dest='process_short_name', help='force short name for processes name') - parser.add_argument('-0', '--disable-irix', action='store_true', default=False, - dest='disable_irix', help='task\'s cpu usage will be divided by the total number of CPUs') if not WINDOWS: parser.add_argument('--hide-kernel-threads', action='store_true', default=False, dest='no_kernel_threads', help='hide kernel threads in process list') @@ -234,8 +242,6 @@ Start the client browser (browser mode):\n\ dest='diskio_iops', help='show IO per second in the DiskIO plugin') parser.add_argument('--fahrenheit', action='store_true', default=False, dest='fahrenheit', help='display temperature in Fahrenheit (default is Celsius)') - parser.add_argument('-1', '--percpu', action='store_true', default=False, - dest='percpu', help='start Glances in per CPU mode') parser.add_argument('--fs-free-space', action='store_true', default=False, dest='fs_free_space', help='display FS free space instead of used') parser.add_argument('--theme-white', action='store_true', default=False, @@ -278,11 +284,14 @@ Start the client browser (browser mode):\n\ args.password_prompt = True # Prompt username if args.server: - args.username = self.__get_username(description='Define the Glances server username: ') + args.username = self.__get_username( + description='Define the Glances server username: ') elif args.webserver: - args.username = self.__get_username(description='Define the Glances webserver username: ') + args.username = self.__get_username( + description='Define the Glances webserver username: ') elif args.client: - args.username = self.__get_username(description='Enter the Glances server username: ') + args.username = self.__get_username( + description='Enter the Glances server username: ') else: # Default user name is 'glances' args.username = self.username @@ -291,17 +300,20 @@ Start the client browser (browser mode):\n\ # Interactive or file password if args.server: args.password = self.__get_password( - description='Define the Glances server password ({} username): '.format(args.username), + description='Define the Glances server password ({} username): '.format( + args.username), confirm=True, username=args.username) elif args.webserver: args.password = self.__get_password( - description='Define the Glances webserver password ({} username): '.format(args.username), + description='Define the Glances webserver password ({} username): '.format( + args.username), confirm=True, username=args.username) elif args.client: args.password = self.__get_password( - description='Enter the Glances server password ({} username): '.format(args.username), + description='Enter the Glances server password ({} username): '.format( + args.username), clear=True, username=args.username) else: @@ -321,7 +333,7 @@ Start the client browser (browser mode):\n\ args.disable_quicklook = False args.disable_cpu = True args.disable_mem = True - args.disable_swap = True + args.disable_memswap = True args.disable_load = False # Manage disable_top option @@ -330,7 +342,7 @@ Start the client browser (browser mode):\n\ args.disable_quicklook = True args.disable_cpu = True args.disable_mem = True - args.disable_swap = True + args.disable_memswap = True args.disable_load = True # Control parameter and exit if it is not OK @@ -338,28 +350,32 @@ Start the client browser (browser mode):\n\ # Export is only available in standalone or client mode (issue #614) export_tag = args.export_csv or \ - args.export_elasticsearch or \ - args.export_statsd or \ - args.export_influxdb or \ - args.export_cassandra or \ - args.export_opentsdb or \ - args.export_rabbitmq or \ - args.export_couchdb + args.export_elasticsearch or \ + args.export_statsd or \ + args.export_influxdb or \ + args.export_cassandra or \ + args.export_opentsdb or \ + args.export_rabbitmq or \ + args.export_couchdb if not (self.is_standalone() or self.is_client()) and export_tag: - logger.critical("Export is only available in standalone or client mode") + logger.critical( + "Export is only available in standalone or client mode") sys.exit(2) # Filter is only available in standalone mode if args.process_filter is not None and not self.is_standalone(): - logger.critical("Process filter is only available in standalone mode") + logger.critical( + "Process filter is only available in standalone mode") sys.exit(2) # Check graph output path if args.export_graph and args.path_graph is not None: if not os.access(args.path_graph, os.W_OK): - logger.critical("Graphs output path {0} do not exist or is not writable".format(args.path_graph)) + logger.critical( + "Graphs output path {0} do not exist or is not writable".format(args.path_graph)) sys.exit(2) - logger.debug("Graphs output path is set to {0}".format(args.path_graph)) + logger.debug( + "Graphs output path is set to {0}".format(args.path_graph)) # For export graph, history is mandatory if args.export_graph and args.disable_history: diff --git a/glances/outdated.py b/glances/outdated.py index 5aa00bf4..fb2dc692 100644 --- a/glances/outdated.py +++ b/glances/outdated.py @@ -32,7 +32,7 @@ except ImportError: else: outdated_tag = True -from glances import __version__, __appname__ +from glances import __version__ from glances.globals import BSD, LINUX, OSX, WINDOWS from glances.logger import logger @@ -155,12 +155,11 @@ class Outdated(object): if LINUX or BSD: return os.path.join(os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), - __appname__) + 'glances') elif OSX: - return os.path.join(os.path.expanduser('~/Library/Application Support/'), - __appname__) + return os.path.expanduser('~/Library/Application Support/glances') elif WINDOWS: - return os.path.join(os.environ.get('APPDATA'), __appname__) + return os.path.join(os.environ.get('APPDATA'), 'glances') def _update_pypi_version(self): """Get the latest Pypi version (as a string) via the Restful JSON API""" diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index 82a68b47..d5ee9e44 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -24,8 +24,10 @@ import os import sys import tempfile from io import open +import webbrowser from glances.timer import Timer +from glances.globals import WINDOWS from glances.logger import logger try: @@ -39,7 +41,10 @@ class GlancesBottle(object): """This class manages the Bottle Web server.""" - def __init__(self, args=None): + def __init__(self, config=None, args=None): + # Init config + self.config = config + # Init args self.args = args @@ -89,6 +94,8 @@ class GlancesBottle(object): self._app.route('/', method=["GET"], callback=self._index) # REST API + self._app.route('/api/2/config', method="GET", callback=self._api_config) + self._app.route('/api/2/config/', method="GET", callback=self._api_config_item) self._app.route('/api/2/args', method="GET", callback=self._api_args) self._app.route('/api/2/args/', method="GET", callback=self._api_args_item) self._app.route('/api/2/help', method="GET", callback=self._api_help) @@ -117,9 +124,19 @@ class GlancesBottle(object): self.plugins_list = self.stats.getAllPlugins() # Bind the Bottle TCP address/port - bindmsg = 'Glances web server started on http://{}:{}/'.format(self.args.bind_address, self.args.port) + bindurl = 'http://{}:{}/'.format(self.args.bind_address, + self.args.port) + bindmsg = 'Glances web server started on {}'.format(bindurl) logger.info(bindmsg) print(bindmsg) + if self.args.open_web_browser: + # Implementation of the issue #946 + # Try to open the Glances Web UI in the default Web browser if: + # 1) --open-web-browser option is used + # 2) Glances standalone mode is running on Windows OS + webbrowser.open(bindurl, + new=2, + autoraise=1) self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug) def end(self): @@ -407,6 +424,43 @@ class GlancesBottle(object): """ return self._api_itemvalue(plugin, item, value) + def _api_config(self): + """Glances API RESTFul implementation. + + Return the JSON representation of the Glances configuration file + HTTP/200 if OK + HTTP/404 if others error + """ + response.content_type = 'application/json' + + try: + # Get the JSON value of the config' dict + args_json = json.dumps(self.config.as_dict()) + except Exception as e: + abort(404, "Cannot get config (%s)" % str(e)) + return args_json + + def _api_config_item(self, item): + """Glances API RESTFul implementation. + + Return the JSON representation of the Glances configuration item + HTTP/200 if OK + HTTP/400 if item is not found + HTTP/404 if others error + """ + response.content_type = 'application/json' + + config_dict = self.config.as_dict() + if item not in config_dict: + abort(400, "Unknown configuration item %s" % item) + + try: + # Get the JSON value of the config' dict + args_json = json.dumps(config_dict[item]) + except Exception as e: + abort(404, "Cannot get config item (%s)" % str(e)) + return args_json + def _api_args(self): """Glances API RESTFul implementation. @@ -436,7 +490,7 @@ class GlancesBottle(object): response.content_type = 'application/json' if item not in self.args: - abort(400, "Unknown item %s" % item) + abort(400, "Unknown argument item %s" % item) try: # Get the JSON value of the args' dict diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index b22e9d33..f752e0b2 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -51,6 +51,45 @@ class _GlancesCurses(object): Note: It is a private class, use GlancesCursesClient or GlancesCursesBrowser. """ + _hotkeys = { + '0': {'switch': 'disable_irix'}, + '1': {'switch': 'percpu'}, + '2': {'switch': 'disable_left_sidebar'}, + '3': {'switch': 'disable_quicklook'}, + '4': {'switch': 'full_quicklook'}, + '5': {'switch': 'disable_top'}, + '/': {'switch': 'process_short_name'}, + 'd': {'switch': 'disable_diskio'}, + 'A': {'switch': 'disable_amps'}, + 'b': {'switch': 'byte'}, + 'B': {'switch': 'diskio_iops'}, + 'D': {'switch': 'disable_docker'}, + 'e': {'switch': 'enable_process_extended'}, + 'F': {'switch': 'fs_free_space'}, + 'h': {'switch': 'help_tag'}, + 'I': {'switch': 'disable_ip'}, + 'l': {'switch': 'disable_alert'}, + 'M': {'switch': 'reset_minmax_tag'}, + 'n': {'switch': 'disable_network'}, + 'P': {'switch': 'disable_ports'}, + 'Q': {'switch': 'enable_irq'}, + 'r': {'switch': 'reset_history_tag'}, + 'R': {'switch': 'disable_raid'}, + 's': {'switch': 'disable_sensors'}, + 'T': {'switch': 'network_sum'}, + 'U': {'switch': 'network_cumul'}, + 'W': {'switch': 'disable_wifi'}, + # Processes sort hotkeys + 'a': {'auto_sort': True, 'sort_key': 'cpu_percent'}, + 'c': {'auto_sort': False, 'sort_key': 'cpu_percent'}, + 'i': {'auto_sort': False, 'sort_key': 'io_counters'}, + 'm': {'auto_sort': False, 'sort_key': 'memory_percent'}, + 'p': {'auto_sort': False, 'sort_key': 'name'}, + 't': {'auto_sort': False, 'sort_key': 'cpu_times'}, + 'u': {'auto_sort': False, 'sort_key': 'username'}, + 'c': {'auto_sort': False, 'sort_key': 'cpu_percent'} + } + def __init__(self, config=None, args=None): # Init self.config = config @@ -73,7 +112,6 @@ class _GlancesCurses(object): # Load the 'outputs' section of the configuration file # - Init the theme (default is black) self.theme = {'name': 'black'} - self.load_config(self.config) # Init cursor self._init_cursor() @@ -286,7 +324,23 @@ class _GlancesCurses(object): # Catch the pressed key self.pressedkey = self.get_key(self.term_window) - # Actions... + # Actions (available in the global hotkey dict)... + for hotkey in self._hotkeys: + if self.pressedkey == ord(hotkey) and 'switch' in self._hotkeys[hotkey]: + setattr(self.args, + self._hotkeys[hotkey]['switch'], + not getattr(self.args, + self._hotkeys[hotkey]['switch'])) + if self.pressedkey == ord(hotkey) and 'auto_sort' in self._hotkeys[hotkey]: + setattr(glances_processes, + 'auto_sort', + self._hotkeys[hotkey]['auto_sort']) + if self.pressedkey == ord(hotkey) and 'sort_key' in self._hotkeys[hotkey]: + setattr(glances_processes, + 'sort_key', + self._hotkeys[hotkey]['sort_key']) + + # Other actions... if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'): # 'ESC'|'q' > Quit if return_to_browser: @@ -295,152 +349,29 @@ class _GlancesCurses(object): self.end() logger.info("Stop Glances") sys.exit(0) - elif self.pressedkey == 10: + elif self.pressedkey == ord('\n'): # 'ENTER' > Edit the process filter self.edit_filter = not self.edit_filter - elif self.pressedkey == ord('0'): - # '0' > Switch between IRIX and Solaris mode - self.args.disable_irix = not self.args.disable_irix - elif self.pressedkey == ord('1'): - # '1' > Switch between CPU and PerCPU information - self.args.percpu = not self.args.percpu - elif self.pressedkey == ord('2'): - # '2' > Enable/disable left sidebar - self.args.disable_left_sidebar = not self.args.disable_left_sidebar - elif self.pressedkey == ord('3'): - # '3' > Enable/disable quicklook - self.args.disable_quicklook = not self.args.disable_quicklook elif self.pressedkey == ord('4'): - # '4' > Enable/disable all but quick look and load - self.args.full_quicklook = not self.args.full_quicklook if self.args.full_quicklook: - self.args.disable_quicklook = False - self.args.disable_cpu = True - self.args.disable_mem = True - self.args.disable_swap = True + self.enable_fullquicklook() else: - self.args.disable_quicklook = False - self.args.disable_cpu = False - self.args.disable_mem = False - self.args.disable_swap = False + self.disable_fullquicklook() elif self.pressedkey == ord('5'): - # '5' > Enable/disable top menu - logger.info(self.args.disable_top) - self.args.disable_top = not self.args.disable_top if self.args.disable_top: - self.args.disable_quicklook = True - self.args.disable_cpu = True - self.args.disable_mem = True - self.args.disable_swap = True - self.args.disable_load = True + self.disable_top() else: - self.args.disable_quicklook = False - self.args.disable_cpu = False - self.args.disable_mem = False - self.args.disable_swap = False - self.args.disable_load = False - elif self.pressedkey == ord('/'): - # '/' > Switch between short/long name for processes - self.args.process_short_name = not self.args.process_short_name - elif self.pressedkey == ord('a'): - # 'a' > Sort processes automatically and reset to 'cpu_percent' - glances_processes.auto_sort = True - glances_processes.sort_key = 'cpu_percent' - elif self.pressedkey == ord('A'): - # 'A' > enable/disable AMP module - self.args.disable_amps = not self.args.disable_amps - elif self.pressedkey == ord('b'): - # 'b' > Switch between bit/s and Byte/s for network IO - self.args.byte = not self.args.byte - elif self.pressedkey == ord('B'): - # 'B' > Switch between bit/s and IO/s for Disk IO - self.args.diskio_iops = not self.args.diskio_iops - elif self.pressedkey == ord('c'): - # 'c' > Sort processes by CPU usage - glances_processes.auto_sort = False - glances_processes.sort_key = 'cpu_percent' - elif self.pressedkey == ord('d'): - # 'd' > Show/hide disk I/O stats - self.args.disable_diskio = not self.args.disable_diskio - elif self.pressedkey == ord('D'): - # 'D' > Show/hide Docker stats - self.args.disable_docker = not self.args.disable_docker - elif self.pressedkey == ord('e'): - # 'e' > Enable/Disable extended stats for top process - self.args.enable_process_extended = not self.args.enable_process_extended - if not self.args.enable_process_extended: - glances_processes.disable_extended() - else: - glances_processes.enable_extended() + self.enable_top() elif self.pressedkey == ord('E'): # 'E' > Erase the process filter - logger.info("Erase process filter") glances_processes.process_filter = None - elif self.pressedkey == ord('F'): - # 'F' > Switch between FS available and free space - self.args.fs_free_space = not self.args.fs_free_space elif self.pressedkey == ord('f'): # 'f' > Show/hide fs / folder stats self.args.disable_fs = not self.args.disable_fs - self.args.disable_folder = not self.args.disable_folder + self.args.disable_folders = not self.args.disable_folders elif self.pressedkey == ord('g'): - # 'g' > Export graphs to file + # 'g' > Generate graph from history self.graph_tag = not self.graph_tag - elif self.pressedkey == ord('h'): - # 'h' > Show/hide help - self.args.help_tag = not self.args.help_tag - elif self.pressedkey == ord('i'): - # 'i' > Sort processes by IO rate (not available on OS X) - glances_processes.auto_sort = False - glances_processes.sort_key = 'io_counters' - elif self.pressedkey == ord('I'): - # 'I' > Show/hide IP module - self.args.disable_ip = not self.args.disable_ip - elif self.pressedkey == ord('l'): - # 'l' > Show/hide log messages - self.args.disable_log = not self.args.disable_log - elif self.pressedkey == ord('m'): - # 'm' > Sort processes by MEM usage - glances_processes.auto_sort = False - glances_processes.sort_key = 'memory_percent' - elif self.pressedkey == ord('M'): - # 'M' > Reset processes summary min/max - self.args.reset_minmax_tag = not self.args.reset_minmax_tag - elif self.pressedkey == ord('n'): - # 'n' > Show/hide network stats - self.args.disable_network = not self.args.disable_network - elif self.pressedkey == ord('p'): - # 'p' > Sort processes by name - glances_processes.auto_sort = False - glances_processes.sort_key = 'name' - elif self.pressedkey == ord('P'): - # 'P' > Disable ports scan plugins - self.args.disable_ports = not self.args.disable_ports - elif self.pressedkey == ord('Q'): - self.args.disable_irq = not self.args.disable_irq - elif self.pressedkey == ord('r'): - # 'r' > Reset history - self.reset_history_tag = not self.reset_history_tag - elif self.pressedkey == ord('R'): - # 'R' > Hide RAID plugins - self.args.disable_raid = not self.args.disable_raid - elif self.pressedkey == ord('s'): - # 's' > Show/hide sensors stats (Linux-only) - self.args.disable_sensors = not self.args.disable_sensors - elif self.pressedkey == ord('t'): - # 't' > Sort processes by TIME usage - glances_processes.auto_sort = False - glances_processes.sort_key = 'cpu_times' - elif self.pressedkey == ord('T'): - # 'T' > View network traffic as sum Rx+Tx - self.args.network_sum = not self.args.network_sum - elif self.pressedkey == ord('u'): - # 'u' > Sort processes by USER - glances_processes.auto_sort = False - glances_processes.sort_key = 'username' - elif self.pressedkey == ord('U'): - # 'U' > View cumulative network I/O (instead of bitrate) - self.args.network_cumul = not self.args.network_cumul elif self.pressedkey == ord('w'): # 'w' > Delete finished warning logs glances_logs.clean() @@ -448,17 +379,52 @@ class _GlancesCurses(object): # 'x' > Delete finished warning and critical logs glances_logs.clean(critical=True) elif self.pressedkey == ord('z'): - # 'z' > Enable/Disable processes stats (count + list + AMPs) - # Enable/Disable display + # 'z' > Enable or disable processes self.args.disable_process = not self.args.disable_process - # Enable/Disable update if self.args.disable_process: glances_processes.disable() else: glances_processes.enable() + + # Change the curse interface according to the current configuration + if not self.args.enable_process_extended: + glances_processes.disable_extended() + else: + glances_processes.enable_extended() + # Return the key code return self.pressedkey + def disable_top(self): + """Disable the top panel""" + self.args.disable_quicklook = True + self.args.disable_cpu = True + self.args.disable_mem = True + self.args.disable_memswap = True + self.args.disable_load = True + + def enable_top(self): + """Enable the top panel""" + self.args.disable_quicklook = False + self.args.disable_cpu = False + self.args.disable_mem = False + self.args.disable_memswap = False + self.args.disable_load = False + + def disable_fullquicklook(self): + """Disable the full quicklook mode""" + self.args.disable_quicklook = False + self.args.disable_cpu = False + self.args.disable_mem = False + self.args.disable_memswap = False + + def enable_fullquicklook(self): + """Disable the full quicklook mode""" + self.args.disable_quicklook = False + self.args.disable_cpu = True + self.args.disable_mem = True + self.args.disable_memswap = True + def end(self): """Shutdown the curses window.""" if hasattr(curses, 'echo'): @@ -495,6 +461,51 @@ class _GlancesCurses(object): """New column in the curses interface.""" self.column = self.next_column + def __get_stat_display(self, stats, plugin_max_width): + ret = {} + ret["system"] = stats.get_plugin( + 'system').get_stats_display(args=self.args) + ret["uptime"] = stats.get_plugin('uptime').get_stats_display() + if self.args.percpu: + ret["cpu"] = stats.get_plugin('percpu').get_stats_display(args=self.args) + else: + ret["cpu"] = stats.get_plugin('cpu').get_stats_display(args=self.args) + ret["load"] = stats.get_plugin('load').get_stats_display(args=self.args) + ret["mem"] = stats.get_plugin('mem').get_stats_display(args=self.args) + ret["memswap"] = stats.get_plugin('memswap').get_stats_display(args=self.args) + ret["network"] = stats.get_plugin('network').get_stats_display( + args=self.args, max_width=plugin_max_width) + ret["wifi"] = stats.get_plugin('wifi').get_stats_display( + args=self.args, max_width=plugin_max_width) + ret["irq"] = stats.get_plugin('irq').get_stats_display( + args=self.args, max_width=plugin_max_width) + try: + ret["ip"] = stats.get_plugin('ip').get_stats_display(args=self.args) + except AttributeError: + ret["ip"] = None + ret["diskio"] = stats.get_plugin( + 'diskio').get_stats_display(args=self.args) + ret["fs"] = stats.get_plugin('fs').get_stats_display( + args=self.args, max_width=plugin_max_width) + ret["folders"] = stats.get_plugin('folders').get_stats_display( + args=self.args, max_width=plugin_max_width) + ret["raid"] = stats.get_plugin('raid').get_stats_display( + args=self.args) + ret["sensors"] = stats.get_plugin( + 'sensors').get_stats_display(args=self.args) + ret["ports"] = stats.get_plugin( + 'ports').get_stats_display(args=self.args) + ret["now"] = stats.get_plugin('now').get_stats_display() + ret["docker"] = stats.get_plugin('docker').get_stats_display( + args=self.args) + ret["processcount"] = stats.get_plugin( + 'processcount').get_stats_display(args=self.args) + ret["amps"] = stats.get_plugin( + 'amps').get_stats_display(args=self.args) + ret["alert"] = stats.get_plugin( + 'alert').get_stats_display(args=self.args) + return ret + def display(self, stats, cs_status=None): """Display stats on the screen. @@ -528,50 +539,12 @@ class _GlancesCurses(object): # Update the client server status self.args.cs_status = cs_status - stats_system = stats.get_plugin( - 'system').get_stats_display(args=self.args) - stats_uptime = stats.get_plugin('uptime').get_stats_display() - if self.args.percpu: - stats_cpu = stats.get_plugin('percpu').get_stats_display(args=self.args) - else: - stats_cpu = stats.get_plugin('cpu').get_stats_display(args=self.args) - stats_load = stats.get_plugin('load').get_stats_display(args=self.args) - stats_mem = stats.get_plugin('mem').get_stats_display(args=self.args) - stats_memswap = stats.get_plugin('memswap').get_stats_display(args=self.args) - stats_network = stats.get_plugin('network').get_stats_display( - args=self.args, max_width=plugin_max_width) - stats_irq = stats.get_plugin('irq').get_stats_display( - args=self.args, max_width=plugin_max_width) - try: - stats_ip = stats.get_plugin('ip').get_stats_display(args=self.args) - except AttributeError: - stats_ip = None - stats_diskio = stats.get_plugin( - 'diskio').get_stats_display(args=self.args) - stats_fs = stats.get_plugin('fs').get_stats_display( - args=self.args, max_width=plugin_max_width) - stats_folders = stats.get_plugin('folders').get_stats_display( - args=self.args, max_width=plugin_max_width) - stats_raid = stats.get_plugin('raid').get_stats_display( - args=self.args) - stats_sensors = stats.get_plugin( - 'sensors').get_stats_display(args=self.args) - stats_ports = stats.get_plugin( - 'ports').get_stats_display(args=self.args) - stats_now = stats.get_plugin('now').get_stats_display() - stats_docker = stats.get_plugin('docker').get_stats_display( - args=self.args) - stats_processcount = stats.get_plugin( - 'processcount').get_stats_display(args=self.args) - stats_amps = stats.get_plugin( - 'amps').get_stats_display(args=self.args) - stats_alert = stats.get_plugin( - 'alert').get_stats_display(args=self.args) + __stat_display = self.__get_stat_display(stats, plugin_max_width) # Adapt number of processes to the available space max_processes_displayed = screen_y - 11 - \ - self.get_stats_display_height(stats_alert) - \ - self.get_stats_display_height(stats_docker) + self.get_stats_display_height(__stat_display["alert"]) - \ + self.get_stats_display_height(__stat_display["docker"]) try: if self.args.enable_process_extended and not self.args.process_tree: max_processes_displayed -= 4 @@ -584,7 +557,7 @@ class _GlancesCurses(object): logger.debug("Set number of displayed processes to {}".format(max_processes_displayed)) glances_processes.max_processes = max_processes_displayed - stats_processlist = stats.get_plugin( + __stat_display["processlist"] = stats.get_plugin( 'processlist').get_stats_display(args=self.args) # Display the stats on the curses interface @@ -604,16 +577,19 @@ class _GlancesCurses(object): # Space between column self.space_between_column = 0 self.new_line() - l_uptime = self.get_stats_display_width( - stats_system) + self.space_between_column + self.get_stats_display_width(stats_ip) + 3 + self.get_stats_display_width(stats_uptime) + l_uptime = self.get_stats_display_width(__stat_display["system"]) \ + + self.space_between_column \ + + self.get_stats_display_width(__stat_display["ip"]) + 3 \ + + self.get_stats_display_width(__stat_display["uptime"]) self.display_plugin( - stats_system, display_optional=(screen_x >= l_uptime)) + __stat_display["system"], + display_optional=(screen_x >= l_uptime)) self.new_column() - self.display_plugin(stats_ip) + self.display_plugin(__stat_display["ip"]) # Space between column self.space_between_column = 3 self.new_column() - self.display_plugin(stats_uptime) + self.display_plugin(__stat_display["uptime"]) # ======================================================== # Display second line (+CPU|PERCPU+LOAD+MEM+SWAP) @@ -622,36 +598,36 @@ class _GlancesCurses(object): self.new_line() # Init quicklook - stats_quicklook = {'msgdict': []} + __stat_display["quicklook"] = {'msgdict': []} quicklook_width = 0 # Get stats for CPU, MEM, SWAP and LOAD (if needed) if self.args.disable_cpu: cpu_width = 0 else: - cpu_width = self.get_stats_display_width(stats_cpu) + cpu_width = self.get_stats_display_width(__stat_display["cpu"]) if self.args.disable_mem: mem_width = 0 else: - mem_width = self.get_stats_display_width(stats_mem) - if self.args.disable_swap: + mem_width = self.get_stats_display_width(__stat_display["mem"]) + if self.args.disable_memswap: swap_width = 0 else: - swap_width = self.get_stats_display_width(stats_memswap) + swap_width = self.get_stats_display_width(__stat_display["memswap"]) if self.args.disable_load: load_width = 0 else: - load_width = self.get_stats_display_width(stats_load) + load_width = self.get_stats_display_width(__stat_display["load"]) # Size of plugins but quicklook stats_width = cpu_width + mem_width + swap_width + load_width # Number of plugin but quicklook stats_number = ( - int(not self.args.disable_cpu and stats_cpu['msgdict'] != []) + - int(not self.args.disable_mem and stats_mem['msgdict'] != []) + - int(not self.args.disable_swap and stats_memswap['msgdict'] != []) + - int(not self.args.disable_load and stats_load['msgdict'] != [])) + int(not self.args.disable_cpu and __stat_display["cpu"]['msgdict'] != []) + + int(not self.args.disable_mem and __stat_display["mem"]['msgdict'] != []) + + int(not self.args.disable_memswap and __stat_display["memswap"]['msgdict'] != []) + + int(not self.args.disable_load and __stat_display["load"]['msgdict'] != [])) if not self.args.disable_quicklook: # Quick look is in the place ! @@ -660,15 +636,15 @@ class _GlancesCurses(object): else: quicklook_width = min(screen_x - (stats_width + 8 + stats_number * self.space_between_column), 79) try: - stats_quicklook = stats.get_plugin( + __stat_display["quicklook"] = stats.get_plugin( 'quicklook').get_stats_display(max_width=quicklook_width, args=self.args) except AttributeError as e: logger.debug("Quicklook plugin not available (%s)" % e) else: - quicklook_width = self.get_stats_display_width(stats_quicklook) + quicklook_width = self.get_stats_display_width(__stat_display["quicklook"]) stats_width += quicklook_width + 1 self.space_between_column = 1 - self.display_plugin(stats_quicklook) + self.display_plugin(__stat_display["quicklook"]) self.new_column() # Compute spaces between plugins @@ -683,7 +659,7 @@ class _GlancesCurses(object): if self.args.disable_mem: mem_width = 0 else: - mem_width = self.get_stats_display_width(stats_mem, without_option=True) + mem_width = self.get_stats_display_width(__stat_display["mem"], without_option=True) stats_width = quicklook_width + 1 + cpu_width + mem_width + swap_width + load_width self.space_between_column = max(1, int((screen_x - stats_width) / (stats_number - 1))) # No space again ? Remove optionnal CPU stats @@ -692,20 +668,20 @@ class _GlancesCurses(object): if self.args.disable_cpu: cpu_width = 0 else: - cpu_width = self.get_stats_display_width(stats_cpu, without_option=True) + cpu_width = self.get_stats_display_width(__stat_display["cpu"], without_option=True) stats_width = quicklook_width + 1 + cpu_width + mem_width + swap_width + load_width self.space_between_column = max(1, int((screen_x - stats_width) / (stats_number - 1))) else: self.space_between_column = 0 # Display CPU, MEM, SWAP and LOAD - self.display_plugin(stats_cpu, display_optional=display_optional_cpu) + self.display_plugin(__stat_display["cpu"], display_optional=display_optional_cpu) self.new_column() - self.display_plugin(stats_mem, display_optional=display_optional_mem) + self.display_plugin(__stat_display["mem"], display_optional=display_optional_mem) self.new_column() - self.display_plugin(stats_memswap) + self.display_plugin(__stat_display["memswap"]) self.new_column() - self.display_plugin(stats_load) + self.display_plugin(__stat_display["load"]) # Space between column self.space_between_column = 3 @@ -718,13 +694,24 @@ class _GlancesCurses(object): # ================================================================== self.init_column() if not (self.args.disable_network and + self.args.disable_wifi and self.args.disable_ports and self.args.disable_diskio and self.args.disable_fs and - self.args.disable_folder and + self.args.enable_irq and + self.args.disable_folders and self.args.disable_raid and self.args.disable_sensors) and not self.args.disable_left_sidebar: - for s in (stats_network, stats_ports, stats_diskio, stats_fs, stats_irq, stats_folders, stats_raid, stats_sensors, stats_now): + for s in (__stat_display["network"], + __stat_display["wifi"], + __stat_display["ports"], + __stat_display["diskio"], + __stat_display["fs"], + __stat_display["irq"], + __stat_display["folders"], + __stat_display["raid"], + __stat_display["sensors"], + __stat_display["now"]): self.new_line() self.display_plugin(s) @@ -740,18 +727,18 @@ class _GlancesCurses(object): # DOCKER+PROCESS_COUNT+AMPS+PROCESS_LIST+ALERT self.new_column() self.new_line() - self.display_plugin(stats_docker) + self.display_plugin(__stat_display["docker"]) self.new_line() - self.display_plugin(stats_processcount) + self.display_plugin(__stat_display["processcount"]) self.new_line() - self.display_plugin(stats_amps) + self.display_plugin(__stat_display["amps"]) self.new_line() - self.display_plugin(stats_processlist, + self.display_plugin(__stat_display["processlist"], display_optional=(screen_x > 102), display_additional=(not OSX), - max_y=(screen_y - self.get_stats_display_height(stats_alert) - 2)) + max_y=(screen_y - self.get_stats_display_height(__stat_display["alert"]) - 2)) self.new_line() - self.display_plugin(stats_alert) + self.display_plugin(__stat_display["alert"]) # History option # Generate history graph diff --git a/glances/outputs/static/css/bootstrap.min.css b/glances/outputs/static/css/bootstrap.css similarity index 100% rename from glances/outputs/static/css/bootstrap.min.css rename to glances/outputs/static/css/bootstrap.css diff --git a/glances/outputs/static/gulpfile.js b/glances/outputs/static/gulpfile.js index 8fd530cb..615a9c8c 100644 --- a/glances/outputs/static/gulpfile.js +++ b/glances/outputs/static/gulpfile.js @@ -4,6 +4,7 @@ var mainBowerFiles = require('main-bower-files'); var ngAnnotate = require('gulp-ng-annotate'); var templateCache = require('gulp-angular-templatecache'); var del = require('del'); +var rename = require('gulp-rename'); gulp.task('clean', function() { del('./public/*') @@ -14,6 +15,7 @@ gulp.task('copy', function() { .pipe(gulp.dest('./public')); gulp.src('./css/*.css') + .pipe(rename({suffix: '.min'})) .pipe(gulp.dest('./public/css')); gulp.src('./images/*.png') @@ -26,6 +28,7 @@ gulp.task('copy', function() { gulp.task('bower', function() { return gulp.src(mainBowerFiles()) .pipe(concat('vendor.js')) + .pipe(rename({suffix: '.min'})) .pipe(gulp.dest('./public/js')) }); @@ -33,12 +36,14 @@ gulp.task('build-js', function() { return gulp.src('./js/**/*.js') .pipe(ngAnnotate()) .pipe(concat('main.js')) + .pipe(rename({suffix: '.min'})) .pipe(gulp.dest('./public/js')) }); gulp.task('template', function () { return gulp.src('./html/plugins/*.html') .pipe(templateCache('templates.js', {'root': 'plugins/', 'module': 'glancesApp'})) + .pipe(rename({suffix: '.min'})) .pipe(gulp.dest('./public/js')); }); diff --git a/glances/outputs/static/html/index.html b/glances/outputs/static/html/index.html index d6c8e55e..eec4fb70 100644 --- a/glances/outputs/static/html/index.html +++ b/glances/outputs/static/html/index.html @@ -8,13 +8,13 @@ - + - + - - - + + + diff --git a/glances/outputs/static/html/plugins/wifi.html b/glances/outputs/static/html/plugins/wifi.html new file mode 100644 index 00000000..6c46c938 --- /dev/null +++ b/glances/outputs/static/html/plugins/wifi.html @@ -0,0 +1,10 @@ +
+
WIFI
+
+
dBm
+
+
+
{{ hotspot.ssid|limitTo:20 }} {{ hotspot.encryption_type }}
+
+
{{ hotspot.signal }}
+
diff --git a/glances/outputs/static/html/stats.html b/glances/outputs/static/html/stats.html index 2974822e..e4525806 100644 --- a/glances/outputs/static/html/stats.html +++ b/glances/outputs/static/html/stats.html @@ -35,7 +35,7 @@ -
+
@@ -46,20 +46,21 @@
-
-
+
+
diff --git a/glances/outputs/static/js/stats_controller.js b/glances/outputs/static/js/controllers.js similarity index 94% rename from glances/outputs/static/js/stats_controller.js rename to glances/outputs/static/js/controllers.js index faee06f1..691e31cd 100644 --- a/glances/outputs/static/js/stats_controller.js +++ b/glances/outputs/static/js/controllers.js @@ -43,6 +43,7 @@ glancesApp.controller('statsController', function ($scope, $rootScope, $interval $scope.statsSystem = GlancesStats.getPlugin('system'); $scope.statsUptime = GlancesStats.getPlugin('uptime'); $scope.statsPorts = GlancesStats.getPlugin('ports'); + $scope.statsWifi = GlancesStats.getPlugin('wifi'); $rootScope.title = $scope.statsSystem.hostname + ' - Glances'; @@ -112,7 +113,7 @@ glancesApp.controller('statsController', function ($scope, $rootScope, $interval break; case $event.shiftKey && $event.keyCode == keycodes.Q: // Q => Show/hide IRQ - $scope.arguments.disable_irq = !$scope.arguments.disable_irq; + $scope.arguments.enable_irq = !$scope.arguments.enable_irq; break; case !$event.shiftKey && $event.keyCode == keycodes.f: // f => Show/hide filesystem stats @@ -152,7 +153,7 @@ glancesApp.controller('statsController', function ($scope, $rootScope, $interval break; case !$event.shiftKey && $event.keyCode == keycodes.l: // l => Show/hide alert logs - $scope.arguments.disable_log = !$scope.arguments.disable_log; + $scope.arguments.disable_alert = !$scope.arguments.disable_alert; break; case $event.shiftKey && $event.keyCode == keycodes.ONE: // 1 => Global CPU or per-CPU stats @@ -182,7 +183,7 @@ glancesApp.controller('statsController', function ($scope, $rootScope, $interval $scope.arguments.disable_quicklook = !$scope.arguments.disable_quicklook; $scope.arguments.disable_cpu = !$scope.arguments.disable_cpu; $scope.arguments.disable_mem = !$scope.arguments.disable_mem; - $scope.arguments.disable_swap = !$scope.arguments.disable_swap; + $scope.arguments.disable_memswap = !$scope.arguments.disable_memswap; $scope.arguments.disable_load = !$scope.arguments.disable_load; break; case $event.shiftKey && $event.keyCode == keycodes.i: @@ -193,6 +194,10 @@ glancesApp.controller('statsController', function ($scope, $rootScope, $interval // I => Enable/disable ports module $scope.arguments.disable_ports = !$scope.arguments.disable_ports; break; + case $event.shiftKey && $event.keyCode == keycodes.w: + // 'W' > Enable/Disable Wifi plugin + $scope.arguments.disable_wifi = !$scope.arguments.disable_wifi; + break; } }; }); diff --git a/glances/outputs/static/js/services/core/glances_stats.js b/glances/outputs/static/js/services/core/stats.js similarity index 97% rename from glances/outputs/static/js/services/core/glances_stats.js rename to glances/outputs/static/js/services/core/stats.js index 4d049849..117b8762 100644 --- a/glances/outputs/static/js/services/core/glances_stats.js +++ b/glances/outputs/static/js/services/core/stats.js @@ -23,7 +23,8 @@ glancesApp.service('GlancesStats', function($http, $injector, $q, GlancesPlugin) 'sensors': 'GlancesPluginSensors', 'system': 'GlancesPluginSystem', 'uptime': 'GlancesPluginUptime', - 'ports': 'GlancesPluginPorts' + 'ports': 'GlancesPluginPorts', + 'wifi': 'GlancesPluginWifi' }; this.getData = function() { diff --git a/glances/outputs/static/js/services/plugins/glances_alert.js b/glances/outputs/static/js/services/plugins/alert.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_alert.js rename to glances/outputs/static/js/services/plugins/alert.js diff --git a/glances/outputs/static/js/services/plugins/glances_amps.js b/glances/outputs/static/js/services/plugins/amps.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_amps.js rename to glances/outputs/static/js/services/plugins/amps.js diff --git a/glances/outputs/static/js/services/plugins/glances_cpu.js b/glances/outputs/static/js/services/plugins/cpu.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_cpu.js rename to glances/outputs/static/js/services/plugins/cpu.js diff --git a/glances/outputs/static/js/services/plugins/glances_diskio.js b/glances/outputs/static/js/services/plugins/diskio.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_diskio.js rename to glances/outputs/static/js/services/plugins/diskio.js diff --git a/glances/outputs/static/js/services/plugins/glances_docker.js b/glances/outputs/static/js/services/plugins/docker.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_docker.js rename to glances/outputs/static/js/services/plugins/docker.js diff --git a/glances/outputs/static/js/services/plugins/glances_folders.js b/glances/outputs/static/js/services/plugins/folders.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_folders.js rename to glances/outputs/static/js/services/plugins/folders.js diff --git a/glances/outputs/static/js/services/plugins/glances_fs.js b/glances/outputs/static/js/services/plugins/fs.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_fs.js rename to glances/outputs/static/js/services/plugins/fs.js diff --git a/glances/outputs/static/js/services/plugins/glances_ip.js b/glances/outputs/static/js/services/plugins/ip.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_ip.js rename to glances/outputs/static/js/services/plugins/ip.js diff --git a/glances/outputs/static/js/services/plugins/glances_irq.js b/glances/outputs/static/js/services/plugins/irq.js similarity index 91% rename from glances/outputs/static/js/services/plugins/glances_irq.js rename to glances/outputs/static/js/services/plugins/irq.js index 0d22b42d..8e71c8ed 100644 --- a/glances/outputs/static/js/services/plugins/glances_irq.js +++ b/glances/outputs/static/js/services/plugins/irq.js @@ -12,7 +12,7 @@ glancesApp.service('GlancesPluginIrq', function() { var irq = { 'irq_line': IrqData['irq_line'], - 'irq_rate': IrqData['irq_rate'] + 'irq_rate': IrqData['irq_rate'] }; this.irqs.push(irq); diff --git a/glances/outputs/static/js/services/plugins/glances_load.js b/glances/outputs/static/js/services/plugins/load.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_load.js rename to glances/outputs/static/js/services/plugins/load.js diff --git a/glances/outputs/static/js/services/plugins/glances_mem.js b/glances/outputs/static/js/services/plugins/mem.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_mem.js rename to glances/outputs/static/js/services/plugins/mem.js diff --git a/glances/outputs/static/js/services/plugins/glances_memswap.js b/glances/outputs/static/js/services/plugins/memswap.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_memswap.js rename to glances/outputs/static/js/services/plugins/memswap.js diff --git a/glances/outputs/static/js/services/plugins/glances_network.js b/glances/outputs/static/js/services/plugins/network.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_network.js rename to glances/outputs/static/js/services/plugins/network.js diff --git a/glances/outputs/static/js/services/plugins/glances_percpu.js b/glances/outputs/static/js/services/plugins/percpu.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_percpu.js rename to glances/outputs/static/js/services/plugins/percpu.js diff --git a/glances/outputs/static/js/services/plugins/glances_plugin.js b/glances/outputs/static/js/services/plugins/plugin.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_plugin.js rename to glances/outputs/static/js/services/plugins/plugin.js diff --git a/glances/outputs/static/js/services/plugins/glances_ports.js b/glances/outputs/static/js/services/plugins/ports.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_ports.js rename to glances/outputs/static/js/services/plugins/ports.js diff --git a/glances/outputs/static/js/services/plugins/glances_processcount.js b/glances/outputs/static/js/services/plugins/processcount.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_processcount.js rename to glances/outputs/static/js/services/plugins/processcount.js diff --git a/glances/outputs/static/js/services/plugins/glances_processlist.js b/glances/outputs/static/js/services/plugins/processlist.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_processlist.js rename to glances/outputs/static/js/services/plugins/processlist.js diff --git a/glances/outputs/static/js/services/plugins/glances_quicklook.js b/glances/outputs/static/js/services/plugins/quicklook.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_quicklook.js rename to glances/outputs/static/js/services/plugins/quicklook.js diff --git a/glances/outputs/static/js/services/plugins/glances_raid.js b/glances/outputs/static/js/services/plugins/raid.js similarity index 93% rename from glances/outputs/static/js/services/plugins/glances_raid.js rename to glances/outputs/static/js/services/plugins/raid.js index 637009e7..900b2a5d 100644 --- a/glances/outputs/static/js/services/plugins/glances_raid.js +++ b/glances/outputs/static/js/services/plugins/raid.js @@ -3,7 +3,7 @@ glancesApp.service('GlancesPluginRaid', function () { this.disks = []; this.setData = function (data, views) { - this.disks = []; + var disks = []; data = data[_pluginName]; _.forIn(data, function(diskData, diskKey) { @@ -26,8 +26,10 @@ glancesApp.service('GlancesPluginRaid', function () { }); }); - this.disks.push(disk); - }, this); + disks.push(disk); + }); + + this.disks = disks; }; this.hasDisks = function() { diff --git a/glances/outputs/static/js/services/plugins/glances_sensors.js b/glances/outputs/static/js/services/plugins/sensors.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_sensors.js rename to glances/outputs/static/js/services/plugins/sensors.js diff --git a/glances/outputs/static/js/services/plugins/glances_system.js b/glances/outputs/static/js/services/plugins/system.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_system.js rename to glances/outputs/static/js/services/plugins/system.js diff --git a/glances/outputs/static/js/services/plugins/glances_uptime.js b/glances/outputs/static/js/services/plugins/uptime.js similarity index 100% rename from glances/outputs/static/js/services/plugins/glances_uptime.js rename to glances/outputs/static/js/services/plugins/uptime.js diff --git a/glances/outputs/static/js/services/plugins/wifi.js b/glances/outputs/static/js/services/plugins/wifi.js new file mode 100644 index 00000000..2defd931 --- /dev/null +++ b/glances/outputs/static/js/services/plugins/wifi.js @@ -0,0 +1,36 @@ +glancesApp.service('GlancesPluginWifi', function() { + var _pluginName = "wifi"; + var _view = {}; + this.hotspots = []; + + this.setData = function(data, views) { + data = data[_pluginName]; + _view = views[_pluginName]; + + this.hotspots = []; + for (var i = 0; i < data.length; i++) { + var hotspotData = data[i]; + + if (hotspotData['ssid'] === '') { + continue; + } + + var hotspot = { + 'ssid': hotspotData['ssid'], + 'encrypted': hotspotData['encrypted'], + 'signal': hotspotData['signal'], + 'encryption_type': hotspotData['encryption_type'], + }; + + this.hotspots.push(hotspot); + } + }; + + this.getDecoration = function(hotpost, field) { + if(_view[hotpost.ssid][field] == undefined) { + return; + } + + return _view[hotpost.ssid][field].decoration.toLowerCase(); + }; +}); diff --git a/glances/outputs/static/js/variables.js b/glances/outputs/static/js/variables.js index f6ebe80e..3d9bbd03 100644 --- a/glances/outputs/static/js/variables.js +++ b/glances/outputs/static/js/variables.js @@ -30,5 +30,5 @@ var keycodes = { 'r' : '82', 'q' : '81', 'A' : '65', - 'R' : '82', + 'Q' : '81' } diff --git a/glances/outputs/static/package.json b/glances/outputs/static/package.json index 2c4ce437..b5247938 100644 --- a/glances/outputs/static/package.json +++ b/glances/outputs/static/package.json @@ -8,6 +8,7 @@ "gulp-angular-templatecache": "^2.0.0", "gulp-concat": "^2.6.0", "gulp-ng-annotate": "^2.0.0", + "gulp-rename": "^1.2.2", "main-bower-files": "^2.13.1" }, "scripts": { diff --git a/glances/outputs/static/public/css/normalize.css b/glances/outputs/static/public/css/normalize.min.css similarity index 100% rename from glances/outputs/static/public/css/normalize.css rename to glances/outputs/static/public/css/normalize.min.css diff --git a/glances/outputs/static/public/css/style.css b/glances/outputs/static/public/css/style.min.css similarity index 97% rename from glances/outputs/static/public/css/style.css rename to glances/outputs/static/public/css/style.min.css index b54b310f..acc7d2cf 100644 --- a/glances/outputs/static/public/css/style.css +++ b/glances/outputs/static/public/css/style.min.css @@ -62,11 +62,15 @@ body { } .ok, .status, .process { color: #3E7B04; - font-weight: bold; + /*font-weight: bold;*/ } .ok_log { background-color: #3E7B04; color: white; + /*font-weight: bold;*/ +} +.max { + color: #3E7B04; font-weight: bold; } .careful { diff --git a/glances/outputs/static/public/index.html b/glances/outputs/static/public/index.html index d6c8e55e..eec4fb70 100644 Binary files a/glances/outputs/static/public/index.html and b/glances/outputs/static/public/index.html differ diff --git a/glances/outputs/static/public/js/main.js b/glances/outputs/static/public/js/main.min.js similarity index 93% rename from glances/outputs/static/public/js/main.js rename to glances/outputs/static/public/js/main.min.js index 85121641..cc1e536a 100644 --- a/glances/outputs/static/public/js/main.js +++ b/glances/outputs/static/public/js/main.min.js @@ -27,6 +27,210 @@ var glancesApp = angular.module('glancesApp', ['ngRoute']) $rootScope.title = "Glances"; }]); +glancesApp.controller('statsController', ["$scope", "$rootScope", "$interval", "GlancesStats", "help", "arguments", "favicoService", function ($scope, $rootScope, $interval, GlancesStats, help, arguments, favicoService) { + $scope.help = help; + $scope.arguments = arguments; + + $scope.sorter = { + column: "cpu_percent", + auto: true, + isReverseColumn: function (column) { + return !(column == 'username' || column == 'name'); + }, + getColumnLabel: function (column) { + if (_.isEqual(column, ['io_read', 'io_write'])) { + return 'io_counters'; + } else { + return column; + } + } + }; + + $scope.dataLoaded = false; + $scope.refreshData = function () { + GlancesStats.getData().then(function (data) { + + $scope.statsAlert = GlancesStats.getPlugin('alert'); + $scope.statsCpu = GlancesStats.getPlugin('cpu'); + $scope.statsDiskio = GlancesStats.getPlugin('diskio'); + $scope.statsIrq = GlancesStats.getPlugin('irq'); + $scope.statsDocker = GlancesStats.getPlugin('docker'); + $scope.statsFs = GlancesStats.getPlugin('fs'); + $scope.statsFolders = GlancesStats.getPlugin('folders'); + $scope.statsIp = GlancesStats.getPlugin('ip'); + $scope.statsLoad = GlancesStats.getPlugin('load'); + $scope.statsMem = GlancesStats.getPlugin('mem'); + $scope.statsMemSwap = GlancesStats.getPlugin('memswap'); + $scope.statsAmps = GlancesStats.getPlugin('amps'); + $scope.statsNetwork = GlancesStats.getPlugin('network'); + $scope.statsPerCpu = GlancesStats.getPlugin('percpu'); + $scope.statsProcessCount = GlancesStats.getPlugin('processcount'); + $scope.statsProcessList = GlancesStats.getPlugin('processlist'); + $scope.statsQuicklook = GlancesStats.getPlugin('quicklook'); + $scope.statsRaid = GlancesStats.getPlugin('raid'); + $scope.statsSensors = GlancesStats.getPlugin('sensors'); + $scope.statsSystem = GlancesStats.getPlugin('system'); + $scope.statsUptime = GlancesStats.getPlugin('uptime'); + $scope.statsPorts = GlancesStats.getPlugin('ports'); + $scope.statsWifi = GlancesStats.getPlugin('wifi'); + + $rootScope.title = $scope.statsSystem.hostname + ' - Glances'; + + if ($scope.statsAlert.hasOngoingAlerts()) { + favicoService.badge($scope.statsAlert.countOngoingAlerts()); + } else { + favicoService.reset(); + } + + $scope.is_disconnected = false; + $scope.dataLoaded = true; + }, function() { + $scope.is_disconnected = true; + }); + }; + + $scope.refreshData(); + $interval(function () { + $scope.refreshData(); + }, arguments.time * 1000); // in milliseconds + + $scope.onKeyDown = function ($event) { + + switch (true) { + case !$event.shiftKey && $event.keyCode == keycodes.a: + // a => Sort processes automatically + $scope.sorter.column = "cpu_percent"; + $scope.sorter.auto = true; + break; + case $event.shiftKey && $event.keyCode == keycodes.A: + // A => Enable/disable AMPs + $scope.arguments.disable_amps = !$scope.arguments.disable_amps; + break; + case !$event.shiftKey && $event.keyCode == keycodes.c: + // c => Sort processes by CPU% + $scope.sorter.column = "cpu_percent"; + $scope.sorter.auto = false; + break; + case !$event.shiftKey && $event.keyCode == keycodes.m: + // m => Sort processes by MEM% + $scope.sorter.column = "memory_percent"; + $scope.sorter.auto = false; + break; + case !$event.shiftKey && $event.keyCode == keycodes.u: + // u => Sort processes by user + $scope.sorter.column = "username"; + $scope.sorter.auto = false; + break; + case !$event.shiftKey && $event.keyCode == keycodes.p: + // p => Sort processes by name + $scope.sorter.column = "name"; + $scope.sorter.auto = false; + break; + case !$event.shiftKey && $event.keyCode == keycodes.i: + // i => Sort processes by I/O rate + $scope.sorter.column = ['io_read', 'io_write']; + $scope.sorter.auto = false; + break; + case !$event.shiftKey && $event.keyCode == keycodes.t: + // t => Sort processes by time + $scope.sorter.column = "timemillis"; + $scope.sorter.auto = false; + break; + case !$event.shiftKey && $event.keyCode == keycodes.d: + // d => Show/hide disk I/O stats + $scope.arguments.disable_diskio = !$scope.arguments.disable_diskio; + break; + case $event.shiftKey && $event.keyCode == keycodes.Q: + // Q => Show/hide IRQ + $scope.arguments.enable_irq = !$scope.arguments.enable_irq; + break; + case !$event.shiftKey && $event.keyCode == keycodes.f: + // f => Show/hide filesystem stats + $scope.arguments.disable_fs = !$scope.arguments.disable_fs; + break; + case !$event.shiftKey && $event.keyCode == keycodes.n: + // n => Show/hide network stats + $scope.arguments.disable_network = !$scope.arguments.disable_network; + break; + case !$event.shiftKey && $event.keyCode == keycodes.s: + // s => Show/hide sensors stats + $scope.arguments.disable_sensors = !$scope.arguments.disable_sensors; + break; + case $event.shiftKey && $event.keyCode == keycodes.TWO: + // 2 => Show/hide left sidebar + $scope.arguments.disable_left_sidebar = !$scope.arguments.disable_left_sidebar; + break; + case !$event.shiftKey && $event.keyCode == keycodes.z: + // z => Enable/disable processes stats + $scope.arguments.disable_process = !$scope.arguments.disable_process; + break; + case $event.keyCode == keycodes.SLASH: + // SLASH => Enable/disable short processes name + $scope.arguments.process_short_name = !$scope.arguments.process_short_name; + break; + case $event.shiftKey && $event.keyCode == keycodes.D: + // D => Enable/disable Docker stats + $scope.arguments.disable_docker = !$scope.arguments.disable_docker; + break; + case !$event.shiftKey && $event.keyCode == keycodes.b: + // b => Bytes or bits for network I/O + $scope.arguments.byte = !$scope.arguments.byte; + break; + case $event.shiftKey && $event.keyCode == keycodes.b: + // 'B' => Switch between bit/s and IO/s for Disk IO + $scope.arguments.diskio_iops = !$scope.arguments.diskio_iops; + break; + case !$event.shiftKey && $event.keyCode == keycodes.l: + // l => Show/hide alert logs + $scope.arguments.disable_alert = !$scope.arguments.disable_alert; + break; + case $event.shiftKey && $event.keyCode == keycodes.ONE: + // 1 => Global CPU or per-CPU stats + $scope.arguments.percpu = !$scope.arguments.percpu; + break; + case !$event.shiftKey && $event.keyCode == keycodes.h: + // h => Show/hide this help screen + $scope.arguments.help_tag = !$scope.arguments.help_tag; + break; + case $event.shiftKey && $event.keyCode == keycodes.T: + // T => View network I/O as combination + $scope.arguments.network_sum = !$scope.arguments.network_sum; + break; + case $event.shiftKey && $event.keyCode == keycodes.u: + // U => View cumulative network I/O + $scope.arguments.network_cumul = !$scope.arguments.network_cumul; + break; + case $event.shiftKey && $event.keyCode == keycodes.f: + // F => Show filesystem free space + $scope.arguments.fs_free_space = !$scope.arguments.fs_free_space; + break; + case $event.shiftKey && $event.keyCode == keycodes.THREE: + // 3 => Enable/disable quick look plugin + $scope.arguments.disable_quicklook = !$scope.arguments.disable_quicklook; + break; + case $event.shiftKey && $event.keyCode == keycodes.FIVE: + $scope.arguments.disable_quicklook = !$scope.arguments.disable_quicklook; + $scope.arguments.disable_cpu = !$scope.arguments.disable_cpu; + $scope.arguments.disable_mem = !$scope.arguments.disable_mem; + $scope.arguments.disable_memswap = !$scope.arguments.disable_memswap; + $scope.arguments.disable_load = !$scope.arguments.disable_load; + break; + case $event.shiftKey && $event.keyCode == keycodes.i: + // I => Show/hide IP module + $scope.arguments.disable_ip = !$scope.arguments.disable_ip; + break; + case $event.shiftKey && $event.keyCode == keycodes.p: + // I => Enable/disable ports module + $scope.arguments.disable_ports = !$scope.arguments.disable_ports; + break; + case $event.shiftKey && $event.keyCode == keycodes.w: + // 'W' > Enable/Disable Wifi plugin + $scope.arguments.disable_wifi = !$scope.arguments.disable_wifi; + break; + } + }; +}]); + glancesApp.directive("sortableTh", function() { return { restrict: 'A', @@ -177,199 +381,6 @@ glancesApp.filter('timedelta', ["$filter", function($filter) { } }]); -glancesApp.controller('statsController', ["$scope", "$rootScope", "$interval", "GlancesStats", "help", "arguments", function ($scope, $rootScope, $interval, GlancesStats, help, arguments) { - $scope.help = help; - $scope.arguments = arguments; - - $scope.sorter = { - column: "cpu_percent", - auto: true, - isReverseColumn: function (column) { - return !(column == 'username' || column == 'name'); - }, - getColumnLabel: function (column) { - if (_.isEqual(column, ['io_read', 'io_write'])) { - return 'io_counters'; - } else { - return column; - } - } - }; - - $scope.dataLoaded = false; - $scope.refreshData = function () { - GlancesStats.getData().then(function (data) { - - $scope.statsAlert = GlancesStats.getPlugin('alert'); - $scope.statsCpu = GlancesStats.getPlugin('cpu'); - $scope.statsDiskio = GlancesStats.getPlugin('diskio'); - $scope.statsIrq = GlancesStats.getPlugin('irq'); - $scope.statsDocker = GlancesStats.getPlugin('docker'); - $scope.statsFs = GlancesStats.getPlugin('fs'); - $scope.statsFolders = GlancesStats.getPlugin('folders'); - $scope.statsIp = GlancesStats.getPlugin('ip'); - $scope.statsLoad = GlancesStats.getPlugin('load'); - $scope.statsMem = GlancesStats.getPlugin('mem'); - $scope.statsMemSwap = GlancesStats.getPlugin('memswap'); - $scope.statsAmps = GlancesStats.getPlugin('amps'); - $scope.statsNetwork = GlancesStats.getPlugin('network'); - $scope.statsPerCpu = GlancesStats.getPlugin('percpu'); - $scope.statsProcessCount = GlancesStats.getPlugin('processcount'); - $scope.statsProcessList = GlancesStats.getPlugin('processlist'); - $scope.statsQuicklook = GlancesStats.getPlugin('quicklook'); - $scope.statsRaid = GlancesStats.getPlugin('raid'); - $scope.statsSensors = GlancesStats.getPlugin('sensors'); - $scope.statsSystem = GlancesStats.getPlugin('system'); - $scope.statsUptime = GlancesStats.getPlugin('uptime'); - $scope.statsPorts = GlancesStats.getPlugin('ports'); - - $rootScope.title = $scope.statsSystem.hostname + ' - Glances'; - - $scope.is_disconnected = false; - $scope.dataLoaded = true; - }, function() { - $scope.is_disconnected = true; - }); - }; - - $scope.refreshData(); - $interval(function () { - $scope.refreshData(); - }, arguments.time * 1000); // in milliseconds - - $scope.onKeyDown = function ($event) { - - switch (true) { - case !$event.shiftKey && $event.keyCode == keycodes.a: - // a => Sort processes automatically - $scope.sorter.column = "cpu_percent"; - $scope.sorter.auto = true; - break; - case $event.shiftKey && $event.keyCode == keycodes.A: - // A => Enable/disable AMPs - $scope.arguments.disable_amps = !$scope.arguments.disable_amps; - break; - case !$event.shiftKey && $event.keyCode == keycodes.c: - // c => Sort processes by CPU% - $scope.sorter.column = "cpu_percent"; - $scope.sorter.auto = false; - break; - case !$event.shiftKey && $event.keyCode == keycodes.m: - // m => Sort processes by MEM% - $scope.sorter.column = "memory_percent"; - $scope.sorter.auto = false; - break; - case !$event.shiftKey && $event.keyCode == keycodes.u: - // u => Sort processes by user - $scope.sorter.column = "username"; - $scope.sorter.auto = false; - break; - case !$event.shiftKey && $event.keyCode == keycodes.p: - // p => Sort processes by name - $scope.sorter.column = "name"; - $scope.sorter.auto = false; - break; - case !$event.shiftKey && $event.keyCode == keycodes.i: - // i => Sort processes by I/O rate - $scope.sorter.column = ['io_read', 'io_write']; - $scope.sorter.auto = false; - break; - case !$event.shiftKey && $event.keyCode == keycodes.t: - // t => Sort processes by time - $scope.sorter.column = "timemillis"; - $scope.sorter.auto = false; - break; - case !$event.shiftKey && $event.keyCode == keycodes.d: - // d => Show/hide disk I/O stats - $scope.arguments.disable_diskio = !$scope.arguments.disable_diskio; - break; - case $event.shiftKey && $event.keyCode == keycodes.Q: - // R => Show/hide IRQ - $scope.arguments.disable_irq = !$scope.arguments.disable_irq; - break; - case !$event.shiftKey && $event.keyCode == keycodes.f: - // f => Show/hide filesystem stats - $scope.arguments.disable_fs = !$scope.arguments.disable_fs; - break; - case !$event.shiftKey && $event.keyCode == keycodes.n: - // n => Show/hide network stats - $scope.arguments.disable_network = !$scope.arguments.disable_network; - break; - case !$event.shiftKey && $event.keyCode == keycodes.s: - // s => Show/hide sensors stats - $scope.arguments.disable_sensors = !$scope.arguments.disable_sensors; - break; - case $event.shiftKey && $event.keyCode == keycodes.TWO: - // 2 => Show/hide left sidebar - $scope.arguments.disable_left_sidebar = !$scope.arguments.disable_left_sidebar; - break; - case !$event.shiftKey && $event.keyCode == keycodes.z: - // z => Enable/disable processes stats - $scope.arguments.disable_process = !$scope.arguments.disable_process; - break; - case $event.keyCode == keycodes.SLASH: - // SLASH => Enable/disable short processes name - $scope.arguments.process_short_name = !$scope.arguments.process_short_name; - break; - case $event.shiftKey && $event.keyCode == keycodes.D: - // D => Enable/disable Docker stats - $scope.arguments.disable_docker = !$scope.arguments.disable_docker; - break; - case !$event.shiftKey && $event.keyCode == keycodes.b: - // b => Bytes or bits for network I/O - $scope.arguments.byte = !$scope.arguments.byte; - break; - case $event.shiftKey && $event.keyCode == keycodes.b: - // 'B' => Switch between bit/s and IO/s for Disk IO - $scope.arguments.diskio_iops = !$scope.arguments.diskio_iops; - break; - case !$event.shiftKey && $event.keyCode == keycodes.l: - // l => Show/hide alert logs - $scope.arguments.disable_log = !$scope.arguments.disable_log; - break; - case $event.shiftKey && $event.keyCode == keycodes.ONE: - // 1 => Global CPU or per-CPU stats - $scope.arguments.percpu = !$scope.arguments.percpu; - break; - case !$event.shiftKey && $event.keyCode == keycodes.h: - // h => Show/hide this help screen - $scope.arguments.help_tag = !$scope.arguments.help_tag; - break; - case $event.shiftKey && $event.keyCode == keycodes.T: - // T => View network I/O as combination - $scope.arguments.network_sum = !$scope.arguments.network_sum; - break; - case $event.shiftKey && $event.keyCode == keycodes.u: - // U => View cumulative network I/O - $scope.arguments.network_cumul = !$scope.arguments.network_cumul; - break; - case $event.shiftKey && $event.keyCode == keycodes.f: - // F => Show filesystem free space - $scope.arguments.fs_free_space = !$scope.arguments.fs_free_space; - break; - case $event.shiftKey && $event.keyCode == keycodes.THREE: - // 3 => Enable/disable quick look plugin - $scope.arguments.disable_quicklook = !$scope.arguments.disable_quicklook; - break; - case $event.shiftKey && $event.keyCode == keycodes.FIVE: - $scope.arguments.disable_quicklook = !$scope.arguments.disable_quicklook; - $scope.arguments.disable_cpu = !$scope.arguments.disable_cpu; - $scope.arguments.disable_mem = !$scope.arguments.disable_mem; - $scope.arguments.disable_swap = !$scope.arguments.disable_swap; - $scope.arguments.disable_load = !$scope.arguments.disable_load; - break; - case $event.shiftKey && $event.keyCode == keycodes.i: - // I => Show/hide IP module - $scope.arguments.disable_ip = !$scope.arguments.disable_ip; - break; - case $event.shiftKey && $event.keyCode == keycodes.p: - // I => Enable/disable ports module - $scope.arguments.disable_ports = !$scope.arguments.disable_ports; - break; - } - }; -}]); - var keycodes = { 'a' : '65', 'c' : '67', @@ -405,6 +416,21 @@ var keycodes = { 'R' : '82', } +glancesApp.service('favicoService', function() { + + var favico = new Favico({ + animation : 'none' + }); + + this.badge = function(nb) { + favico.badge(nb); + }; + + this.reset = function() { + favico.reset(); + }; +}); + glancesApp.service('GlancesStats', ["$http", "$injector", "$q", "GlancesPlugin", function($http, $injector, $q, GlancesPlugin) { var _stats = [], _views = [], _limits = []; @@ -412,7 +438,7 @@ glancesApp.service('GlancesStats', ["$http", "$injector", "$q", "GlancesPlugin", 'alert': 'GlancesPluginAlert', 'cpu': 'GlancesPluginCpu', 'diskio': 'GlancesPluginDiskio', - 'irq' : 'GlancesPluginIrq', + 'irq' : 'GlancesPluginIrq', 'docker': 'GlancesPluginDocker', 'ip': 'GlancesPluginIp', 'fs': 'GlancesPluginFs', @@ -430,7 +456,8 @@ glancesApp.service('GlancesStats', ["$http", "$injector", "$q", "GlancesPlugin", 'sensors': 'GlancesPluginSensors', 'system': 'GlancesPluginSystem', 'uptime': 'GlancesPluginUptime', - 'ports': 'GlancesPluginPorts' + 'ports': 'GlancesPluginPorts', + 'wifi': 'GlancesPluginWifi' }; this.getData = function() { @@ -532,7 +559,7 @@ glancesApp.service('GlancesPluginAlert', function () { , minutes = parseInt((duration / (1000 * 60)) % 60) , hours = parseInt((duration / (1000 * 60 * 60)) % 24); - alert.duration = _.padLeft(hours, 2, '0') + ":" + _.padLeft(minutes, 2, '0') + ":" + _.padLeft(seconds, 2, '0'); + alert.duration = _.padStart(hours, 2, '0') + ":" + _.padStart(minutes, 2, '0') + ":" + _.padStart(seconds, 2, '0'); } _alerts.push(alert); @@ -550,6 +577,14 @@ glancesApp.service('GlancesPluginAlert', function () { this.count = function () { return _alerts.length; }; + + this.hasOngoingAlerts = function () { + return _.filter(_alerts, { 'ongoing': true }).length > 0; + }; + + this.countOngoingAlerts = function () { + return _.filter(_alerts, { 'ongoing': true }).length; + } }); glancesApp.service('GlancesPluginAmps', function() { @@ -824,7 +859,7 @@ glancesApp.service('GlancesPluginIrq', function() { var irq = { 'irq_line': IrqData['irq_line'], - 'irq_rate': IrqData['irq_rate'] + 'irq_rate': IrqData['irq_rate'] }; this.irqs.push(irq); @@ -1176,7 +1211,7 @@ glancesApp.service('GlancesPluginRaid', function () { this.disks = []; this.setData = function (data, views) { - this.disks = []; + var disks = []; data = data[_pluginName]; _.forIn(data, function(diskData, diskKey) { @@ -1199,8 +1234,10 @@ glancesApp.service('GlancesPluginRaid', function () { }); }); - this.disks.push(disk); - }, this); + disks.push(disk); + }); + + this.disks = disks; }; this.hasDisks = function() { @@ -1287,3 +1324,40 @@ glancesApp.service('GlancesPluginUptime', function() { this.uptime = data['uptime']; }; }); + +glancesApp.service('GlancesPluginWifi', function() { + var _pluginName = "wifi"; + var _view = {}; + this.hotspots = []; + + this.setData = function(data, views) { + data = data[_pluginName]; + _view = views[_pluginName]; + + this.hotspots = []; + for (var i = 0; i < data.length; i++) { + var hotspotData = data[i]; + + if (hotspotData['ssid'] === '') { + continue; + } + + var hotspot = { + 'ssid': hotspotData['ssid'], + 'encrypted': hotspotData['encrypted'], + 'signal': hotspotData['signal'], + 'encryption_type': hotspotData['encryption_type'], + }; + + this.hotspots.push(hotspot); + } + }; + + this.getDecoration = function(hotpost, field) { + if(_view[hotpost.ssid][field] == undefined) { + return; + } + + return _view[hotpost.ssid][field].decoration.toLowerCase(); + }; +}); diff --git a/glances/outputs/static/public/js/templates.js b/glances/outputs/static/public/js/templates.min.js similarity index 97% rename from glances/outputs/static/public/js/templates.js rename to glances/outputs/static/public/js/templates.min.js index 97c6ec6a..ab588b58 100644 --- a/glances/outputs/static/public/js/templates.js +++ b/glances/outputs/static/public/js/templates.min.js @@ -21,4 +21,5 @@ $templateCache.put('plugins/quicklook.html','
\n {{ stat $templateCache.put('plugins/raid.html','
\n
RAID disks
\n
Used
\n
Total
\n
\n
\n
\n {{ disk.type | uppercase }} {{ disk.name }}\n
\u2514\u2500 Degraded mode
\n
   \u2514\u2500 {{ disk.config }}
\n\n
\u2514\u2500 Status {{ disk.status }}
\n
\n    {{ $last ? \'\u2514\u2500\' : \'\u251C\u2500\' }} disk {{ component.number }}: {{ component.name }}\n
\n
\n
{{ disk.used }}
\n
{{ disk.available }}
\n
'); $templateCache.put('plugins/sensors.html','
\n
SENSORS
\n
\n\n
\n
{{ sensor.label }}
\n
{{ sensor.unit }}
\n
{{ sensor.value }}
\n
\n'); $templateCache.put('plugins/system.html','Disconnected from\n{{ statsSystem.hostname }}\n\n'); -$templateCache.put('plugins/uptime.html','Uptime: {{ statsUptime.uptime }}\n');}]); \ No newline at end of file +$templateCache.put('plugins/uptime.html','Uptime: {{ statsUptime.uptime }}\n'); +$templateCache.put('plugins/wifi.html','
\n
WIFI
\n
\n
dBm
\n
\n
\n
{{ hotspot.ssid|limitTo:20 }} {{ hotspot.encryption_type }}
\n
\n
{{ hotspot.signal }}
\n
\n');}]); \ No newline at end of file diff --git a/glances/outputs/static/public/js/vendor.js b/glances/outputs/static/public/js/vendor.min.js similarity index 97% rename from glances/outputs/static/public/js/vendor.js rename to glances/outputs/static/public/js/vendor.min.js index a5fd87ff..db4b128d 100644 --- a/glances/outputs/static/public/js/vendor.js +++ b/glances/outputs/static/public/js/vendor.min.js @@ -32850,17 +32850,21 @@ function ngViewFillContentFactory($compile, $controller, $route) { var undefined; /** Used as the semantic version number. */ - var VERSION = '4.15.0'; + var VERSION = '4.16.4'; /** Used as the size to enable large array optimizations. */ var LARGE_ARRAY_SIZE = 200; - /** Used as the `TypeError` message for "Functions" methods. */ - var FUNC_ERROR_TEXT = 'Expected a function'; + /** Error message constants. */ + var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://github.com/es-shims.', + FUNC_ERROR_TEXT = 'Expected a function'; /** Used to stand-in for `undefined` hash values. */ var HASH_UNDEFINED = '__lodash_hash_undefined__'; + /** Used as the maximum memoize cache size. */ + var MAX_MEMOIZE_SIZE = 500; + /** Used as the internal argument placeholder. */ var PLACEHOLDER = '__lodash_placeholder__'; @@ -32885,7 +32889,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { DEFAULT_TRUNC_OMISSION = '...'; /** Used to detect hot functions by number of calls within a span of milliseconds. */ - var HOT_COUNT = 150, + var HOT_COUNT = 500, HOT_SPAN = 16; /** Used to indicate the type of lazy iteratees. */ @@ -32929,6 +32933,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { numberTag = '[object Number]', objectTag = '[object Object]', promiseTag = '[object Promise]', + proxyTag = '[object Proxy]', regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', @@ -32954,8 +32959,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; /** Used to match HTML entities and HTML characters. */ - var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g, - reUnescapedHtml = /[&<>"'`]/g, + var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, + reUnescapedHtml = /[&<>"']/g, reHasEscapedHtml = RegExp(reEscapedHtml.source), reHasUnescapedHtml = RegExp(reUnescapedHtml.source); @@ -33002,9 +33007,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** Used to match `RegExp` flags from their coerced string values. */ var reFlags = /\w*$/; - /** Used to detect hexadecimal string values. */ - var reHasHexPrefix = /^0x/i; - /** Used to detect bad signed hexadecimal string values. */ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; @@ -33199,7 +33201,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', '\u0132': 'IJ', '\u0133': 'ij', '\u0152': 'Oe', '\u0153': 'oe', - '\u0149': "'n", '\u017f': 'ss' + '\u0149': "'n", '\u017f': 's' }; /** Used to map characters to HTML entities. */ @@ -33208,8 +33210,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { '<': '<', '>': '>', '"': '"', - "'": ''', - '`': '`' + "'": ''' }; /** Used to map HTML entities to characters. */ @@ -33218,8 +33219,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { '<': '<', '>': '>', '"': '"', - ''': "'", - '`': '`' + ''': "'" }; /** Used to escape characters for inclusion in compiled string literals. */ @@ -33660,18 +33660,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {number} Returns the index of the matched value, else `-1`. */ function baseIndexOf(array, value, fromIndex) { - if (value !== value) { - return baseFindIndex(array, baseIsNaN, fromIndex); - } - var index = fromIndex - 1, - length = array.length; - - while (++index < length) { - if (array[index] === value) { - return index; - } - } - return -1; + return value === value + ? strictIndexOf(array, value, fromIndex) + : baseFindIndex(array, baseIsNaN, fromIndex); } /** @@ -33876,7 +33867,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } /** - * Checks if a cache value for `key` exists. + * Checks if a `cache` value for `key` exists. * * @private * @param {Object} cache The cache to query. @@ -33934,7 +33925,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { while (length--) { if (array[length] === placeholder) { - result++; + ++result; } } return result; @@ -34004,25 +33995,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { return reHasUnicodeWord.test(string); } - /** - * Checks if `value` is a host object in IE < 9. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a host object, else `false`. - */ - function isHostObject(value) { - // Many host objects are `Object` objects that can coerce to strings - // despite having improperly defined `toString` methods. - var result = false; - if (value != null && typeof value.toString != 'function') { - try { - result = !!(value + ''); - } catch (e) {} - } - return result; - } - /** * Converts `iterator` to an array. * @@ -34130,6 +34102,48 @@ function ngViewFillContentFactory($compile, $controller, $route) { return result; } + /** + * A specialized version of `_.indexOf` which performs strict equality + * comparisons of values, i.e. `===`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function strictIndexOf(array, value, fromIndex) { + var index = fromIndex - 1, + length = array.length; + + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * A specialized version of `_.lastIndexOf` which performs strict equality + * comparisons of values, i.e. `===`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function strictLastIndexOf(array, value, fromIndex) { + var index = fromIndex + 1; + while (index--) { + if (array[index] === value) { + return index; + } + } + return index; + } + /** * Gets the number of symbols in `string`. * @@ -34175,7 +34189,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { function unicodeSize(string) { var result = reUnicode.lastIndex = 0; while (reUnicode.test(string)) { - result++; + ++result; } return result; } @@ -34230,17 +34244,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { * lodash.isFunction(lodash.bar); * // => true * - * // Use `context` to stub `Date#getTime` use in `_.now`. - * var stubbed = _.runInContext({ - * 'Date': function() { - * return { 'getTime': stubGetTime }; - * } - * }); - * * // Create a suped-up `defer` in Node.js. * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer; */ - function runInContext(context) { + var runInContext = (function runInContext(context) { context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root; /** Built-in constructor references. */ @@ -34300,6 +34307,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { var Buffer = moduleExports ? context.Buffer : undefined, Symbol = context.Symbol, Uint8Array = context.Uint8Array, + allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined, getPrototype = overArg(Object.getPrototypeOf, Object), iteratorSymbol = Symbol ? Symbol.iterator : undefined, objectCreate = Object.create, @@ -34307,6 +34315,14 @@ function ngViewFillContentFactory($compile, $controller, $route) { splice = arrayProto.splice, spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined; + var defineProperty = (function() { + try { + var func = getNative(Object, 'defineProperty'); + func({}, '', {}); + return func; + } catch (e) {} + }()); + /** Mocked built-ins. */ var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout, ctxNow = Date && Date.now !== root.Date.now && Date.now, @@ -34322,6 +34338,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { nativeKeys = overArg(Object.keys, Object), nativeMax = Math.max, nativeMin = Math.min, + nativeNow = Date.now, nativeParseInt = context.parseInt, nativeRandom = Math.random, nativeReverse = arrayProto.reverse; @@ -34334,20 +34351,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { WeakMap = getNative(context, 'WeakMap'), nativeCreate = getNative(Object, 'create'); - /* Used to set `toString` methods. */ - var defineProperty = (function() { - var func = getNative(Object, 'defineProperty'), - name = getNative.name; - - return (name && name.length > 2) ? func : undefined; - }()); - /** Used to store function metadata. */ var metaMap = WeakMap && new WeakMap; - /** Detect if properties shadowing those on `Object.prototype` are non-enumerable. */ - var nonEnumShadows = !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf'); - /** Used to lookup unminified function names. */ var realNames = {}; @@ -34494,6 +34500,30 @@ function ngViewFillContentFactory($compile, $controller, $route) { return new LodashWrapper(value); } + /** + * The base implementation of `_.create` without support for assigning + * properties to the created object. + * + * @private + * @param {Object} proto The object to inherit from. + * @returns {Object} Returns the new object. + */ + var baseCreate = (function() { + function object() {} + return function(proto) { + if (!isObject(proto)) { + return {}; + } + if (objectCreate) { + return objectCreate(proto); + } + object.prototype = proto; + var result = new object; + object.prototype = undefined; + return result; + }; + }()); + /** * The function whose prototype chain sequence wrappers inherit from. * @@ -34735,6 +34765,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function hashClear() { this.__data__ = nativeCreate ? nativeCreate(null) : {}; + this.size = 0; } /** @@ -34748,7 +34779,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function hashDelete(key) { - return this.has(key) && delete this.__data__[key]; + var result = this.has(key) && delete this.__data__[key]; + this.size -= result ? 1 : 0; + return result; } /** @@ -34795,6 +34828,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function hashSet(key, value) { var data = this.__data__; + this.size += this.has(key) ? 0 : 1; data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; return this; } @@ -34835,6 +34869,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function listCacheClear() { this.__data__ = []; + this.size = 0; } /** @@ -34859,6 +34894,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } else { splice.call(data, index, 1); } + --this.size; return true; } @@ -34906,6 +34942,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { index = assocIndexOf(data, key); if (index < 0) { + ++this.size; data.push([key, value]); } else { data[index][1] = value; @@ -34948,6 +34985,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @memberOf MapCache */ function mapCacheClear() { + this.size = 0; this.__data__ = { 'hash': new Hash, 'map': new (Map || ListCache), @@ -34965,7 +35003,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function mapCacheDelete(key) { - return getMapData(this, key)['delete'](key); + var result = getMapData(this, key)['delete'](key); + this.size -= result ? 1 : 0; + return result; } /** @@ -35005,7 +35045,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Object} Returns the map cache instance. */ function mapCacheSet(key, value) { - getMapData(this, key).set(key, value); + var data = getMapData(this, key), + size = data.size; + + data.set(key, value); + this.size += data.size == size ? 0 : 1; return this; } @@ -35078,7 +35122,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @param {Array} [entries] The key-value pairs to cache. */ function Stack(entries) { - this.__data__ = new ListCache(entries); + var data = this.__data__ = new ListCache(entries); + this.size = data.size; } /** @@ -35090,6 +35135,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function stackClear() { this.__data__ = new ListCache; + this.size = 0; } /** @@ -35102,7 +35148,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function stackDelete(key) { - return this.__data__['delete'](key); + var data = this.__data__, + result = data['delete'](key); + + this.size = data.size; + return result; } /** @@ -35142,16 +35192,18 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Object} Returns the stack cache instance. */ function stackSet(key, value) { - var cache = this.__data__; - if (cache instanceof ListCache) { - var pairs = cache.__data__; + var data = this.__data__; + if (data instanceof ListCache) { + var pairs = data.__data__; if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { pairs.push([key, value]); + this.size = ++data.size; return this; } - cache = this.__data__ = new MapCache(pairs); + data = this.__data__ = new MapCache(pairs); } - cache.set(key, value); + data.set(key, value); + this.size = data.size; return this; } @@ -35173,24 +35225,67 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Array} Returns the array of property names. */ function arrayLikeKeys(value, inherited) { - // Safari 8.1 makes `arguments.callee` enumerable in strict mode. - // Safari 9 makes `arguments.length` enumerable in strict mode. - var result = (isArray(value) || isArguments(value)) - ? baseTimes(value.length, String) - : []; - - var length = result.length, - skipIndexes = !!length; + var isArr = isArray(value), + isArg = !isArr && isArguments(value), + isBuff = !isArr && !isArg && isBuffer(value), + isType = !isArr && !isArg && !isBuff && isTypedArray(value), + skipIndexes = isArr || isArg || isBuff || isType, + result = skipIndexes ? baseTimes(value.length, String) : [], + length = result.length; for (var key in value) { if ((inherited || hasOwnProperty.call(value, key)) && - !(skipIndexes && (key == 'length' || isIndex(key, length)))) { + !(skipIndexes && ( + // Safari 9 has enumerable `arguments.length` in strict mode. + key == 'length' || + // Node.js 0.10 has enumerable non-index properties on buffers. + (isBuff && (key == 'offset' || key == 'parent')) || + // PhantomJS 2 has enumerable non-index properties on typed arrays. + (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || + // Skip index properties. + isIndex(key, length) + ))) { result.push(key); } } return result; } + /** + * A specialized version of `_.sample` for arrays. + * + * @private + * @param {Array} array The array to sample. + * @returns {*} Returns the random element. + */ + function arraySample(array) { + var length = array.length; + return length ? array[baseRandom(0, length - 1)] : undefined; + } + + /** + * A specialized version of `_.sampleSize` for arrays. + * + * @private + * @param {Array} array The array to sample. + * @param {number} n The number of elements to sample. + * @returns {Array} Returns the random elements. + */ + function arraySampleSize(array, n) { + return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length)); + } + + /** + * A specialized version of `_.shuffle` for arrays. + * + * @private + * @param {Array} array The array to shuffle. + * @returns {Array} Returns the new shuffled array. + */ + function arrayShuffle(array) { + return shuffleSelf(copyArray(array)); + } + /** * Used by `_.defaults` to customize its `_.assignIn` use. * @@ -35220,8 +35315,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function assignMergeValue(object, key, value) { if ((value !== undefined && !eq(object[key], value)) || - (typeof key == 'number' && value === undefined && !(key in object))) { - object[key] = value; + (value === undefined && !(key in object))) { + baseAssignValue(object, key, value); } } @@ -35239,7 +35334,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { var objValue = object[key]; if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || (value === undefined && !(key in object))) { - object[key] = value; + baseAssignValue(object, key, value); } } @@ -35292,6 +35387,28 @@ function ngViewFillContentFactory($compile, $controller, $route) { return object && copyObject(source, keys(source), object); } + /** + * The base implementation of `assignValue` and `assignMergeValue` without + * value checks. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function baseAssignValue(object, key, value) { + if (key == '__proto__' && defineProperty) { + defineProperty(object, key, { + 'configurable': true, + 'enumerable': true, + 'value': value, + 'writable': true + }); + } else { + object[key] = value; + } + } + /** * The base implementation of `_.at` without support for individual paths. * @@ -35372,9 +35489,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { return cloneBuffer(value, isDeep); } if (tag == objectTag || tag == argsTag || (isFunc && !object)) { - if (isHostObject(value)) { - return object ? value : {}; - } result = initCloneObject(isFunc ? {} : value); if (!isDeep) { return copySymbols(value, baseAssign(result, value)); @@ -35394,9 +35508,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } stack.set(value, result); - if (!isArr) { - var props = isFull ? getAllKeys(value) : keys(value); - } + var props = isArr ? undefined : (isFull ? getAllKeys : keys)(value); arrayEach(props || value, function(subValue, key) { if (props) { key = subValue; @@ -35448,18 +35560,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { return true; } - /** - * The base implementation of `_.create` without support for assigning - * properties to the created object. - * - * @private - * @param {Object} prototype The object to inherit from. - * @returns {Object} Returns the new object. - */ - function baseCreate(proto) { - return isObject(proto) ? objectCreate(proto) : {}; - } - /** * The base implementation of `_.delay` and `_.defer` which accepts `args` * to provide to `func`. @@ -35942,6 +36042,17 @@ function ngViewFillContentFactory($compile, $controller, $route) { return func == null ? undefined : apply(func, object, args); } + /** + * The base implementation of `_.isArguments`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + */ + function baseIsArguments(value) { + return isObjectLike(value) && objectToString.call(value) == argsTag; + } + /** * The base implementation of `_.isArrayBuffer` without Node.js optimizations. * @@ -36018,10 +36129,17 @@ function ngViewFillContentFactory($compile, $controller, $route) { othTag = getTag(other); othTag = othTag == argsTag ? objectTag : othTag; } - var objIsObj = objTag == objectTag && !isHostObject(object), - othIsObj = othTag == objectTag && !isHostObject(other), + var objIsObj = objTag == objectTag, + othIsObj = othTag == objectTag, isSameTag = objTag == othTag; + if (isSameTag && isBuffer(object)) { + if (!isBuffer(other)) { + return false; + } + objIsArr = true; + objIsObj = false; + } if (isSameTag && !objIsObj) { stack || (stack = new Stack); return (objIsArr || isTypedArray(object)) @@ -36124,7 +36242,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (!isObject(value) || isMasked(value)) { return false; } - var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor; + var pattern = isFunction(value) ? reIsNative : reIsHostCtor; return pattern.test(toSource(value)); } @@ -36311,14 +36429,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (object === source) { return; } - if (!(isArray(source) || isTypedArray(source))) { - var props = baseKeysIn(source); - } - arrayEach(props || source, function(srcValue, key) { - if (props) { - key = srcValue; - srcValue = source[key]; - } + baseFor(source, function(srcValue, key) { if (isObject(srcValue)) { stack || (stack = new Stack); baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); @@ -36333,7 +36444,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } assignMergeValue(object, key, newValue); } - }); + }, keysIn); } /** @@ -36367,29 +36478,37 @@ function ngViewFillContentFactory($compile, $controller, $route) { var isCommon = newValue === undefined; if (isCommon) { + var isArr = isArray(srcValue), + isBuff = !isArr && isBuffer(srcValue), + isTyped = !isArr && !isBuff && isTypedArray(srcValue); + newValue = srcValue; - if (isArray(srcValue) || isTypedArray(srcValue)) { + if (isArr || isBuff || isTyped) { if (isArray(objValue)) { newValue = objValue; } else if (isArrayLikeObject(objValue)) { newValue = copyArray(objValue); } - else { + else if (isBuff) { isCommon = false; - newValue = baseClone(srcValue, true); + newValue = cloneBuffer(srcValue, true); + } + else if (isTyped) { + isCommon = false; + newValue = cloneTypedArray(srcValue, true); + } + else { + newValue = []; } } else if (isPlainObject(srcValue) || isArguments(srcValue)) { + newValue = objValue; if (isArguments(objValue)) { newValue = toPlainObject(objValue); } else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) { - isCommon = false; - newValue = baseClone(srcValue, true); - } - else { - newValue = objValue; + newValue = initCloneObject(srcValue); } } else { @@ -36482,7 +36601,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { value = object[key]; if (predicate(value, key)) { - result[key] = value; + baseAssignValue(result, key, value); } } return result; @@ -36648,24 +36767,31 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Function} Returns the new function. */ function baseRest(func, start) { - start = nativeMax(start === undefined ? (func.length - 1) : start, 0); - return function() { - var args = arguments, - index = -1, - length = nativeMax(args.length - start, 0), - array = Array(length); + return setToString(overRest(func, start, identity), func + ''); + } - while (++index < length) { - array[index] = args[start + index]; - } - index = -1; - var otherArgs = Array(start + 1); - while (++index < start) { - otherArgs[index] = args[index]; - } - otherArgs[start] = array; - return apply(func, this, otherArgs); - }; + /** + * The base implementation of `_.sample`. + * + * @private + * @param {Array|Object} collection The collection to sample. + * @returns {*} Returns the random element. + */ + function baseSample(collection) { + return arraySample(values(collection)); + } + + /** + * The base implementation of `_.sampleSize` without param guards. + * + * @private + * @param {Array|Object} collection The collection to sample. + * @param {number} n The number of elements to sample. + * @returns {Array} Returns the random elements. + */ + function baseSampleSize(collection, n) { + var array = values(collection); + return shuffleSelf(array, baseClamp(n, 0, array.length)); } /** @@ -36709,7 +36835,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } /** - * The base implementation of `setData` without support for hot loop detection. + * The base implementation of `setData` without support for hot loop shorting. * * @private * @param {Function} func The function to associate metadata with. @@ -36721,6 +36847,34 @@ function ngViewFillContentFactory($compile, $controller, $route) { return func; }; + /** + * The base implementation of `setToString` without support for hot loop shorting. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var baseSetToString = !defineProperty ? identity : function(func, string) { + return defineProperty(func, 'toString', { + 'configurable': true, + 'enumerable': false, + 'value': constant(string), + 'writable': true + }); + }; + + /** + * The base implementation of `_.shuffle`. + * + * @private + * @param {Array|Object} collection The collection to shuffle. + * @returns {Array} Returns the new shuffled array. + */ + function baseShuffle(collection) { + return shuffleSelf(values(collection)); + } + /** * The base implementation of `_.slice` without an iteratee call guard. * @@ -36914,6 +37068,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (typeof value == 'string') { return value; } + if (isArray(value)) { + // Recursively convert values (susceptible to call stack limits). + return arrayMap(value, baseToString) + ''; + } if (isSymbol(value)) { return symbolToString ? symbolToString.call(value) : ''; } @@ -37135,6 +37293,17 @@ function ngViewFillContentFactory($compile, $controller, $route) { return isArray(value) ? value : stringToPath(value); } + /** + * A `baseRest` alias which can be replaced with `identity` by module + * replacement plugins. + * + * @private + * @type {Function} + * @param {Function} func The function to apply a rest parameter to. + * @returns {Function} Returns the new function. + */ + var castRest = baseRest; + /** * Casts `array` to a slice if it's needed. * @@ -37172,7 +37341,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (isDeep) { return buffer.slice(); } - var result = new buffer.constructor(buffer.length); + var length = buffer.length, + result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length); + buffer.copy(result); return result; } @@ -37449,6 +37620,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Object} Returns `object`. */ function copyObject(source, props, object, customizer) { + var isNew = !object; object || (object = {}); var index = -1, @@ -37461,7 +37633,14 @@ function ngViewFillContentFactory($compile, $controller, $route) { ? customizer(object[key], source[key], key, object, source) : undefined; - assignValue(object, key, newValue === undefined ? source[key] : newValue); + if (newValue === undefined) { + newValue = source[key]; + } + if (isNew) { + baseAssignValue(object, key, newValue); + } else { + assignValue(object, key, newValue); + } } return object; } @@ -37740,9 +37919,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Function} Returns the new flow function. */ function createFlow(fromRight) { - return baseRest(function(funcs) { - funcs = baseFlatten(funcs, 1); - + return flatRest(function(funcs) { var length = funcs.length, index = length, prereq = LodashWrapper.prototype.thru; @@ -37925,11 +38102,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Function} Returns the new over function. */ function createOver(arrayFunc) { - return baseRest(function(iteratees) { - iteratees = (iteratees.length == 1 && isArray(iteratees[0])) - ? arrayMap(iteratees[0], baseUnary(getIteratee())) - : arrayMap(baseFlatten(iteratees, 1), baseUnary(getIteratee())); - + return flatRest(function(iteratees) { + iteratees = arrayMap(iteratees, baseUnary(getIteratee())); return baseRest(function(args) { var thisArg = this; return arrayFunc(iteratees, function(iteratee) { @@ -38272,9 +38446,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { // Recursively compare arrays (susceptible to call stack limits). if (seen) { if (!arraySome(other, function(othValue, othIndex) { - if (!seen.has(othIndex) && + if (!cacheHas(seen, othIndex) && (arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack))) { - return seen.add(othIndex); + return seen.push(othIndex); } })) { result = false; @@ -38454,6 +38628,17 @@ function ngViewFillContentFactory($compile, $controller, $route) { return result; } + /** + * A specialized version of `baseRest` which flattens the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @returns {Function} Returns the new function. + */ + function flatRest(func) { + return setToString(overRest(func, undefined, flatten), func + ''); + } + /** * Creates an array of own enumerable property names and symbols of `object`. * @@ -38622,8 +38807,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ var getTag = baseGetTag; - // Fallback for data views, maps, sets, and weak maps in IE 11, - // for data views in Edge < 14, and promises in Node.js. + // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || (Map && getTag(new Map) != mapTag) || (Promise && getTag(Promise.resolve()) != promiseTag) || @@ -38699,9 +38883,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { function hasPath(object, path, hasFunc) { path = isKey(path, object) ? [path] : castPath(path); - var result, - index = -1, - length = path.length; + var index = -1, + length = path.length, + result = false; while (++index < length) { var key = toKey(path[index]); @@ -38710,10 +38894,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { } object = object[key]; } - if (result) { + if (result || ++index != length) { return result; } - var length = object ? object.length : 0; + length = object ? object.length : 0; return !!length && isLength(length) && isIndex(key, length) && (isArray(object) || isArguments(object)); } @@ -38808,9 +38992,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {string} Returns the modified source. */ function insertWrapDetails(source, details) { - var length = details.length, - lastIndex = length - 1; - + var length = details.length; + if (!length) { + return source; + } + var lastIndex = length - 1; details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex]; details = details.join(length > 2 ? ', ' : ' '); return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n'); @@ -38989,6 +39175,26 @@ function ngViewFillContentFactory($compile, $controller, $route) { }; } + /** + * A specialized version of `_.memoize` which clears the memoized function's + * cache when it exceeds `MAX_MEMOIZE_SIZE`. + * + * @private + * @param {Function} func The function to have its output memoized. + * @returns {Function} Returns the new memoized function. + */ + function memoizeCapped(func) { + var result = memoize(func, function(key) { + if (cache.size === MAX_MEMOIZE_SIZE) { + cache.clear(); + } + return key; + }); + + var cache = result.cache; + return result; + } + /** * Merges the function metadata of `source` into `data`. * @@ -39102,6 +39308,36 @@ function ngViewFillContentFactory($compile, $controller, $route) { return result; } + /** + * A specialized version of `baseRest` which transforms the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @param {Function} transform The rest array transform. + * @returns {Function} Returns the new function. + */ + function overRest(func, start, transform) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = transform(array); + return apply(func, this, otherArgs); + }; + } + /** * Gets the parent value at `path` of `object`. * @@ -39150,25 +39386,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @param {*} data The metadata. * @returns {Function} Returns `func`. */ - var setData = (function() { - var count = 0, - lastCalled = 0; - - return function(key, value) { - var stamp = now(), - remaining = HOT_SPAN - (stamp - lastCalled); - - lastCalled = stamp; - if (remaining > 0) { - if (++count >= HOT_COUNT) { - return key; - } - } else { - count = 0; - } - return baseSetData(key, value); - }; - }()); + var setData = shortOut(baseSetData); /** * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout). @@ -39182,6 +39400,16 @@ function ngViewFillContentFactory($compile, $controller, $route) { return root.setTimeout(func, wait); }; + /** + * Sets the `toString` method of `func` to return `string`. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var setToString = shortOut(baseSetToString); + /** * Sets the `toString` method of `wrapper` to mimic the source of `reference` * with wrapper details in a comment at the top of the source body. @@ -39192,14 +39420,64 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @param {number} bitmask The bitmask flags. See `createWrap` for more details. * @returns {Function} Returns `wrapper`. */ - var setWrapToString = !defineProperty ? identity : function(wrapper, reference, bitmask) { + function setWrapToString(wrapper, reference, bitmask) { var source = (reference + ''); - return defineProperty(wrapper, 'toString', { - 'configurable': true, - 'enumerable': false, - 'value': constant(insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))) - }); - }; + return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))); + } + + /** + * Creates a function that'll short out and invoke `identity` instead + * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` + * milliseconds. + * + * @private + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new shortable function. + */ + function shortOut(func) { + var count = 0, + lastCalled = 0; + + return function() { + var stamp = nativeNow(), + remaining = HOT_SPAN - (stamp - lastCalled); + + lastCalled = stamp; + if (remaining > 0) { + if (++count >= HOT_COUNT) { + return arguments[0]; + } + } else { + count = 0; + } + return func.apply(undefined, arguments); + }; + } + + /** + * A specialized version of `_.shuffle` which mutates and sets the size of `array`. + * + * @private + * @param {Array} array The array to shuffle. + * @param {number} [size=array.length] The size of `array`. + * @returns {Array} Returns `array`. + */ + function shuffleSelf(array, size) { + var index = -1, + length = array.length, + lastIndex = length - 1; + + size = size === undefined ? length : size; + while (++index < size) { + var rand = baseRandom(index, lastIndex), + value = array[rand]; + + array[rand] = array[index]; + array[index] = value; + } + array.length = size; + return array; + } /** * Converts `string` to a property path array. @@ -39208,7 +39486,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @param {string} string The string to convert. * @returns {Array} Returns the property path array. */ - var stringToPath = memoize(function(string) { + var stringToPath = memoizeCapped(function(string) { string = toString(string); var result = []; @@ -39387,24 +39665,25 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => [1] */ function concat() { - var length = arguments.length, - args = Array(length ? length - 1 : 0), + var length = arguments.length; + if (!length) { + return []; + } + var args = Array(length - 1), array = arguments[0], index = length; while (index--) { args[index - 1] = arguments[index]; } - return length - ? arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)) - : []; + return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)); } /** * Creates an array of `array` values not included in the other given arrays * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order of result values is determined by the - * order they occur in the first array. + * for equality comparisons. The order and references of result values are + * determined by the first array. * * **Note:** Unlike `_.pullAll`, this method returns a new array. * @@ -39430,8 +39709,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.difference` except that it accepts `iteratee` which * is invoked for each element of `array` and `values` to generate the criterion - * by which they're compared. Result values are chosen from the first array. - * The iteratee is invoked with one argument: (value). + * by which they're compared. The order and references of result values are + * determined by the first array. The iteratee is invoked with one argument: + * (value). * * **Note:** Unlike `_.pullAllBy`, this method returns a new array. * @@ -39464,9 +39744,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.difference` except that it accepts `comparator` - * which is invoked to compare elements of `array` to `values`. Result values - * are chosen from the first array. The comparator is invoked with two arguments: - * (arrVal, othVal). + * which is invoked to compare elements of `array` to `values`. The order and + * references of result values are determined by the first array. The comparator + * is invoked with two arguments: (arrVal, othVal). * * **Note:** Unlike `_.pullAllWith`, this method returns a new array. * @@ -39960,8 +40240,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * Creates an array of unique values that are included in all given arrays * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order of result values is determined by the - * order they occur in the first array. + * for equality comparisons. The order and references of result values are + * determined by the first array. * * @static * @memberOf _ @@ -39984,8 +40264,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.intersection` except that it accepts `iteratee` * which is invoked for each element of each `arrays` to generate the criterion - * by which they're compared. Result values are chosen from the first array. - * The iteratee is invoked with one argument: (value). + * by which they're compared. The order and references of result values are + * determined by the first array. The iteratee is invoked with one argument: + * (value). * * @static * @memberOf _ @@ -40019,9 +40300,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.intersection` except that it accepts `comparator` - * which is invoked to compare elements of `arrays`. Result values are chosen - * from the first array. The comparator is invoked with two arguments: - * (arrVal, othVal). + * which is invoked to compare elements of `arrays`. The order and references + * of result values are determined by the first array. The comparator is + * invoked with two arguments: (arrVal, othVal). * * @static * @memberOf _ @@ -40119,21 +40400,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { var index = length; if (fromIndex !== undefined) { index = toInteger(fromIndex); - index = ( - index < 0 - ? nativeMax(length + index, 0) - : nativeMin(index, length - 1) - ) + 1; + index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1); } - if (value !== value) { - return baseFindIndex(array, baseIsNaN, index - 1, true); - } - while (index--) { - if (array[index] === value) { - return index; - } - } - return -1; + return value === value + ? strictLastIndexOf(array, value, index) + : baseFindIndex(array, baseIsNaN, index, true); } /** @@ -40295,9 +40566,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * console.log(pulled); * // => ['b', 'd'] */ - var pullAt = baseRest(function(array, indexes) { - indexes = baseFlatten(indexes, 1); - + var pullAt = flatRest(function(array, indexes) { var length = array ? array.length : 0, result = baseAt(array, indexes); @@ -40872,8 +41141,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * Creates a duplicate-free version of an array, using * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons, in which only the first occurrence of each - * element is kept. + * for equality comparisons, in which only the first occurrence of each element + * is kept. The order of result values is determined by the order they occur + * in the array. * * @static * @memberOf _ @@ -40895,7 +41165,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.uniq` except that it accepts `iteratee` which is * invoked for each element in `array` to generate the criterion by which - * uniqueness is computed. The iteratee is invoked with one argument: (value). + * uniqueness is computed. The order of result values is determined by the + * order they occur in the array. The iteratee is invoked with one argument: + * (value). * * @static * @memberOf _ @@ -40922,8 +41194,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.uniq` except that it accepts `comparator` which - * is invoked to compare elements of `array`. The comparator is invoked with - * two arguments: (arrVal, othVal). + * is invoked to compare elements of `array`. The order of result values is + * determined by the order they occur in the array.The comparator is invoked + * with two arguments: (arrVal, othVal). * * @static * @memberOf _ @@ -41065,8 +41338,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.xor` except that it accepts `iteratee` which is * invoked for each element of each `arrays` to generate the criterion by - * which by which they're compared. The iteratee is invoked with one argument: - * (value). + * which by which they're compared. The order of result values is determined + * by the order they occur in the arrays. The iteratee is invoked with one + * argument: (value). * * @static * @memberOf _ @@ -41095,8 +41369,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.xor` except that it accepts `comparator` which is - * invoked to compare elements of `arrays`. The comparator is invoked with - * two arguments: (arrVal, othVal). + * invoked to compare elements of `arrays`. The order of result values is + * determined by the order they occur in the arrays. The comparator is invoked + * with two arguments: (arrVal, othVal). * * @static * @memberOf _ @@ -41313,8 +41588,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _(object).at(['a[0].b.c', 'a[1]']).value(); * // => [3, 4] */ - var wrapperAt = baseRest(function(paths) { - paths = baseFlatten(paths, 1); + var wrapperAt = flatRest(function(paths) { var length = paths.length, start = length ? paths[0] : 0, value = this.__wrapped__, @@ -41579,7 +41853,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => { '3': 2, '5': 1 } */ var countBy = createAggregator(function(result, value, key) { - hasOwnProperty.call(result, key) ? ++result[key] : (result[key] = 1); + if (hasOwnProperty.call(result, key)) { + ++result[key]; + } else { + baseAssignValue(result, key, 1); + } }); /** @@ -41834,7 +42112,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @see _.forEachRight * @example * - * _([1, 2]).forEach(function(value) { + * _.forEach([1, 2], function(value) { * console.log(value); * }); * // => Logs `1` then `2`. @@ -41902,7 +42180,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (hasOwnProperty.call(result, key)) { result[key].push(value); } else { - result[key] = [value]; + baseAssignValue(result, key, [value]); } }); @@ -42015,7 +42293,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } */ var keyBy = createAggregator(function(result, value, key) { - result[key] = value; + baseAssignValue(result, key, value); }); /** @@ -42275,10 +42553,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => 2 */ function sample(collection) { - var array = isArrayLike(collection) ? collection : values(collection), - length = array.length; - - return length > 0 ? array[baseRandom(0, length - 1)] : undefined; + var func = isArray(collection) ? arraySample : baseSample; + return func(collection); } /** @@ -42302,25 +42578,13 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => [2, 3, 1] */ function sampleSize(collection, n, guard) { - var index = -1, - result = toArray(collection), - length = result.length, - lastIndex = length - 1; - if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) { n = 1; } else { - n = baseClamp(toInteger(n), 0, length); + n = toInteger(n); } - while (++index < n) { - var rand = baseRandom(index, lastIndex), - value = result[rand]; - - result[rand] = result[index]; - result[index] = value; - } - result.length = n; - return result; + var func = isArray(collection) ? arraySampleSize : baseSampleSize; + return func(collection, n); } /** @@ -42339,7 +42603,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => [4, 1, 3, 2] */ function shuffle(collection) { - return sampleSize(collection, MAX_ARRAY_LENGTH); + var func = isArray(collection) ? arrayShuffle : baseShuffle; + return func(collection); } /** @@ -42444,16 +42709,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * { 'user': 'barney', 'age': 34 } * ]; * - * _.sortBy(users, function(o) { return o.user; }); + * _.sortBy(users, [function(o) { return o.user; }]); * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] * * _.sortBy(users, ['user', 'age']); * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]] - * - * _.sortBy(users, 'user', function(o) { - * return Math.floor(o.age / 10); - * }); - * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] */ var sortBy = baseRest(function(collection, iteratees) { if (collection == null) { @@ -42968,7 +43228,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.defer(function(text) { * console.log(text); * }, 'deferred'); - * // => Logs 'deferred' after one or more milliseconds. + * // => Logs 'deferred' after one millisecond. */ var defer = baseRest(function(func, args) { return baseDelay(func, 1, args); @@ -43076,14 +43336,14 @@ function ngViewFillContentFactory($compile, $controller, $route) { return cache.get(key); } var result = func.apply(this, args); - memoized.cache = cache.set(key, result); + memoized.cache = cache.set(key, result) || cache; return result; }; memoized.cache = new (memoize.Cache || MapCache); return memoized; } - // Assign cache to `_.memoize`. + // Expose `MapCache`. memoize.Cache = MapCache; /** @@ -43175,7 +43435,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * func(10, 5); * // => [100, 10] */ - var overArgs = baseRest(function(func, transforms) { + var overArgs = castRest(function(func, transforms) { transforms = (transforms.length == 1 && isArray(transforms[0])) ? arrayMap(transforms[0], baseUnary(getIteratee())) : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee())); @@ -43289,8 +43549,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * rearged('b', 'c', 'a') * // => ['a', 'b', 'c'] */ - var rearg = baseRest(function(func, indexes) { - return createWrap(func, REARG_FLAG, undefined, undefined, undefined, baseFlatten(indexes, 1)); + var rearg = flatRest(function(func, indexes) { + return createWrap(func, REARG_FLAG, undefined, undefined, undefined, indexes); }); /** @@ -43780,11 +44040,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.isArguments([1, 2, 3]); * // => false */ - function isArguments(value) { - // Safari 8.1 makes `arguments.callee` enumerable in strict mode. - return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && - (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); - } + var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) { + return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && + !propertyIsEnumerable.call(value, 'callee'); + }; /** * Checks if `value` is classified as an `Array` object. @@ -43966,7 +44225,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => false */ function isElement(value) { - return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value); + return value != null && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value); } /** @@ -44004,16 +44263,16 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function isEmpty(value) { if (isArrayLike(value) && - (isArray(value) || typeof value == 'string' || - typeof value.splice == 'function' || isBuffer(value) || isArguments(value))) { + (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || + isBuffer(value) || isTypedArray(value) || isArguments(value))) { return !value.length; } var tag = getTag(value); if (tag == mapTag || tag == setTag) { return !value.size; } - if (nonEnumShadows || isPrototype(value)) { - return !nativeKeys(value).length; + if (isPrototype(value)) { + return !baseKeys(value).length; } for (var key in value) { if (hasOwnProperty.call(value, key)) { @@ -44168,9 +44427,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function isFunction(value) { // The use of `Object#toString` avoids issues with the `typeof` operator - // in Safari 8-9 which returns 'object' for typed array and other constructors. + // in Safari 9 which returns 'object' for typed array and other constructors. var tag = isObject(value) ? objectToString.call(value) : ''; - return tag == funcTag || tag == genTag; + return tag == funcTag || tag == genTag || tag == proxyTag; } /** @@ -44261,7 +44520,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function isObject(value) { var type = typeof value; - return !!value && (type == 'object' || type == 'function'); + return value != null && (type == 'object' || type == 'function'); } /** @@ -44289,7 +44548,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => false */ function isObjectLike(value) { - return !!value && typeof value == 'object'; + return value != null && typeof value == 'object'; } /** @@ -44443,7 +44702,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function isNative(value) { if (isMaskable(value)) { - throw new Error('This method is not supported with core-js. Try https://github.com/es-shims.'); + throw new Error(CORE_ERROR_TEXT); } return baseIsNative(value); } @@ -44553,8 +44812,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => true */ function isPlainObject(value) { - if (!isObjectLike(value) || - objectToString.call(value) != objectTag || isHostObject(value)) { + if (!isObjectLike(value) || objectToString.call(value) != objectTag) { return false; } var proto = getPrototype(value); @@ -45059,8 +45317,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @memberOf _ * @since 4.0.0 * @category Lang - * @param {*} value The value to process. - * @returns {string} Returns the string. + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. * @example * * _.toString(null); @@ -45111,7 +45369,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => { 'a': 1, 'c': 3 } */ var assign = createAssigner(function(object, source) { - if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) { + if (isPrototype(source) || isArrayLike(source)) { copyObject(source, keys(source), object); return; } @@ -45239,9 +45497,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.at(object, ['a[0].b.c', 'a[1]']); * // => [3, 4] */ - var at = baseRest(function(object, paths) { - return baseAt(object, baseFlatten(paths, 1)); - }); + var at = flatRest(baseAt); /** * Creates an object that inherits from the `prototype` object. If a @@ -45844,7 +46100,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { iteratee = getIteratee(iteratee, 3); baseForOwn(object, function(value, key, object) { - result[iteratee(value, key, object)] = value; + baseAssignValue(result, iteratee(value, key, object), value); }); return result; } @@ -45882,7 +46138,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { iteratee = getIteratee(iteratee, 3); baseForOwn(object, function(value, key, object) { - result[key] = iteratee(value, key, object); + baseAssignValue(result, key, iteratee(value, key, object)); }); return result; } @@ -45926,7 +46182,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * This method is like `_.merge` except that it accepts `customizer` which * is invoked to produce the merged values of the destination and source * properties. If `customizer` returns `undefined`, merging is handled by the - * method instead. The `customizer` is invoked with seven arguments: + * method instead. The `customizer` is invoked with six arguments: * (objValue, srcValue, key, object, source, stack). * * **Note:** This method mutates `object`. @@ -45976,11 +46232,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.omit(object, ['a', 'c']); * // => { 'b': '2' } */ - var omit = baseRest(function(object, props) { + var omit = flatRest(function(object, props) { if (object == null) { return {}; } - props = arrayMap(baseFlatten(props, 1), toKey); + props = arrayMap(props, toKey); return basePick(object, baseDifference(getAllKeysIn(object), props)); }); @@ -46025,8 +46281,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.pick(object, ['a', 'c']); * // => { 'a': 1, 'c': 3 } */ - var pick = baseRest(function(object, props) { - return object == null ? {} : basePick(object, arrayMap(baseFlatten(props, 1), toKey)); + var pick = flatRest(function(object, props) { + return object == null ? {} : basePick(object, arrayMap(props, toKey)); }); /** @@ -46246,22 +46502,23 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => { '1': ['a', 'c'], '2': ['b'] } */ function transform(object, iteratee, accumulator) { - var isArr = isArray(object) || isTypedArray(object); - iteratee = getIteratee(iteratee, 4); + var isArr = isArray(object), + isArrLike = isArr || isBuffer(object) || isTypedArray(object); + iteratee = getIteratee(iteratee, 4); if (accumulator == null) { - if (isArr || isObject(object)) { - var Ctor = object.constructor; - if (isArr) { - accumulator = isArray(object) ? new Ctor : []; - } else { - accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; - } - } else { + var Ctor = object && object.constructor; + if (isArrLike) { + accumulator = isArr ? new Ctor : []; + } + else if (isObject(object)) { + accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; + } + else { accumulator = {}; } } - (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) { + (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) { return iteratee(accumulator, value, index, object); }); return accumulator; @@ -46680,8 +46937,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { } /** - * Converts the characters "&", "<", ">", '"', "'", and "\`" in `string` to - * their corresponding HTML entities. + * Converts the characters "&", "<", ">", '"', and "'" in `string` to their + * corresponding HTML entities. * * **Note:** No other characters are escaped. To escape additional * characters use a third-party library like [_he_](https://mths.be/he). @@ -46692,12 +46949,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) * (under "semi-related fun fact") for more details. * - * Backticks are escaped because in IE < 9, they can break out of - * attribute values or HTML comments. See [#59](https://html5sec.org/#59), - * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and - * [#133](https://html5sec.org/#133) of the - * [HTML5 Security Cheatsheet](https://html5sec.org/) for more details. - * * When working with HTML you should always * [quote attribute values](http://wonko.com/post/html-escaping) to reduce * XSS vectors. @@ -46940,15 +47191,12 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => [6, 8, 10] */ function parseInt(string, radix, guard) { - // Chrome fails to trim leading whitespace characters. - // See https://bugs.chromium.org/p/v8/issues/detail?id=3109 for more details. if (guard || radix == null) { radix = 0; } else if (radix) { radix = +radix; } - string = toString(string).replace(reTrim, ''); - return nativeParseInt(string, radix || (reHasHexPrefix.test(string) ? 16 : 10)); + return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0); } /** @@ -47187,7 +47435,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * compiled({ 'user': 'barney' }); * // => 'hello barney!' * - * // Use the ES delimiter as an alternative to the default "interpolate" delimiter. + * // Use the ES template literal delimiter as an "interpolate" delimiter. + * // Disable support by replacing the "interpolate" delimiter. * var compiled = _.template('hello ${ user }!'); * compiled({ 'user': 'pebbles' }); * // => 'hello pebbles!' @@ -47588,7 +47837,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * The inverse of `_.escape`; this method converts the HTML entities - * `&`, `<`, `>`, `"`, `'`, and ``` in `string` to + * `&`, `<`, `>`, `"`, and `'` in `string` to * their corresponding characters. * * **Note:** No other HTML entities are unescaped. To unescape additional @@ -47742,10 +47991,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { * jQuery(element).on('click', view.click); * // => Logs 'clicked docs' when clicked. */ - var bindAll = baseRest(function(object, methodNames) { - arrayEach(baseFlatten(methodNames, 1), function(key) { + var bindAll = flatRest(function(object, methodNames) { + arrayEach(methodNames, function(key) { key = toKey(key); - object[key] = bind(object[key], object); + baseAssignValue(object, key, bind(object[key], object)); }); return object; }); @@ -49536,7 +49785,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { lodash.prototype[iteratorSymbol] = wrapperToIterator; } return lodash; - } + }); /*--------------------------------------------------------------------------*/ @@ -49569,3 +49818,861 @@ function ngViewFillContentFactory($compile, $controller, $route) { root._ = _; } }.call(this)); + +/** + * @license MIT + * @fileOverview Favico animations + * @author Miroslav Magda, http://blog.ejci.net + * @version 0.3.10 + */ + +/** + * Create new favico instance + * @param {Object} Options + * @return {Object} Favico object + * @example + * var favico = new Favico({ + * bgColor : '#d00', + * textColor : '#fff', + * fontFamily : 'sans-serif', + * fontStyle : 'bold', + * position : 'down', + * type : 'circle', + * animation : 'slide', + * dataUrl: function(url){}, + * win: top + * }); + */ +(function () { + + var Favico = (function (opt) { + 'use strict'; + opt = (opt) ? opt : {}; + var _def = { + bgColor: '#d00', + textColor: '#fff', + fontFamily: 'sans-serif', //Arial,Verdana,Times New Roman,serif,sans-serif,... + fontStyle: 'bold', //normal,italic,oblique,bold,bolder,lighter,100,200,300,400,500,600,700,800,900 + type: 'circle', + position: 'down', // down, up, left, leftup (upleft) + animation: 'slide', + elementId: false, + dataUrl: false, + win: window + }; + var _opt, _orig, _h, _w, _canvas, _context, _img, _ready, _lastBadge, _running, _readyCb, _stop, _browser, _animTimeout, _drawTimeout, _doc; + + _browser = {}; + _browser.ff = typeof InstallTrigger != 'undefined'; + _browser.chrome = !!window.chrome; + _browser.opera = !!window.opera || navigator.userAgent.indexOf('Opera') >= 0; + _browser.ie = /*@cc_on!@*/false; + _browser.safari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; + _browser.supported = (_browser.chrome || _browser.ff || _browser.opera); + + var _queue = []; + _readyCb = function () { + }; + _ready = _stop = false; + /** + * Initialize favico + */ + var init = function () { + //merge initial options + _opt = merge(_def, opt); + _opt.bgColor = hexToRgb(_opt.bgColor); + _opt.textColor = hexToRgb(_opt.textColor); + _opt.position = _opt.position.toLowerCase(); + _opt.animation = (animation.types['' + _opt.animation]) ? _opt.animation : _def.animation; + + _doc = _opt.win.document; + + var isUp = _opt.position.indexOf('up') > -1; + var isLeft = _opt.position.indexOf('left') > -1; + + //transform animation + if (isUp || isLeft) { + for (var i = 0; i < animation.types['' + _opt.animation].length; i++) { + var step = animation.types['' + _opt.animation][i]; + + if (isUp) { + if (step.y < 0.6) { + step.y = step.y - 0.4; + } else { + step.y = step.y - 2 * step.y + (1 - step.w); + } + } + + if (isLeft) { + if (step.x < 0.6) { + step.x = step.x - 0.4; + } else { + step.x = step.x - 2 * step.x + (1 - step.h); + } + } + + animation.types['' + _opt.animation][i] = step; + } + } + _opt.type = (type['' + _opt.type]) ? _opt.type : _def.type; + + _orig = link.getIcon(); + //create temp canvas + _canvas = document.createElement('canvas'); + //create temp image + _img = document.createElement('img'); + if (_orig.hasAttribute('href')) { + _img.setAttribute('crossOrigin', 'anonymous'); + //get width/height + _img.onload = function () { + _h = (_img.height > 0) ? _img.height : 32; + _w = (_img.width > 0) ? _img.width : 32; + _canvas.height = _h; + _canvas.width = _w; + _context = _canvas.getContext('2d'); + icon.ready(); + }; + _img.setAttribute('src', _orig.getAttribute('href')); + } else { + _img.onload = function () { + _h = 32; + _w = 32; + _img.height = _h; + _img.width = _w; + _canvas.height = _h; + _canvas.width = _w; + _context = _canvas.getContext('2d'); + icon.ready(); + }; + _img.setAttribute('src', ''); + } + + }; + /** + * Icon namespace + */ + var icon = {}; + /** + * Icon is ready (reset icon) and start animation (if ther is any) + */ + icon.ready = function () { + _ready = true; + icon.reset(); + _readyCb(); + }; + /** + * Reset icon to default state + */ + icon.reset = function () { + //reset + if (!_ready) { + return; + } + _queue = []; + _lastBadge = false; + _running = false; + _context.clearRect(0, 0, _w, _h); + _context.drawImage(_img, 0, 0, _w, _h); + //_stop=true; + link.setIcon(_canvas); + //webcam('stop'); + //video('stop'); + window.clearTimeout(_animTimeout); + window.clearTimeout(_drawTimeout); + }; + /** + * Start animation + */ + icon.start = function () { + if (!_ready || _running) { + return; + } + var finished = function () { + _lastBadge = _queue[0]; + _running = false; + if (_queue.length > 0) { + _queue.shift(); + icon.start(); + } else { + + } + }; + if (_queue.length > 0) { + _running = true; + var run = function () { + // apply options for this animation + ['type', 'animation', 'bgColor', 'textColor', 'fontFamily', 'fontStyle'].forEach(function (a) { + if (a in _queue[0].options) { + _opt[a] = _queue[0].options[a]; + } + }); + animation.run(_queue[0].options, function () { + finished(); + }, false); + }; + if (_lastBadge) { + animation.run(_lastBadge.options, function () { + run(); + }, true); + } else { + run(); + } + } + }; + + /** + * Badge types + */ + var type = {}; + var options = function (opt) { + opt.n = ((typeof opt.n) === 'number') ? Math.abs(opt.n | 0) : opt.n; + opt.x = _w * opt.x; + opt.y = _h * opt.y; + opt.w = _w * opt.w; + opt.h = _h * opt.h; + opt.len = ("" + opt.n).length; + return opt; + }; + /** + * Generate circle + * @param {Object} opt Badge options + */ + type.circle = function (opt) { + opt = options(opt); + var more = false; + if (opt.len === 2) { + opt.x = opt.x - opt.w * 0.4; + opt.w = opt.w * 1.4; + more = true; + } else if (opt.len >= 3) { + opt.x = opt.x - opt.w * 0.65; + opt.w = opt.w * 1.65; + more = true; + } + _context.clearRect(0, 0, _w, _h); + _context.drawImage(_img, 0, 0, _w, _h); + _context.beginPath(); + _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.85 : 1)) + "px " + _opt.fontFamily; + _context.textAlign = 'center'; + if (more) { + _context.moveTo(opt.x + opt.w / 2, opt.y); + _context.lineTo(opt.x + opt.w - opt.h / 2, opt.y); + _context.quadraticCurveTo(opt.x + opt.w, opt.y, opt.x + opt.w, opt.y + opt.h / 2); + _context.lineTo(opt.x + opt.w, opt.y + opt.h - opt.h / 2); + _context.quadraticCurveTo(opt.x + opt.w, opt.y + opt.h, opt.x + opt.w - opt.h / 2, opt.y + opt.h); + _context.lineTo(opt.x + opt.h / 2, opt.y + opt.h); + _context.quadraticCurveTo(opt.x, opt.y + opt.h, opt.x, opt.y + opt.h - opt.h / 2); + _context.lineTo(opt.x, opt.y + opt.h / 2); + _context.quadraticCurveTo(opt.x, opt.y, opt.x + opt.h / 2, opt.y); + } else { + _context.arc(opt.x + opt.w / 2, opt.y + opt.h / 2, opt.h / 2, 0, 2 * Math.PI); + } + _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')'; + _context.fill(); + _context.closePath(); + _context.beginPath(); + _context.stroke(); + _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')'; + //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); + if ((typeof opt.n) === 'number' && opt.n > 999) { + _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2)); + } else { + _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); + } + _context.closePath(); + }; + /** + * Generate rectangle + * @param {Object} opt Badge options + */ + type.rectangle = function (opt) { + opt = options(opt); + var more = false; + if (opt.len === 2) { + opt.x = opt.x - opt.w * 0.4; + opt.w = opt.w * 1.4; + more = true; + } else if (opt.len >= 3) { + opt.x = opt.x - opt.w * 0.65; + opt.w = opt.w * 1.65; + more = true; + } + _context.clearRect(0, 0, _w, _h); + _context.drawImage(_img, 0, 0, _w, _h); + _context.beginPath(); + _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.9 : 1)) + "px " + _opt.fontFamily; + _context.textAlign = 'center'; + _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')'; + _context.fillRect(opt.x, opt.y, opt.w, opt.h); + _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')'; + //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); + if ((typeof opt.n) === 'number' && opt.n > 999) { + _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2)); + } else { + _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); + } + _context.closePath(); + }; + + /** + * Set badge + */ + var badge = function (number, opts) { + opts = ((typeof opts) === 'string' ? { + animation: opts + } : opts) || {}; + _readyCb = function () { + try { + if (typeof (number) === 'number' ? (number > 0) : (number !== '')) { + var q = { + type: 'badge', + options: { + n: number + } + }; + if ('animation' in opts && animation.types['' + opts.animation]) { + q.options.animation = '' + opts.animation; + } + if ('type' in opts && type['' + opts.type]) { + q.options.type = '' + opts.type; + } + ['bgColor', 'textColor'].forEach(function (o) { + if (o in opts) { + q.options[o] = hexToRgb(opts[o]); + } + }); + ['fontStyle', 'fontFamily'].forEach(function (o) { + if (o in opts) { + q.options[o] = opts[o]; + } + }); + _queue.push(q); + if (_queue.length > 100) { + throw new Error('Too many badges requests in queue.'); + } + icon.start(); + } else { + icon.reset(); + } + } catch (e) { + throw new Error('Error setting badge. Message: ' + e.message); + } + }; + if (_ready) { + _readyCb(); + } + }; + + /** + * Set image as icon + */ + var image = function (imageElement) { + _readyCb = function () { + try { + var w = imageElement.width; + var h = imageElement.height; + var newImg = document.createElement('img'); + var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h); + newImg.setAttribute('crossOrigin', 'anonymous'); + newImg.onload=function(){ + _context.clearRect(0, 0, _w, _h); + _context.drawImage(newImg, 0, 0, _w, _h); + link.setIcon(_canvas); + }; + newImg.setAttribute('src', imageElement.getAttribute('src')); + newImg.height = (h / ratio); + newImg.width = (w / ratio); + } catch (e) { + throw new Error('Error setting image. Message: ' + e.message); + } + }; + if (_ready) { + _readyCb(); + } + }; + /** + * Set video as icon + */ + var video = function (videoElement) { + _readyCb = function () { + try { + if (videoElement === 'stop') { + _stop = true; + icon.reset(); + _stop = false; + return; + } + //var w = videoElement.width; + //var h = videoElement.height; + //var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h); + videoElement.addEventListener('play', function () { + drawVideo(this); + }, false); + + } catch (e) { + throw new Error('Error setting video. Message: ' + e.message); + } + }; + if (_ready) { + _readyCb(); + } + }; + /** + * Set video as icon + */ + var webcam = function (action) { + //UR + if (!window.URL || !window.URL.createObjectURL) { + window.URL = window.URL || {}; + window.URL.createObjectURL = function (obj) { + return obj; + }; + } + if (_browser.supported) { + var newVideo = false; + navigator.getUserMedia = navigator.getUserMedia || navigator.oGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia; + _readyCb = function () { + try { + if (action === 'stop') { + _stop = true; + icon.reset(); + _stop = false; + return; + } + newVideo = document.createElement('video'); + newVideo.width = _w; + newVideo.height = _h; + navigator.getUserMedia({ + video: true, + audio: false + }, function (stream) { + newVideo.src = URL.createObjectURL(stream); + newVideo.play(); + drawVideo(newVideo); + }, function () { + }); + } catch (e) { + throw new Error('Error setting webcam. Message: ' + e.message); + } + }; + if (_ready) { + _readyCb(); + } + } + + }; + + /** + * Draw video to context and repeat :) + */ + function drawVideo(video) { + if (video.paused || video.ended || _stop) { + return false; + } + //nasty hack for FF webcam (Thanks to Julian Ćwirko, kontakt@redsunmedia.pl) + try { + _context.clearRect(0, 0, _w, _h); + _context.drawImage(video, 0, 0, _w, _h); + } catch (e) { + + } + _drawTimeout = setTimeout(function () { + drawVideo(video); + }, animation.duration); + link.setIcon(_canvas); + } + + var link = {}; + /** + * Get icon from HEAD tag or create a new element + */ + link.getIcon = function () { + var elm = false; + //get link element + var getLink = function () { + var link = _doc.getElementsByTagName('head')[0].getElementsByTagName('link'); + for (var l = link.length, i = (l - 1); i >= 0; i--) { + if ((/(^|\s)icon(\s|$)/i).test(link[i].getAttribute('rel'))) { + return link[i]; + } + } + return false; + }; + if (_opt.element) { + elm = _opt.element; + } else if (_opt.elementId) { + //if img element identified by elementId + elm = _doc.getElementById(_opt.elementId); + elm.setAttribute('href', elm.getAttribute('src')); + } else { + //if link element + elm = getLink(); + if (elm === false) { + elm = _doc.createElement('link'); + elm.setAttribute('rel', 'icon'); + _doc.getElementsByTagName('head')[0].appendChild(elm); + } + } + elm.setAttribute('type', 'image/png'); + return elm; + }; + link.setIcon = function (canvas) { + var url = canvas.toDataURL('image/png'); + if (_opt.dataUrl) { + //if using custom exporter + _opt.dataUrl(url); + } + if (_opt.element) { + _opt.element.setAttribute('href', url); + _opt.element.setAttribute('src', url); + } else if (_opt.elementId) { + //if is attached to element (image) + var elm = _doc.getElementById(_opt.elementId); + elm.setAttribute('href', url); + elm.setAttribute('src', url); + } else { + //if is attached to fav icon + if (_browser.ff || _browser.opera) { + //for FF we need to "recreate" element, atach to dom and remove old + //var originalType = _orig.getAttribute('rel'); + var old = _orig; + _orig = _doc.createElement('link'); + //_orig.setAttribute('rel', originalType); + if (_browser.opera) { + _orig.setAttribute('rel', 'icon'); + } + _orig.setAttribute('rel', 'icon'); + _orig.setAttribute('type', 'image/png'); + _doc.getElementsByTagName('head')[0].appendChild(_orig); + _orig.setAttribute('href', url); + if (old.parentNode) { + old.parentNode.removeChild(old); + } + } else { + _orig.setAttribute('href', url); + } + } + }; + + //http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb#answer-5624139 + //HEX to RGB convertor + function hexToRgb(hex) { + var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, function (m, r, g, b) { + return r + r + g + g + b + b; + }); + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : false; + } + + /** + * Merge options + */ + function merge(def, opt) { + var mergedOpt = {}; + var attrname; + for (attrname in def) { + mergedOpt[attrname] = def[attrname]; + } + for (attrname in opt) { + mergedOpt[attrname] = opt[attrname]; + } + return mergedOpt; + } + + /** + * Cross-browser page visibility shim + * http://stackoverflow.com/questions/12536562/detect-whether-a-window-is-visible + */ + function isPageHidden() { + return _doc.hidden || _doc.msHidden || _doc.webkitHidden || _doc.mozHidden; + } + + /** + * @namespace animation + */ + var animation = {}; + /** + * Animation "frame" duration + */ + animation.duration = 40; + /** + * Animation types (none,fade,pop,slide) + */ + animation.types = {}; + animation.types.fade = [{ + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.0 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.1 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.2 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.3 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.4 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.5 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.6 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.7 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.8 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.9 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1.0 + }]; + animation.types.none = [{ + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1 + }]; + animation.types.pop = [{ + x: 1, + y: 1, + w: 0, + h: 0, + o: 1 + }, { + x: 0.9, + y: 0.9, + w: 0.1, + h: 0.1, + o: 1 + }, { + x: 0.8, + y: 0.8, + w: 0.2, + h: 0.2, + o: 1 + }, { + x: 0.7, + y: 0.7, + w: 0.3, + h: 0.3, + o: 1 + }, { + x: 0.6, + y: 0.6, + w: 0.4, + h: 0.4, + o: 1 + }, { + x: 0.5, + y: 0.5, + w: 0.5, + h: 0.5, + o: 1 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1 + }]; + animation.types.popFade = [{ + x: 0.75, + y: 0.75, + w: 0, + h: 0, + o: 0 + }, { + x: 0.65, + y: 0.65, + w: 0.1, + h: 0.1, + o: 0.2 + }, { + x: 0.6, + y: 0.6, + w: 0.2, + h: 0.2, + o: 0.4 + }, { + x: 0.55, + y: 0.55, + w: 0.3, + h: 0.3, + o: 0.6 + }, { + x: 0.50, + y: 0.50, + w: 0.4, + h: 0.4, + o: 0.8 + }, { + x: 0.45, + y: 0.45, + w: 0.5, + h: 0.5, + o: 0.9 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1 + }]; + animation.types.slide = [{ + x: 0.4, + y: 1, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.9, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.9, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.8, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.7, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.6, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.5, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1 + }]; + /** + * Run animation + * @param {Object} opt Animation options + * @param {Object} cb Callabak after all steps are done + * @param {Object} revert Reverse order? true|false + * @param {Object} step Optional step number (frame bumber) + */ + animation.run = function (opt, cb, revert, step) { + var animationType = animation.types[isPageHidden() ? 'none' : _opt.animation]; + if (revert === true) { + step = (typeof step !== 'undefined') ? step : animationType.length - 1; + } else { + step = (typeof step !== 'undefined') ? step : 0; + } + cb = (cb) ? cb : function () { + }; + if ((step < animationType.length) && (step >= 0)) { + type[_opt.type](merge(opt, animationType[step])); + _animTimeout = setTimeout(function () { + if (revert) { + step = step - 1; + } else { + step = step + 1; + } + animation.run(opt, cb, revert, step); + }, animation.duration); + + link.setIcon(_canvas); + } else { + cb(); + return; + } + }; + //auto init + init(); + return { + badge: badge, + video: video, + image: image, + webcam: webcam, + reset: icon.reset, + browser: { + supported: _browser.supported + } + }; + }); + + // AMD / RequireJS + if (typeof define !== 'undefined' && define.amd) { + define([], function () { + return Favico; + }); + } + // CommonJS + else if (typeof module !== 'undefined' && module.exports) { + module.exports = Favico; + } + // included directly via