Merge branch 'develop'

This commit is contained in:
nicolargo 2015-05-20 10:14:12 +02:00
commit fd6c7a8934
122 changed files with 5836 additions and 4252 deletions

15
AUTHORS
View File

@ -5,25 +5,34 @@ Developers
Nicolas Hennion (aka) Nicolargo
http://blog.nicolargo.com
https://twitter.com/nicolargo
https://github.com/nicolargo
nicolashennion@gmail.com
PGP Fingerprint: 835F C447 3BCD 60E9 9200 2778 ABA4 D1AB 9731 6A3C
PGP Public key: gpg --keyserver pgp.mit.edu --recv-keys 0xaba4d1ab97316a3c
Alessio Sergi (aka) al3hex
Alessio Sergi (aka) Al3hex
https://twitter.com/al3hex
https://github.com/asergi
Brandon Philips (aka) Philips
http://ifup.org/
https://github.com/philips
Jon Renner (aka) Jrenner
https://github.com/jrenner
Maxime Desbrus (aka) desbma
Maxime Desbrus (aka) Desbma
https://github.com/desbma
Nicolas Hart (aka) NclsHart for the UI design
Nicolas Hart (aka) NclsHart (for the Web user interface)
https://github.com/nclsHart
Sylvain Mouquet (aka) SylvainMouquet (for the Web user interface)
http://github.com/sylvainmouquet
Floran Brutel (aka) notFloran (for the Web user interface)
https://github.com/notFloran
=========
Packagers
=========

View File

@ -3,11 +3,16 @@ include COPYING
include NEWS
include README.rst
include conf/glances.conf
include glances/outputs/bottle/*.tpl
include glances/outputs/static/html/*.html
include glances/outputs/static/html/components/*.html
include glances/outputs/static/html/plugins/*.html
include glances/outputs/static/*.ico
include glances/outputs/static/css/*.css
include glances/outputs/static/js/*.js
include glances/outputs/static/js/*.js.map
include glances/outputs/static/js/vendors/*.js
include glances/outputs/static/js/vendors/*.js.map
include glances/outputs/static/images/*.png
include man/glances.1
recursive-include docs images/*.png glances-doc.html
recursive-include glances *.py
recursive-include i18n *.mo

45
NEWS
View File

@ -2,10 +2,47 @@
Glances Version 2.x
==============================================================================
Version 2.4
===========
Changes:
* Glances doesn't provide a system-wide configuration file by default anymore.
Just copy it in any of the supported locations. See glances-doc.html for
more information. (issue #541)
* The default key bindings have been changed to:
- 'u': sort processes by USER
- 'U': show cumulative network I/O
* No more translations
Enhancements and new features:
* The Web user interface is now based on AngularJS (issue #473, #508, #468)
* Implement a 'quick look' plugin (issue #505)
* Add sort processes by USER (issue #531)
* Add a new IP information plugin (issue #509)
* Add RabbitMQ export module (issue #540 Thk to @Katyucha)
* Add a quiet mode (-q), can be useful using with export module
* Grab FAN speed in the Glances sensors plugin (issue #501)
* Allow logical mounts points in the FS plugin (issue #448)
* Add a --disable-hddtemp to disable HDD temperature module at startup (issue #515)
* Increase alert minimal delay to 6 seconds (issue #522)
* If the Curses application raises an exception, restore the terminal correctly (issue #537)
Bugs corrected:
* Monitor list, all processes are take into account (issue #507)
* Duplicated --enable-history in the doc (issue #511)
* Sensors title is displayed if no sensors are detected (issue #510)
* Server mode issue when no network interface is available (issue #528)
* DEBUG mode activated by default with Python 2.6 (issue #512)
* Glances display of time trims the hours showing only minutes and seconds (issue #543)
* Process list header not decorating when sorting by command (issue #551)
Version 2.3
===========
Enhancements and news features:
Enhancements and new features:
* Add the Docker plugin (issue #440) with per container CPU and memory monitoring (issue #490)
* Add the RAID plugin (issue #447)
@ -37,7 +74,7 @@ Version 2.2.1
Version 2.2
===========
Enhancements and news features:
Enhancements and new features:
* Add centralized curse interface with a Glances servers list to monitor (issue #418)
* Add processes tree view (--tree) (issue #444)
@ -142,7 +179,7 @@ Version 2.0
===========
Glances v2.0 is not a simple upgrade of the version 1.x but a complete code refactoring.
Based on a plugins system, it aims at providing an easy way to add news features.
Based on a plugins system, it aims at providing an easy way to add new features.
- Core defines the basics and commons functions.
- all stats are grabbed through plugins (see the glances/plugins source folder).
- also outputs methods (Curse, Web mode, CSV) are managed as plugins.
@ -348,7 +385,7 @@ Version 1.4.2.1
Version 1.4.2
=============
* Use the news virtual_memory() and virtual_swap() fct (PsUtil)
* Use the new virtual_memory() and virtual_swap() fct (PsUtil)
* Display "Top process" in logs
* Minor patch on man page for Debian packaging
* Code optimization (less try and except)

View File

@ -6,11 +6,11 @@ Glances - An eye on your system
:target: https://flattr.com/thing/484466/nicolargoglances-on-GitHub
.. image:: https://scrutinizer-ci.com/g/nicolargo/glances/badges/quality-score.png?b=master
:target: https://scrutinizer-ci.com/g/nicolargo/glances/
.. image:: https://travis-ci.org/nicolargo/glances.png?branch=master
.. image:: https://travis-ci.org/nicolargo/glances.svg?branch=master
:target: https://travis-ci.org/nicolargo/glances
.. image:: https://badge.fury.io/py/Glances.png
.. image:: https://badge.fury.io/py/Glances.svg
:target: http://badge.fury.io/py/Glances
.. image:: https://pypip.in/d/Glances/badge.png
.. image:: https://pypip.in/d/Glances/badge.svg
:target: https://pypi.python.org/pypi/Glances/
:alt: Downloads
@ -20,18 +20,16 @@ Follow Glances on Twitter: `@nicolargo`_ or `@glances_system`_
**Glances** is a cross-platform curses-based system monitoring tool
written in Python.
It uses the `psutil`_ library to get information from your system.
.. image:: https://raw.github.com/nicolargo/glances/master/docs/images/screenshot-wide.png
Requirements
============
- ``python >= 2.6`` (tested with version 2.6, 2.7, 3.3, 3.4)
- ``python >= 2.6`` or ``>= 3.3`` (tested with version 2.6, 2.7, 3.3, 3.4)
- ``psutil >= 2.0.0``
- ``setuptools``
Optionals dependencies:
Optional dependencies:
- ``bottle`` (for Web server mode)
- ``py3sensors`` (for hardware monitoring support) [Linux-only]
@ -39,11 +37,14 @@ Optionals dependencies:
- ``batinfo`` (for battery monitoring support) [Linux-only]
- ``pymdstat`` (for RAID support) [Linux-only]
- ``pysnmp`` (for SNMP support)
- ``zeroconf`` and ``netifaces`` (for the auto discoverer mode)
- ``zeroconf`` (for the autodiscover mode)
- ``netifaces`` (for the IP plugin)
- ``influxdb`` (for the InfluxDB export module)
- ``statsd`` (for the StatsD export module)
- ``pystache`` (for the action script feature)
- ``docker-py`` (for the Docker monitoring support) [Linux-only]
- ``matplotlib`` (for graphical/chart support)
- ``pika`` (for the RabbitMQ/ActiveMQ export module)
Installation
============
@ -51,7 +52,8 @@ Installation
Glances Auto Install script
---------------------------
To install both dependencies and latest Glances production ready version (aka *master* branch), just enter the following command line:
To install both dependencies and latest Glances production ready version
(aka *master* branch), just enter the following command line:
.. code-block:: console
@ -76,29 +78,30 @@ To install, simply use ``pip``:
.. code-block:: console
pip install Glances
pip install glances
*Note*: Python headers are required to install psutil. For example,
on Debian/Ubuntu you need to install first the *python-dev* package.
*Note 2*: You can also install the following libs in order to use optionnal features:
You can also install the following libraries in order to use optional
features:
.. code-block:: console
pip install bottle batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb statsd pystache
pip install bottle batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb statsd pystache pika
To upgrade Glances to the latest version:
.. code-block:: console
pip install --upgrade Glances
pip install --upgrade glances
If you need to install Glances in a specific user location, use:
.. code-block:: console
export PYTHONUSERBASE=~/mylocalpath
pip install --user Glances
pip install --user glances
GNU/Linux
---------
@ -106,11 +109,11 @@ GNU/Linux
At the moment, packages exist for the following GNU/Linux distributions:
- Arch Linux
- Debian (Testing/Sid)
- Debian
- Fedora/CentOS/RHEL
- Gentoo
- Slackware (SlackBuild)
- Ubuntu (13.04+)
- Ubuntu
- Void Linux
So you should be able to install it using your favorite package manager.
@ -142,7 +145,7 @@ Homebrew
.. code-block:: console
$ brew install python
$ pip install Glances
$ pip install glances
MacPorts
````````
@ -154,10 +157,15 @@ MacPorts
Windows
-------
- Install Python for Windows: http://www.python.org/getit/
- Install the psutil library: https://pypi.python.org/pypi?:action=display&name=psutil#downloads
- Install Python for Windows (Python 2.7.9+ ship with Pip): http://www.python.org/getit/
- Install the `psutil`_ library (latest binary version): https://pypi.python.org/pypi/psutil
- Install the colorconsole library: https://pypi.python.org/pypi/colorconsole
- Install Glances Download Glances from here: http://nicolargo.github.io/glances/
- Install optionnals dependencies (see list bellow)
- Install Glances using pip
.. code-block:: console
$ pip install glances
Source
------
@ -229,14 +237,14 @@ If you have any question (after RTFM!), please post it on the official Q&A `foru
Gateway to other services
=========================
Glances can export stats to: ``CSV`` file, ``InfluxDB`` and ``StatsD`` server.
Glances can export stats to: ``CSV`` file, ``InfluxDB``, ``StatsD`` and ``RabbitMQ`` server.
How to contribute ?
===================
If you want to contribute to the Glances project, read this `Wiki`_ page.
There is also a chat dedicated to the Glances' developpers:
There is also a chat dedicated to the Glances developers:
.. image:: https://badges.gitter.im/Join%20Chat.svg
:target: https://gitter.im/nicolargo/glances?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge

1140
conf/glances-grafana.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,174 +0,0 @@
[cpu]
# Default values if not defined: 50/70/90
user_careful=50
user_warning=70
user_critical=90
#user_log=False
user_critical_action=echo {{user}} {{value}} {{max}} > /tmp/cpu.alert
iowait_careful=50
iowait_warning=70
iowait_critical=90
system_careful=50
system_warning=70
system_critical=90
steal_careful=50
steal_warning=70
steal_critical=90
#steal_log=True
[percpu]
# Default values if not defined: 50/70/90
user_careful=50
user_warning=70
user_critical=90
iowait_careful=50
iowait_warning=70
iowait_critical=90
system_careful=50
system_warning=70
system_critical=90
[load]
# Value * number of cores
# Default values if not defined: 0.7/1.0/5.0 per number of cores
# Source: http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages
# http://www.linuxjournal.com/article/9001
careful=0.7
warning=1.0
critical=5.0
#log=False
[mem]
# Default limits for free RAM memory in %
# Default values if not defined: 50/70/90
careful=50
warning=70
critical=90
[memswap]
# Default limits for free swap memory in %
# Default values if not defined: 50/70/90
careful=50
warning=70
critical=90
[network]
# Define the list of hidden network interfaces (comma separeted)
hide=lo
# WLAN0 alias name
wlan0_alias=Wireless
# WLAN0 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
[diskio]
# Define the list of hidden disks (comma separeted)
hide=sda5
# Alias for sda1
#sda1_alias=IntDisk
# SDA1 limits (in bytes per second aka Bps) for interface bitrate
sda2_rx_careful=150000000
sda2_rx_warning=180000000
sda2_rx_critical=200000000
#sda2_rx_log=True
sda2_tx_careful=150000000
sda2_tx_warning=180000000
sda2_tx_critical=200000000
#sda2_tx_log=True
[fs]
# Default limits for free filesytem space in %
# Default values if not defined: 50/70/90
careful=50
careful_action=echo {{mnt_point}} {{used}}/{{size}} > /tmp/fs.alert
warning=70
critical=90
[sensors]
# Sensors core limits
# Default values if not defined: 60/70/80
temperature_core_careful=50
temperature_core_warning=70
temperature_core_critical=80
# Temperatures in °C for hddtemp
# Default values if not defined: 45/52/60
temperature_hdd_careful=45
temperature_hdd_warning=52
temperature_hdd_critical=60
# Battery % limits
battery_careful=80
battery_warning=90
battery_critical=95
# Sensors alias
temp1_alias=Motherboard 0
temp2_alias=Motherboard 1
core 0_alias=CPU Core 0
core 1_alias=CPU Core 1
[processlist]
# Limit values for CPU/MEM per process in %
# Default values if not defined: 50/70/90
cpu_careful=50
cpu_warning=70
cpu_critical=90
mem_careful=50
mem_warning=70
mem_critical=90
[monitor]
# Define the list of processes to monitor
# *** This section is optional ***
# The list is composed of items (list_#nb <= 10)
# An item is defined:
# * description: Description of the processes (max 16 chars)
# * regex: regular expression of the processes to monitor
# * command: (optional) full path to shell command/script for extended stat
# Use with caution. Should return a single line string.
# Only execute when at least one process is running
# By default display CPU and MEM %
# Limitation: Do not use in client / server mode
# * countmin: (optional) minimal number of processes
# A warning will be displayed if number of process < count
# * countmax: (optional) maximum number of processes
# A warning will be displayed if number of process > count
#list_1_description=Dropbox
#list_1_regex=.*dropbox.*
#list_1_countmin=1
#list_1_command=dropbox status | head -1
list_1_description=Python programs
list_1_regex=.*python.*
list_2_description=Famous Xeyes
list_2_regex=.*xeyes.*
list_2_countmin=1
[serverlist]
# Define the static server list
server_1_name=localhost
server_1_alias=My local PC
server_1_port=61209
server_2_name=localhost
server_2_port=61235
server_3_name=192.168.0.17
server_3_alias=Another PC on my network
server_3_port=61209
server_4_name=pasbon
server_4_port=61237
[influxdb]
host=localhost
port=8086
user=root
password=root
db=glances
[statsd]
host=localhost
port=8125
#prefix=glances

View File

@ -1,3 +1,14 @@
[quicklook]
cpu_careful=50
cpu_warning=70
cpu_critical=90
mem_careful=50
mem_warning=70
mem_critical=90
swap_careful=50
swap_warning=70
swap_critical=90
[cpu]
# Default values if not defined: 50/70/90
user_careful=50
@ -81,6 +92,8 @@ critical=90
careful=50
warning=70
critical=90
# Allow additionnals files types (comma-separated FS type)
#allow=zfs
[sensors]
# Sensors core limits
@ -133,11 +146,11 @@ mem_critical=90
#list_1_regex=.*dropbox.*
#list_1_countmin=1
#list_1_command=dropbox status | head -1
#list_1_description=Python programs
#list_1_regex=.*python.*
#list_2_description=Famous Xeyes
#list_2_regex=.*xeyes.*
#list_2_countmin=1
#list_2_description=Python programs
#list_2_regex=.*python.*
#list_3_description=Famous Xeyes
#list_3_regex=.*xeyes.*
#list_3_countmin=1
#[serverlist]
# Define the static server list
@ -158,8 +171,16 @@ port=8086
user=root
password=root
db=glances
#prefix=localhost
[statsd]
host=localhost
port=8125
#prefix=glances
[rabbitmq]
host=localhost
port=5672
user=guest
password=guest
queue=glances_queue

View File

@ -123,9 +123,9 @@ td.option-group {
<div class="document" id="glances">
<h1 class="title">Glances</h1>
<p>This manual describes <em>Glances</em> version 2.3.</p>
<p>This manual describes <em>Glances</em> version 2.4.</p>
<p>Copyright © 2011-2015 Nicolas Hennion &lt;<a class="reference external" href="mailto:nicolas&#64;nicolargo.com">nicolas&#64;nicolargo.com</a>&gt;</p>
<p>January 2015</p>
<p>May 2015</p>
<div class="contents topic" id="table-of-contents">
<p class="topic-title first">Table of Contents</p>
<ul class="simple">
@ -141,28 +141,39 @@ td.option-group {
<li><a class="reference internal" href="#interactive-commands" id="id12">Interactive Commands</a></li>
</ul>
</li>
<li><a class="reference internal" href="#configuration" id="id13">Configuration</a></li>
<li><a class="reference internal" href="#logs-and-debug-mode" id="id14">Logs and debug mode</a></li>
<li><a class="reference internal" href="#anatomy-of-the-application" id="id15">Anatomy Of The Application</a><ul>
<li><a class="reference internal" href="#legend" id="id16">Legend</a></li>
<li><a class="reference internal" href="#header" id="id17">Header</a></li>
<li><a class="reference internal" href="#cpu" id="id18">CPU</a></li>
<li><a class="reference internal" href="#load" id="id19">Load</a></li>
<li><a class="reference internal" href="#memory" id="id20">Memory</a></li>
<li><a class="reference internal" href="#network" id="id21">Network</a></li>
<li><a class="reference internal" href="#disk-i-o" id="id22">Disk I/O</a></li>
<li><a class="reference internal" href="#file-system" id="id23">File System</a></li>
<li><a class="reference internal" href="#sensors" id="id24">Sensors</a></li>
<li><a class="reference internal" href="#processes-list" id="id25">Processes List</a></li>
<li><a class="reference internal" href="#monitored-processes-list" id="id26">Monitored Processes List</a></li>
<li><a class="reference internal" href="#logs" id="id27">Logs</a></li>
<li><a class="reference internal" href="#docker" id="id28">Docker</a></li>
<li><a class="reference internal" href="#actions" id="id29">Actions</a></li>
<li><a class="reference internal" href="#configuration" id="id13">Configuration</a><ul>
<li><a class="reference internal" href="#location" id="id14">Location</a></li>
<li><a class="reference internal" href="#syntax" id="id15">Syntax</a></li>
</ul>
</li>
<li><a class="reference internal" href="#gateway-to-others-services" id="id30">Gateway to others services</a></li>
<li><a class="reference internal" href="#apis-documentations" id="id31">APIs Documentations</a></li>
<li><a class="reference internal" href="#support" id="id32">Support</a></li>
<li><a class="reference internal" href="#logs-and-debug-mode" id="id16">Logs and debug mode</a></li>
<li><a class="reference internal" href="#anatomy-of-the-application" id="id17">Anatomy Of The Application</a><ul>
<li><a class="reference internal" href="#legend" id="id18">Legend</a></li>
<li><a class="reference internal" href="#header" id="id19">Header</a></li>
<li><a class="reference internal" href="#quicklook" id="id20">QuickLook</a></li>
<li><a class="reference internal" href="#cpu" id="id21">CPU</a></li>
<li><a class="reference internal" href="#load" id="id22">Load</a></li>
<li><a class="reference internal" href="#memory" id="id23">Memory</a></li>
<li><a class="reference internal" href="#network" id="id24">Network</a></li>
<li><a class="reference internal" href="#disk-i-o" id="id25">Disk I/O</a></li>
<li><a class="reference internal" href="#file-system" id="id26">File System</a></li>
<li><a class="reference internal" href="#sensors" id="id27">Sensors</a></li>
<li><a class="reference internal" href="#processes-list" id="id28">Processes List</a></li>
<li><a class="reference internal" href="#monitored-processes-list" id="id29">Monitored Processes List</a></li>
<li><a class="reference internal" href="#logs" id="id30">Logs</a></li>
<li><a class="reference internal" href="#docker" id="id31">Docker</a></li>
<li><a class="reference internal" href="#actions" id="id32">Actions</a></li>
</ul>
</li>
<li><a class="reference internal" href="#gateway-to-others-services" id="id33">Gateway to others services</a><ul>
<li><a class="reference internal" href="#csv" id="id34">CSV</a></li>
<li><a class="reference internal" href="#influxdb" id="id35">InfluxDB</a></li>
<li><a class="reference internal" href="#statsd" id="id36">Statsd</a></li>
<li><a class="reference internal" href="#rabbitmq" id="id37">RabbitMQ</a></li>
</ul>
</li>
<li><a class="reference internal" href="#apis-documentation" id="id38">APIs documentation</a></li>
<li><a class="reference internal" href="#support" id="id39">Support</a></li>
</ul>
</div>
<div class="section" id="introduction">
@ -204,7 +215,12 @@ another one, called <tt class="docutils literal">client</tt>, just run on the se
<span class="generic output">client$ glances -c &#64;server</span>
</pre>
<p>where <tt class="docutils literal">&#64;server</tt> is the IP address or hostname of the server.</p>
<p>Glances can centralize available Glances servers using the <tt class="docutils literal"><span class="pre">--browser</span></tt> option. The server list can be staticaly defined in the Glances configuration file (section [serverlist]). Glances can also detect and display all Glances servers available on you network (auto discover mode is based on the the Zeroconf protocol only available on GNU/Linux and Mac OS X):</p>
<p>Glances can centralize available Glances servers using the <tt class="docutils literal"><span class="pre">--browser</span></tt>
option. The server list can be statically defined in the Glances
configuration file (section <tt class="docutils literal">[serverlist]</tt>).</p>
<p>Glances can also detect and display all Glances servers available on your
network (auto-discover mode is based on the the <tt class="docutils literal">zeroconf</tt> protocol,
which is only available on GNU/Linux and OS X):</p>
<pre class="code console literal-block">
<span class="generic output">client$ glances --browser</span>
</pre>
@ -223,7 +239,7 @@ client, the latter will try to grab stats using the <tt class="docutils literal"
<pre class="code console literal-block">
<span class="generic output">client$ glances -c &#64;snmpserver</span>
</pre>
<p>Note: Stats grabbed by SNMP request are limited (operating system dependent).</p>
<p><em>Note</em>: stats grabbed by SNMP request are limited (OS dependent).</p>
</div>
<div class="section" id="web-server-mode">
<h2><a class="toc-backref" href="#id9">Web Server Mode</a></h2>
@ -237,7 +253,11 @@ device with a web browser, just run the server with the <tt class="docutils lite
http://&#64;server:61208
</pre>
<p>where <tt class="docutils literal">&#64;server</tt> is the IP address or hostname of the server.</p>
<p>To change the refresh rate of the page, just add the period in seconds between refreshes at the end of the URL, ie. to refresh every 10s, use <tt class="docutils literal"><span class="pre">http://&#64;server:61208/10</span></tt>.</p>
<p>To change the refresh rate of the page, just add the period in seconds
at the end of the URL. For example, to refresh the page every 10s:</p>
<pre class="literal-block">
http://&#64;server:61208/10
</pre>
<p>The Glances web interface follows responsive web design principles.</p>
<p>Screenshot from Chrome on Android</p>
<img alt="images/screenshot-web2.png" src="images/screenshot-web2.png" />
@ -260,37 +280,45 @@ http://&#64;server:61208
<td>show program's version number and exit</td></tr>
<tr><td class="option-group">
<kbd><span class="option">-d</span>, <span class="option">--debug</span></kbd></td>
<td>Enable debug mode</td></tr>
<td>enable debug mode</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-C <var>CONF_FILE</var></span>, <span class="option">--config <var>CONF_FILE</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>path to the configuration file</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--enable-history</span></kbd></td>
<kbd><span class="option">--disable-network</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>enable the history mode</td></tr>
<tr><td>&nbsp;</td><td>disable network module</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--disable-bold</span></kbd></td>
<td>disable bold mode in the terminal</td></tr>
<kbd><span class="option">--disable-ip</span></kbd></td>
<td>disable IP module</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-diskio</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>disable disk I/O module</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--disable-fs</span></kbd></td>
<td>disable filesystem module</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-network</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>disable network module</td></tr>
<td>disable file system module</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-sensors</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>disable sensors module</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-hddtemp</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>disable hddtemp module</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--disable-raid</span></kbd></td>
<td>disable RAID module</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-docker</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>disable Docker module</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-left-sidebar</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>disable left sidebar</td></tr>
<tr><td>&nbsp;</td><td>disable network, disk I/O, file system and
sensors modules (py3sensors needed)</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-process</span></kbd></td>
</tr>
@ -299,29 +327,40 @@ http://&#64;server:61208
<kbd><span class="option">--disable-log</span></kbd></td>
<td>disable log module</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-quicklook</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>disable quick look module</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--disable-bold</span></kbd></td>
<td>disable bold mode in the terminal</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--enable-process-extended</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>enable extended stats on top process</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--enable-history</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>enable the history mode</td></tr>
<tr><td>&nbsp;</td><td>enable the history mode (matplotlib needed)</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--path-history <var>PATH_HISTORY</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>Set the export path for graph history</td></tr>
<tr><td>&nbsp;</td><td>set the export path for graph history</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--export-csv <var>CSV_FILE</var></span></kbd></td>
<kbd><span class="option">--export-csv <var>EXPORT_CSV</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>export stats to a CSV file</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--export-influxdb</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>export stats to an InfluxDB server</td></tr>
<tr><td>&nbsp;</td><td>export stats to an InfluxDB server (influxdb needed)</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--export-statsd</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>export stats to a Statsd server</td></tr>
<tr><td>&nbsp;</td><td>export stats to a StatsD server (statsd needed)</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--export-rabbitmq</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>export stats to a RabbitMQ server (pika needed)</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-c <var>CLIENT</var></span>, <span class="option">--client <var>CLIENT</var></span></kbd></td>
</tr>
@ -332,7 +371,7 @@ hostname</td></tr>
<td>run Glances in server mode</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--browser</span></kbd></td>
<td>run the Glances client browser (list of Glances server)</td></tr>
<td>start the client browser (list of Glances servers)</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-autodiscover</span></kbd></td>
</tr>
@ -345,18 +384,9 @@ hostname</td></tr>
<kbd><span class="option">-B <var>BIND_ADDRESS</var></span>, <span class="option">--bind <var>BIND_ADDRESS</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>bind server to the given IPv4/IPv6 address or hostname</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--password-badidea <var>PASSWORD_ARG</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>define password from the command line</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--password</span></kbd></td>
<td>define a client/server password from the prompt or
file</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-autodiscover</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>Hide Glances server from the auto discover feature</td></tr>
<td>define a client/server password</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--snmp-community <var>SNMP_COMMUNITY</var></span></kbd></td>
</tr>
@ -387,11 +417,14 @@ file</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-w</span>, <span class="option">--webserver</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>run Glances in web server mode</td></tr>
<tr><td>&nbsp;</td><td>run Glances in web server mode (bottle needed)</td></tr>
<tr><td class="option-group">
<kbd><span class="option">-q</span>, <span class="option">--quiet</span></kbd></td>
<td>do not display the curses interface</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-f <var>PROCESS_FILTER</var></span>, <span class="option">--process-filter <var>PROCESS_FILTER</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>set the process filter patern (regular expression)</td></tr>
<tr><td>&nbsp;</td><td>set the process filter pattern (regular expression)</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--process-short-name</span></kbd></td>
</tr>
@ -412,10 +445,10 @@ file</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--fs-free-space</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>display FS free space instead of used</td></tr>
<tr><td>&nbsp;</td><td>display file system free space instead of used</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--theme-white</span></kbd></td>
<td>optimize display for white background</td></tr>
<td>optimize display colors for white background</td></tr>
</tbody>
</table>
</blockquote>
@ -451,7 +484,7 @@ Filter is a regular expression pattern:</p>
<dt><tt class="docutils literal">f</tt></dt>
<dd>Show/hide file system stats</dd>
<dt><tt class="docutils literal">F</tt></dt>
<dd>Switch between FS used and free space</dd>
<dd>Switch between file system used and free space</dd>
<dt><tt class="docutils literal">g</tt></dt>
<dd>Generate graphs for current history</dd>
<dt><tt class="docutils literal">h</tt></dt>
@ -477,6 +510,8 @@ Filter is a regular expression pattern:</p>
<dt><tt class="docutils literal">T</tt></dt>
<dd>View network I/O as combination</dd>
<dt><tt class="docutils literal">u</tt></dt>
<dd>Sort processes by USER</dd>
<dt><tt class="docutils literal">U</tt></dt>
<dd>View cumulative network I/O</dd>
<dt><tt class="docutils literal">w</tt></dt>
<dd>Delete finished warning log messages</dd>
@ -488,10 +523,13 @@ Filter is a regular expression pattern:</p>
<dd>Switch between global CPU and per-CPU stats</dd>
<dt><tt class="docutils literal">2</tt></dt>
<dd>Enable/disable left sidebar</dd>
<dt><tt class="docutils literal">3</tt></dt>
<dd>Enable/disable the quick look module</dd>
<dt><tt class="docutils literal">/</tt></dt>
<dd>Switch between short name / command line (processes name)</dd>
</dl>
<p>In the Glances client browser (accessible through the --browser command line argument):</p>
<p>In the Glances client browser (accessible through the <tt class="docutils literal"><span class="pre">--browser</span></tt>
command line argument):</p>
<dl class="docutils">
<dt><tt class="docutils literal">ENTER</tt></dt>
<dd>Run Glances client to the selected server</dd>
@ -507,19 +545,24 @@ Filter is a regular expression pattern:</p>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id13">Configuration</a></h1>
<p>No configuration file is mandatory to use Glances.</p>
<p>Furthermore a configuration file is needed to set up limits, disks or
network interfaces to hide and/or monitored processes list or to define
alias.</p>
<p>By default, the configuration file is under:</p>
<p>Furthermore a configuration file is needed to modify limit alerts, to
set up monitored processes list, to hide disks or network interfaces or
to define alias.</p>
<div class="section" id="location">
<h2><a class="toc-backref" href="#id14">Location</a></h2>
<p>You can put the configuration file <tt class="docutils literal">glances.conf</tt> in the following
locations:</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">Linux:</th><td class="field-body"><tt class="docutils literal">/etc/glances/glances.conf</tt></td>
<tr class="field"><th class="field-name">Linux:</th><td class="field-body"><tt class="docutils literal"><span class="pre">~/.config/glances,</span> /etc/glances</tt></td>
</tr>
<tr class="field"><th class="field-name">*BSD and OS X:</th><td class="field-body"><tt class="docutils literal">/usr/local/etc/glances/glances.conf</tt></td>
<tr class="field"><th class="field-name">*BSD:</th><td class="field-body"><tt class="docutils literal"><span class="pre">~/.config/glances,</span> /usr/local/etc/glances</tt></td>
</tr>
<tr class="field"><th class="field-name">Windows:</th><td class="field-body"><tt class="docutils literal"><span class="pre">%APPDATA%\glances\glances.conf</span></tt></td>
<tr class="field"><th class="field-name">OS X:</th><td class="field-body"><tt class="docutils literal">~/Library/Application Support/glances, /usr/local/etc/glances</tt></td>
</tr>
<tr class="field"><th class="field-name">Windows:</th><td class="field-body"><tt class="docutils literal"><span class="pre">%APPDATA%\glances</span></tt></td>
</tr>
</tbody>
</table>
@ -530,20 +573,12 @@ C:\Documents and Settings\&lt;User&gt;\Application Data
<p>Since Windows Vista and newer versions:</p>
<pre class="literal-block">
C:\Users\&lt;User&gt;\AppData\Roaming
or
%userprofile%\AppData\Roaming
</pre>
<p>You can override the default configuration, located in one of the above
directories on your system, except for Windows.</p>
<p>Just copy the <tt class="docutils literal">glances.conf</tt> file to your <tt class="docutils literal">$XDG_CONFIG_HOME</tt> directory,
e.g., on Linux:</p>
<pre class="code console literal-block">
<span class="generic output">mkdir -p $XDG_CONFIG_HOME/glances
cp /usr/share/doc/glances/glances.conf $XDG_CONFIG_HOME/glances/</span>
</pre>
<p>On OS X, you should copy the configuration file to
<tt class="docutils literal">~/Library/Application Support/glances/</tt>.</p>
<p><em>Configuration file description</em></p>
<p>User-specific options override system-wide options and options given on
the command line override either.</p>
</div>
<div class="section" id="syntax">
<h2><a class="toc-backref" href="#id15">Syntax</a></h2>
<p>Each plugin and export module can have a section.</p>
<p>Example for the CPU plugin:</p>
<pre class="code literal-block">
@ -561,13 +596,15 @@ steal_careful=50
steal_warning=70
steal_critical=90
</pre>
<p>By default Steal CPU time alerts aren't logged. If you want to enable log/alert, just add:</p>
<p>By default the <tt class="docutils literal">steal</tt> CPU time alerts aren't logged. If you want to
enable log/alert, just add:</p>
<pre class="code literal-block">
steal_log=True
</pre>
</div>
</div>
<div class="section" id="logs-and-debug-mode">
<h1><a class="toc-backref" href="#id14">Logs and debug mode</a></h1>
<h1><a class="toc-backref" href="#id16">Logs and debug mode</a></h1>
<p>Glances logs all its internal messages to a log file. By default, only
INFO &amp; WARNING &amp; ERROR &amp;CRITICAL levels are logged, but DEBUG messages
can ben logged using the -d option on the command line.</p>
@ -576,19 +613,20 @@ can ben logged using the -d option on the command line.</p>
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name" colspan="2">Linux, *BSD and OS X:</th></tr>
<tr class="field"><th class="field-name" colspan="2">Linux, *BSD, OS X:</th></tr>
<tr class="field"><td>&nbsp;</td><td class="field-body"><tt class="docutils literal">/tmp/glances.log</tt></td>
</tr>
<tr class="field"><th class="field-name">Windows:</th><td class="field-body"><tt class="docutils literal"><span class="pre">%APPDATA%\Local\temp\glances.log</span></tt></td>
</tr>
</tbody>
</table>
<p>If glances.log is not writable, a new file will be created and returned to the user console.</p>
<p>If <tt class="docutils literal">glances.log</tt> is not writable, a new file will be created and
returned to the user console.</p>
</div>
<div class="section" id="anatomy-of-the-application">
<h1><a class="toc-backref" href="#id15">Anatomy Of The Application</a></h1>
<h1><a class="toc-backref" href="#id17">Anatomy Of The Application</a></h1>
<div class="section" id="legend">
<h2><a class="toc-backref" href="#id16">Legend</a></h2>
<h2><a class="toc-backref" href="#id18">Legend</a></h2>
<div class="line-block">
<div class="line"><tt class="docutils literal">GREEN</tt> stat counter is <tt class="docutils literal">&quot;OK&quot;</tt></div>
<div class="line"><tt class="docutils literal">BLUE</tt> stat counter is <tt class="docutils literal">&quot;CAREFUL&quot;</tt></div>
@ -599,7 +637,7 @@ can ben logged using the -d option on the command line.</p>
view.</p>
</div>
<div class="section" id="header">
<h2><a class="toc-backref" href="#id17">Header</a></h2>
<h2><a class="toc-backref" href="#id19">Header</a></h2>
<img alt="images/header.png" src="images/header.png" />
<p>The header shows the hostname, OS name, release version, platform
architecture and system uptime (on the upper right corner).
@ -610,8 +648,16 @@ Additionally, on GNU/Linux, it also shows the kernel version.</p>
<p>Disconnected:</p>
<img alt="images/disconnected.png" src="images/disconnected.png" />
</div>
<div class="section" id="quicklook">
<h2><a class="toc-backref" href="#id20">QuickLook</a></h2>
<p>The <tt class="docutils literal">quicklook</tt> plugin is only displayed on wide screen and propose a
bar view for CPU and memory (virtual and swap).</p>
<img alt="images/quicklook.png" src="images/quicklook.png" />
<p><em>Note</em>: limit values can be overwritten in the configuration file under
the <tt class="docutils literal">[quicklook]</tt> section.</p>
</div>
<div class="section" id="cpu">
<h2><a class="toc-backref" href="#id18">CPU</a></h2>
<h2><a class="toc-backref" href="#id21">CPU</a></h2>
<p>Short view:</p>
<img alt="images/cpu.png" src="images/cpu.png" />
<p>If enough horizontal space is available, extended CPU information are
@ -632,7 +678,7 @@ time. The total CPU usage is displayed on the first line.</p>
the <tt class="docutils literal">[cpu]</tt> and/or <tt class="docutils literal">[percpu]</tt> sections.</p>
</div>
<div class="section" id="load">
<h2><a class="toc-backref" href="#id19">Load</a></h2>
<h2><a class="toc-backref" href="#id22">Load</a></h2>
<img alt="images/load.png" src="images/load.png" />
<p>On the <em>No Sheep</em> blog, <em>Zachary Tirrell</em> defines the load average <a class="footnote-reference" href="#id3" id="id1">[1]</a>:</p>
<blockquote>
@ -652,7 +698,7 @@ The first line also displays the number of CPU core.</p>
the <tt class="docutils literal">[load]</tt> section.</p>
</div>
<div class="section" id="memory">
<h2><a class="toc-backref" href="#id20">Memory</a></h2>
<h2><a class="toc-backref" href="#id23">Memory</a></h2>
<p>Glances uses two columns: one for the <tt class="docutils literal">RAM</tt> and one for the <tt class="docutils literal">SWAP</tt>.</p>
<img alt="images/mem.png" src="images/mem.png" />
<p>If enough space is available, Glances displays extended information for
@ -669,7 +715,7 @@ the <tt class="docutils literal">RAM</tt>:</p>
the <tt class="docutils literal">[memory]</tt> and/or <tt class="docutils literal">[memswap]</tt> sections.</p>
</div>
<div class="section" id="network">
<h2><a class="toc-backref" href="#id21">Network</a></h2>
<h2><a class="toc-backref" href="#id24">Network</a></h2>
<img alt="images/network.png" src="images/network.png" />
<p>Glances displays the network interface bit rate. The unit is adapted
dynamically (bits per second, kbits per second, Mbits per second, etc).</p>
@ -680,7 +726,7 @@ and per-interface limit values in the <tt class="docutils literal">[network]</tt
configuration file and aliases for interface name.</p>
</div>
<div class="section" id="disk-i-o">
<h2><a class="toc-backref" href="#id22">Disk I/O</a></h2>
<h2><a class="toc-backref" href="#id25">Disk I/O</a></h2>
<img alt="images/diskio.png" src="images/diskio.png" />
<p>Glances displays the disk I/O throughput. The unit is adapted dynamically.</p>
<p>There is no alert on this information.</p>
@ -688,7 +734,7 @@ configuration file and aliases for interface name.</p>
<tt class="docutils literal">[diskio]</tt> section in the configuration file and aliases for disk name.</p>
</div>
<div class="section" id="file-system">
<h2><a class="toc-backref" href="#id23">File System</a></h2>
<h2><a class="toc-backref" href="#id26">File System</a></h2>
<img alt="images/fs.png" src="images/fs.png" />
<p>Glances displays the used and total file system disk space. The unit is
adapted dynamically.</p>
@ -703,9 +749,16 @@ adapted dynamically.</p>
the <tt class="docutils literal">[filesystem]</tt> section.</p>
<p>If a RAID controller is detected on you system, its status will be displayed:</p>
<img alt="images/raid.png" src="images/raid.png" />
<p>By default, the plugin only displays physical devices (hard disks, USB
keys) and ignore all others. To allow others FS type, you have to use the
following section in the configuration file:</p>
<pre class="literal-block">
[fs]
allow=zfs,misc
</pre>
</div>
<div class="section" id="sensors">
<h2><a class="toc-backref" href="#id24">Sensors</a></h2>
<h2><a class="toc-backref" href="#id27">Sensors</a></h2>
<p>Glances can displays the sensors information using <cite>lm-sensors</cite>,
<cite>hddtemp</cite> and <cite>batinfo</cite> <a class="footnote-reference" href="#id4" id="id2">[2]</a>.</p>
<p>All of the above libraries are available only on Linux.</p>
@ -713,11 +766,11 @@ the <tt class="docutils literal">[filesystem]</tt> section.</p>
temperature only.</p>
<img alt="images/sensors.png" src="images/sensors.png" />
<p>There is no alert on this information.</p>
<p><em>Note</em>: limit values and sensors alias names can be defined in the configuration
file under the <tt class="docutils literal">[sensors]</tt> section.</p>
<p><em>Note</em>: limit values and sensors alias names can be defined in the
configuration file under the <tt class="docutils literal">[sensors]</tt> section.</p>
</div>
<div class="section" id="processes-list">
<h2><a class="toc-backref" href="#id25">Processes List</a></h2>
<h2><a class="toc-backref" href="#id28">Processes List</a></h2>
<p>Compact view:</p>
<img alt="images/processlist.png" src="images/processlist.png" />
<p>Full view:</p>
@ -786,7 +839,8 @@ User cans switch to the process name by pressing on the <tt class="docutils lite
<dt><tt class="docutils literal">Z</tt></dt>
<dd>Zombie</dd>
</dl>
<p>In standalone mode, additionals informations are provided for the top process:</p>
<p>In standalone mode, additional informations are provided for the top
process:</p>
<img alt="images/processlist-top.png" src="images/processlist-top.png" />
<ul class="simple">
<li>CPU affinity (number of cores used by the process)</li>
@ -794,12 +848,13 @@ User cans switch to the process name by pressing on the <tt class="docutils lite
<li>Open threads, files and network sessions (TCP and UDP)</li>
<li>IO nice level</li>
</ul>
<p>The extended stats feature could be enabled using the --enable-process-extended option (command line) or the <tt class="docutils literal">e</tt> key (curses interface).</p>
<p>The extended stats feature could be enabled using the <tt class="docutils literal"><span class="pre">--enable-process-extended</span></tt>
option (command line) or the <tt class="docutils literal">e</tt> key (curses interface).</p>
<p><em>Note</em>: limit values can be overwritten in the configuration file under
the <tt class="docutils literal">[process]</tt> section.</p>
</div>
<div class="section" id="monitored-processes-list">
<h2><a class="toc-backref" href="#id26">Monitored Processes List</a></h2>
<h2><a class="toc-backref" href="#id29">Monitored Processes List</a></h2>
<p>The monitored processes list allows user, through the configuration file,
to group processes and quickly show if the number of running processes is
not good.</p>
@ -835,10 +890,10 @@ list_1_regex=.*nginx.*
list_1_command=nginx -v
list_1_countmin=1
list_1_countmax=4
list_1_description=PHP-FPM
list_1_regex=.*php-fpm.*
list_1_countmin=1
list_1_countmax=20
list_2_description=PHP-FPM
list_2_regex=.*php-fpm.*
list_2_countmin=1
list_2_countmax=20
</pre>
<p>In client/server mode, the list is defined on the server side.
A new method, called <cite>getAllMonitored</cite>, is available in the APIs and
@ -851,7 +906,7 @@ get the JSON representation of the monitored processes list.</p>
</div>
</div>
<div class="section" id="logs">
<h2><a class="toc-backref" href="#id27">Logs</a></h2>
<h2><a class="toc-backref" href="#id30">Logs</a></h2>
<img alt="images/logs.png" src="images/logs.png" />
<p>A log messages list is displayed in the bottom of the screen if (and
only if):</p>
@ -870,31 +925,39 @@ processes list alerts</li>
</ol>
</div>
<div class="section" id="docker">
<h2><a class="toc-backref" href="#id28">Docker</a></h2>
<p>If you use Docker, Glances can help you to monitor your container. Glances uses the Docker API through the Docker-Py library.</p>
<h2><a class="toc-backref" href="#id31">Docker</a></h2>
<p>If you use <tt class="docutils literal">Docker</tt>, Glances can help you to monitor your container.
Glances uses the Docker API through the <tt class="docutils literal"><span class="pre">docker-py</span></tt> library.</p>
<img alt="images/docker.png" src="images/docker.png" />
</div>
<div class="section" id="actions">
<h2><a class="toc-backref" href="#id29">Actions</a></h2>
<h2><a class="toc-backref" href="#id32">Actions</a></h2>
<p>Glances can trigger actions on events.</p>
<p>By action, we mean all shell command line. For example, if you want to execute the foo.py script if the last 5 minutes load are critical then add the action line to the Glances configuration file:</p>
<p>By <tt class="docutils literal">action</tt>, we mean all shell command line. For example, if you want
to execute the <tt class="docutils literal">foo.py</tt> script if the last 5 minutes load are critical
then add the action line to the Glances configuration file:</p>
<pre class="code literal-block">
[load]
critical=5.0
critical_action=python /path/to/foo.py
</pre>
<p>All the stats are available in the command line through the use of the {{mustache}} syntax. Another example to create a log file containing used vs total disk space if a space trigger warning is reached:</p>
<p>All the stats are available in the command line through the use of the
<tt class="docutils literal">{{mustache}}</tt> syntax. Another example would be to create a log file
containing used vs total disk space if a space trigger warning is reached:</p>
<pre class="code literal-block">
[fs]
warning=70
warning_action=echo {{mnt_point}} {{used}}/{{size}} &gt; /tmp/fs.alert
</pre>
<p><em>Note</em>: You can use all the stats for the current plugin (see <a class="reference external" href="https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to">https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to</a> for the stats list)</p>
<p><em>Note</em>: you can use all the stats for the current plugin (see
<a class="reference external" href="https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to">https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to</a> for
the stats list)</p>
</div>
</div>
<div class="section" id="gateway-to-others-services">
<h1><a class="toc-backref" href="#id30">Gateway to others services</a></h1>
<p><em>CSV</em></p>
<h1><a class="toc-backref" href="#id33">Gateway to others services</a></h1>
<div class="section" id="csv">
<h2><a class="toc-backref" href="#id34">CSV</a></h2>
<p>It is possible to export statistics to CSV file.</p>
<pre class="code console literal-block">
<span class="generic prompt">$</span> glances --export-csv /tmp/glances.csv
@ -902,8 +965,12 @@ warning_action=echo {{mnt_point}} {{used}}/{{size}} &gt; /tmp/fs.alert
<p>CSV file description:
- Stats description (first line)
- Stats (others lines)</p>
<p><em>InfluxDB</em></p>
<p>You can export statistics to an InfluxDB server (time series server). The connection should be defined in the Glances configuration file as following:</p>
</div>
<div class="section" id="influxdb">
<h2><a class="toc-backref" href="#id35">InfluxDB</a></h2>
<p>You can export statistics to an <tt class="docutils literal">InfluxDB</tt> server (time series server).
The connection should be defined in the Glances configuration file as
following:</p>
<pre class="code literal-block">
[influxdb]
host=localhost
@ -916,15 +983,22 @@ db=glances
<pre class="code console literal-block">
<span class="generic prompt">$</span> glances --export-influxdb
</pre>
<p><em>Statsd</em></p>
<p>You can export statistics to a Statsd server (welcome to Graphite !). The connection should be defined in the Glances configuration file as following:</p>
<p>For Grafana users, Glances provides a dedicated <a class="reference external" href="https://github.com/nicolargo/glances/blob/master/conf/glances-grafana.json">dashboard</a>. Just import
the file in your <tt class="docutils literal">Grafana</tt> web interface.</p>
<img alt="images/grafana.png" src="images/grafana.png" />
</div>
<div class="section" id="statsd">
<h2><a class="toc-backref" href="#id36">Statsd</a></h2>
<p>You can export statistics to a <tt class="docutils literal">Statsd</tt> server (welcome to Graphite!).
The connection should be defined in the Glances configuration file as
following:</p>
<pre class="code literal-block">
[statsd]
host=localhost
port=8125
prefix=glances
</pre>
<p>Note: the prefix option is optionnal ('glances by default')</p>
<p><em>Note</em>: the prefix option is optional ('glances by default')</p>
<p>and run Glances with:</p>
<pre class="code console literal-block">
<span class="generic prompt">$</span> glances --export-statsd
@ -938,18 +1012,39 @@ prefix=glances
...
</pre>
</div>
<div class="section" id="apis-documentations">
<h1><a class="toc-backref" href="#id31">APIs Documentations</a></h1>
<p>Glances includes a <a class="reference external" href="http://docs.python.org/2/library/simplexmlrpcserver.html">XML-RPC server</a> and a <a class="reference external" href="http://jsonapi.org/">RESTFUL-JSON</a> API which and can be used by another client software.</p>
<p>APIs documentations are available at:</p>
<div class="section" id="rabbitmq">
<h2><a class="toc-backref" href="#id37">RabbitMQ</a></h2>
<p>You can export statistics to an <tt class="docutils literal">RabbitMQ</tt> server (AMQP Broker).
The connection should be defined in the Glances configuration file as
following:</p>
<pre class="code literal-block">
[rabbitmq]
host=localhost
port=5672
user=glances
password=glances
queue=glances_queue
</pre>
<p>and run Glances with:</p>
<pre class="code console literal-block">
<span class="generic prompt">$</span> glances --export-rabbitmq
</pre>
</div>
</div>
<div class="section" id="apis-documentation">
<h1><a class="toc-backref" href="#id38">APIs documentation</a></h1>
<p>Glances includes a <a class="reference external" href="http://docs.python.org/2/library/simplexmlrpcserver.html">XML-RPC server</a> and a <a class="reference external" href="http://jsonapi.org/">RESTFUL-JSON</a> API which can
be used by another client software.</p>
<p>APIs documentation is available at:</p>
<ul class="simple">
<li>XML-RPC: <a class="reference external" href="https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to">https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to</a></li>
<li>RESTFUL-JSON: <a class="reference external" href="https://github.com/nicolargo/glances/wiki/The-Glances-RESTFULL-JSON-API">https://github.com/nicolargo/glances/wiki/The-Glances-RESTFULL-JSON-API</a></li>
</ul>
</div>
<div class="section" id="support">
<h1><a class="toc-backref" href="#id32">Support</a></h1>
<p>To post a question about Glances use case, please post it to the offical Q&amp;A <a class="reference external" href="https://groups.google.com/forum/?hl=en#!forum/glances-users">forum</a>.</p>
<h1><a class="toc-backref" href="#id39">Support</a></h1>
<p>To post a question about Glances use cases, please post it to the
official Q&amp;A <a class="reference external" href="https://groups.google.com/forum/?hl=en#!forum/glances-users">forum</a>.</p>
<p>To report a bug or a feature request use the bug tracking system at
<a class="reference external" href="https://github.com/nicolargo/glances/issues">https://github.com/nicolargo/glances/issues</a>.</p>
<p>Feel free to contribute !</p>

View File

@ -2,11 +2,11 @@
Glances
=======
This manual describes *Glances* version 2.3.
This manual describes *Glances* version 2.4.
Copyright © 2011-2015 Nicolas Hennion <nicolas@nicolargo.com>
January 2015
May 2015
.. contents:: Table of Contents
@ -67,7 +67,13 @@ and on the client:
where ``@server`` is the IP address or hostname of the server.
Glances can centralize available Glances servers using the ``--browser`` option. The server list can be staticaly defined in the Glances configuration file (section [serverlist]). Glances can also detect and display all Glances servers available on you network (auto discover mode is based on the the Zeroconf protocol only available on GNU/Linux and Mac OS X):
Glances can centralize available Glances servers using the ``--browser``
option. The server list can be statically defined in the Glances
configuration file (section ``[serverlist]``).
Glances can also detect and display all Glances servers available on your
network (auto-discover mode is based on the the ``zeroconf`` protocol,
which is only available on GNU/Linux and OS X):
.. code-block:: console
@ -97,7 +103,7 @@ client, the latter will try to grab stats using the ``SNMP`` protocol:
client$ glances -c @snmpserver
Note: Stats grabbed by SNMP request are limited (operating system dependent).
*Note*: stats grabbed by SNMP request are limited (OS dependent).
Web Server Mode
---------------
@ -117,7 +123,12 @@ and on the client enter the following URL in your favorite web browser:
where ``@server`` is the IP address or hostname of the server.
To change the refresh rate of the page, just add the period in seconds between refreshes at the end of the URL, ie. to refresh every 10s, use ``http://@server:61208/10``.
To change the refresh rate of the page, just add the period in seconds
at the end of the URL. For example, to refresh the page every 10s:
::
http://@server:61208/10
The Glances web interface follows responsive web design principles.
@ -125,7 +136,6 @@ Screenshot from Chrome on Android
.. image:: images/screenshot-web2.png
Command Reference
=================
@ -134,46 +144,45 @@ Command-Line Options
-h, --help show this help message and exit
-V, --version show program's version number and exit
-d, --debug Enable debug mode
-d, --debug enable debug mode
-C CONF_FILE, --config CONF_FILE
path to the configuration file
--enable-history enable the history mode
--disable-bold disable bold mode in the terminal
--disable-diskio disable disk I/O module
--disable-fs disable filesystem module
--disable-network disable network module
--disable-ip disable IP module
--disable-diskio disable disk I/O module
--disable-fs disable file system module
--disable-sensors disable sensors module
--disable-hddtemp disable hddtemp module
--disable-raid disable RAID module
--disable-docker disable Docker module
--disable-left-sidebar
disable left sidebar
disable network, disk I/O, file system and
sensors modules (py3sensors needed)
--disable-process disable process module
--disable-log disable log module
--disable-quicklook disable quick look module
--disable-bold disable bold mode in the terminal
--enable-process-extended
enable extended stats on top process
--enable-history enable the history mode
--enable-history enable the history mode (matplotlib needed)
--path-history PATH_HISTORY
Set the export path for graph history
--export-csv CSV_FILE
set the export path for graph history
--export-csv EXPORT_CSV
export stats to a CSV file
--export-influxdb
export stats to an InfluxDB server
--export-statsd
export stats to a Statsd server
--export-influxdb export stats to an InfluxDB server (influxdb needed)
--export-statsd export stats to a StatsD server (statsd needed)
--export-rabbitmq export stats to a RabbitMQ server (pika needed)
-c CLIENT, --client CLIENT
connect to a Glances server by IPv4/IPv6 address or
hostname
-s, --server run Glances in server mode
--browser run the Glances client browser (list of Glances server)
--browser start the client browser (list of Glances servers)
--disable-autodiscover
disable autodiscover feature
-p PORT, --port PORT define the client/server TCP port [default: 61209]
-B BIND_ADDRESS, --bind BIND_ADDRESS
bind server to the given IPv4/IPv6 address or hostname
--password-badidea PASSWORD_ARG
define password from the command line
--password define a client/server password from the prompt or
file
--disable-autodiscover
Hide Glances server from the auto discover feature
--password define a client/server password
--snmp-community SNMP_COMMUNITY
SNMP community
--snmp-port SNMP_PORT
@ -186,17 +195,18 @@ Command-Line Options
SNMP authentication key (only for SNMPv3)
--snmp-force force SNMP mode
-t TIME, --time TIME set refresh time in seconds [default: 3 sec]
-w, --webserver run Glances in web server mode
-w, --webserver run Glances in web server mode (bottle needed)
-q, --quiet do not display the curses interface
-f PROCESS_FILTER, --process-filter PROCESS_FILTER
set the process filter patern (regular expression)
set the process filter pattern (regular expression)
--process-short-name force short name for processes name
--hide-kernel-threads
hide kernel threads in process list
--tree display processes as a tree
-b, --byte display network rate in byte per second
-1, --percpu start Glances in per CPU mode
--fs-free-space display FS free space instead of used
--theme-white optimize display for white background
--fs-free-space display file system free space instead of used
--theme-white optimize display colors for white background
Interactive Commands
--------------------
@ -226,7 +236,7 @@ The following commands (key pressed) are supported while in Glances:
``f``
Show/hide file system stats
``F``
Switch between FS used and free space
Switch between file system used and free space
``g``
Generate graphs for current history
``h``
@ -252,6 +262,8 @@ The following commands (key pressed) are supported while in Glances:
``T``
View network I/O as combination
``u``
Sort processes by USER
``U``
View cumulative network I/O
``w``
Delete finished warning log messages
@ -263,10 +275,13 @@ The following commands (key pressed) are supported while in Glances:
Switch between global CPU and per-CPU stats
``2``
Enable/disable left sidebar
``3``
Enable/disable the quick look module
``/``
Switch between short name / command line (processes name)
In the Glances client browser (accessible through the --browser command line argument):
In the Glances client browser (accessible through the ``--browser``
command line argument):
``ENTER``
Run Glances client to the selected server
@ -282,15 +297,20 @@ Configuration
No configuration file is mandatory to use Glances.
Furthermore a configuration file is needed to set up limits, disks or
network interfaces to hide and/or monitored processes list or to define
alias.
Furthermore a configuration file is needed to modify limit alerts, to
set up monitored processes list, to hide disks or network interfaces or
to define alias.
By default, the configuration file is under:
Location
--------
:Linux: ``/etc/glances/glances.conf``
:\*BSD and OS X: ``/usr/local/etc/glances/glances.conf``
:Windows: ``%APPDATA%\glances\glances.conf``
You can put the configuration file ``glances.conf`` in the following
locations:
:Linux: ``~/.config/glances, /etc/glances``
:\*BSD: ``~/.config/glances, /usr/local/etc/glances``
:OS X: ``~/Library/Application Support/glances, /usr/local/etc/glances``
:Windows: ``%APPDATA%\glances``
On Windows XP, the ``%APPDATA%`` path is:
@ -303,24 +323,12 @@ Since Windows Vista and newer versions:
::
C:\Users\<User>\AppData\Roaming
or
%userprofile%\AppData\Roaming
You can override the default configuration, located in one of the above
directories on your system, except for Windows.
User-specific options override system-wide options and options given on
the command line override either.
Just copy the ``glances.conf`` file to your ``$XDG_CONFIG_HOME`` directory,
e.g., on Linux:
.. code-block:: console
mkdir -p $XDG_CONFIG_HOME/glances
cp /usr/share/doc/glances/glances.conf $XDG_CONFIG_HOME/glances/
On OS X, you should copy the configuration file to
``~/Library/Application Support/glances/``.
*Configuration file description*
Syntax
------
Each plugin and export module can have a section.
@ -342,7 +350,8 @@ Example for the CPU plugin:
steal_warning=70
steal_critical=90
By default Steal CPU time alerts aren't logged. If you want to enable log/alert, just add:
By default the ``steal`` CPU time alerts aren't logged. If you want to
enable log/alert, just add:
.. code-block::
@ -357,10 +366,11 @@ can ben logged using the -d option on the command line.
By default, the log file is under:
:Linux, \*BSD and OS X: ``/tmp/glances.log``
:Linux, \*BSD, OS X: ``/tmp/glances.log``
:Windows: ``%APPDATA%\Local\temp\glances.log``
If glances.log is not writable, a new file will be created and returned to the user console.
If ``glances.log`` is not writable, a new file will be created and
returned to the user console.
Anatomy Of The Application
==========================
@ -395,6 +405,17 @@ Disconnected:
.. image:: images/disconnected.png
QuickLook
---------
The ``quicklook`` plugin is only displayed on wide screen and propose a
bar view for CPU and memory (virtual and swap).
.. image:: images/quicklook.png
*Note*: limit values can be overwritten in the configuration file under
the ``[quicklook]`` section.
CPU
---
@ -518,6 +539,15 @@ If a RAID controller is detected on you system, its status will be displayed:
.. image:: images/raid.png
By default, the plugin only displays physical devices (hard disks, USB
keys) and ignore all others. To allow others FS type, you have to use the
following section in the configuration file:
::
[fs]
allow=zfs,misc
Sensors
-------
@ -533,8 +563,8 @@ temperature only.
There is no alert on this information.
*Note*: limit values and sensors alias names can be defined in the configuration
file under the ``[sensors]`` section.
*Note*: limit values and sensors alias names can be defined in the
configuration file under the ``[sensors]`` section.
Processes List
--------------
@ -611,7 +641,8 @@ Process status legend:
``Z``
Zombie
In standalone mode, additionals informations are provided for the top process:
In standalone mode, additional informations are provided for the top
process:
.. image:: images/processlist-top.png
@ -620,7 +651,8 @@ In standalone mode, additionals informations are provided for the top process:
* Open threads, files and network sessions (TCP and UDP)
* IO nice level
The extended stats feature could be enabled using the --enable-process-extended option (command line) or the ``e`` key (curses interface).
The extended stats feature could be enabled using the ``--enable-process-extended``
option (command line) or the ``e`` key (curses interface).
*Note*: limit values can be overwritten in the configuration file under
the ``[process]`` section.
@ -670,10 +702,10 @@ another item:
list_1_command=nginx -v
list_1_countmin=1
list_1_countmax=4
list_1_description=PHP-FPM
list_1_regex=.*php-fpm.*
list_1_countmin=1
list_1_countmax=20
list_2_description=PHP-FPM
list_2_regex=.*php-fpm.*
list_2_countmin=1
list_2_countmax=20
In client/server mode, the list is defined on the server side.
A new method, called `getAllMonitored`, is available in the APIs and
@ -708,7 +740,8 @@ Each alert message displays the following information:
Docker
------
If you use Docker, Glances can help you to monitor your container. Glances uses the Docker API through the Docker-Py library.
If you use ``Docker``, Glances can help you to monitor your container.
Glances uses the Docker API through the ``docker-py`` library.
.. image:: images/docker.png
@ -717,7 +750,9 @@ Actions
Glances can trigger actions on events.
By action, we mean all shell command line. For example, if you want to execute the foo.py script if the last 5 minutes load are critical then add the action line to the Glances configuration file:
By ``action``, we mean all shell command line. For example, if you want
to execute the ``foo.py`` script if the last 5 minutes load are critical
then add the action line to the Glances configuration file:
.. code-block::
@ -725,7 +760,9 @@ By action, we mean all shell command line. For example, if you want to execute t
critical=5.0
critical_action=python /path/to/foo.py
All the stats are available in the command line through the use of the {{mustache}} syntax. Another example to create a log file containing used vs total disk space if a space trigger warning is reached:
All the stats are available in the command line through the use of the
``{{mustache}}`` syntax. Another example would be to create a log file
containing used vs total disk space if a space trigger warning is reached:
.. code-block::
@ -733,13 +770,15 @@ All the stats are available in the command line through the use of the {{mustach
warning=70
warning_action=echo {{mnt_point}} {{used}}/{{size}} > /tmp/fs.alert
*Note*: You can use all the stats for the current plugin (see https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to for the stats list)
*Note*: you can use all the stats for the current plugin (see
https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to for
the stats list)
Gateway to others services
==========================
*CSV*
CSV
---
It is possible to export statistics to CSV file.
@ -751,9 +790,12 @@ CSV file description:
- Stats description (first line)
- Stats (others lines)
*InfluxDB*
InfluxDB
--------
You can export statistics to an InfluxDB server (time series server). The connection should be defined in the Glances configuration file as following:
You can export statistics to an ``InfluxDB`` server (time series server).
The connection should be defined in the Glances configuration file as
following:
.. code-block::
@ -770,9 +812,17 @@ and run Glances with:
$ glances --export-influxdb
*Statsd*
For Grafana users, Glances provides a dedicated `dashboard`_. Just import
the file in your ``Grafana`` web interface.
You can export statistics to a Statsd server (welcome to Graphite !). The connection should be defined in the Glances configuration file as following:
.. image:: images/grafana.png
Statsd
------
You can export statistics to a ``Statsd`` server (welcome to Graphite!).
The connection should be defined in the Glances configuration file as
following:
.. code-block::
@ -781,7 +831,7 @@ You can export statistics to a Statsd server (welcome to Graphite !). The connec
port=8125
prefix=glances
Note: the prefix option is optionnal ('glances by default')
*Note*: the prefix option is optional ('glances by default')
and run Glances with:
@ -799,13 +849,35 @@ Glances will generate stats as:
'glances.load.min1': 0.19,
...
RabbitMQ
--------
APIs Documentations
===================
You can export statistics to an ``RabbitMQ`` server (AMQP Broker).
The connection should be defined in the Glances configuration file as
following:
Glances includes a `XML-RPC server`_ and a `RESTFUL-JSON`_ API which and can be used by another client software.
.. code-block::
APIs documentations are available at:
[rabbitmq]
host=localhost
port=5672
user=glances
password=glances
queue=glances_queue
and run Glances with:
.. code-block:: console
$ glances --export-rabbitmq
APIs documentation
==================
Glances includes a `XML-RPC server`_ and a `RESTFUL-JSON`_ API which can
be used by another client software.
APIs documentation is available at:
- XML-RPC: https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to
- RESTFUL-JSON: https://github.com/nicolargo/glances/wiki/The-Glances-RESTFULL-JSON-API
@ -813,7 +885,8 @@ APIs documentations are available at:
Support
=======
To post a question about Glances use case, please post it to the offical Q&A `forum`_.
To post a question about Glances use cases, please post it to the
official Q&A `forum`_.
To report a bug or a feature request use the bug tracking system at
https://github.com/nicolargo/glances/issues.
@ -828,3 +901,4 @@ Feel free to contribute !
.. _XML-RPC server: http://docs.python.org/2/library/simplexmlrpcserver.html
.. _RESTFUL-JSON: http://jsonapi.org/
.. _forum: https://groups.google.com/forum/?hl=en#!forum/glances-users
.. _dashboard: https://github.com/nicolargo/glances/blob/master/conf/glances-grafana.json

BIN
docs/images/grafana.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
docs/images/quicklook.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 KiB

After

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 496 KiB

View File

@ -1,6 +1,7 @@
You are in the main Glances's source folder. This page is **ONLY** for developpers purposes.
You are in the main Glances source folder. This page is **ONLY** for developers.
If you are looking for the user manual, please follow this link: https://github.com/nicolargo/glances/blob/master/docs/glances-doc.rst
If you are looking for the user manual, please follow this link:
https://github.com/nicolargo/glances/blob/master/docs/glances-doc.rst
===
@ -9,11 +10,10 @@ __main__.py Entry point for Glances module
core/
=> Glances core folder
glances_config.py Manage configuration file
glances_globals.py Share variables uppon modules
glances_globals.py Share variables upon modules
glances_limits.py Manage limits
glances_logs.py Manage logs
glances_main.py Main script to rule them up...
glances_stats.py Inteface to grab stats
glances_client.py Glances client
glances_server.py Glances server
glances_standalone.py Glances standalone (with curse interface)

View File

@ -20,12 +20,11 @@
"""Init the Glances software."""
__appname__ = 'glances'
__version__ = '2.3'
__version__ = '2.4'
__author__ = 'Nicolas Hennion <nicolas@nicolargo.com>'
__license__ = 'LGPL'
# Import system lib
import gettext
import locale
import platform
import signal
@ -40,23 +39,24 @@ except ImportError:
# Import Glances libs
# Note: others Glances libs will be imported optionally
from glances.core.glances_globals import gettext_domain, locale_dir
from glances.core.glances_logging import logger
from glances.core.glances_main import GlancesMain
# Get PSutil version
psutil_min_version = (2, 0, 0)
psutil_version = tuple([int(num) for num in __psutil_version.split('.')])
try:
locale.setlocale(locale.LC_ALL, '')
except locale.Error:
print("Warning: Unable to set locale. Expect encoding problems.")
# First log with Glances and PSUtil version
logger.info('Start Glances {0}'.format(__version__))
logger.info('{0} {1} and PSutil {2} detected'.format(platform.python_implementation(),
platform.python_version(),
__psutil_version))
# Check Python version
if sys.version_info < (2, 6) or (3, 0) <= sys.version_info < (3, 3):
print('Glances requires at least Python 2.6 or 3.3 to run.')
sys.exit(1)
# Check PSutil version
psutil_min_version = (2, 0, 0)
psutil_version = tuple([int(num) for num in __psutil_version.split('.')])
if psutil_version < psutil_min_version:
logger.critical('PSutil 2.0 or higher is needed. Glances cannot start.')
print('PSutil 2.0 or higher is needed. Glances cannot start.')
sys.exit(1)
@ -94,9 +94,12 @@ def main():
Select the mode (standalone, client or server)
Run it...
"""
# Setup translations
locale.setlocale(locale.LC_ALL, '')
gettext.install(gettext_domain, locale_dir)
# Log Glances and PSutil version
logger.info('Start Glances {0}'.format(__version__))
logger.info('{0} {1} and PSutil {2} detected'.format(
platform.python_implementation(),
platform.python_version(),
__psutil_version))
# Share global var
global core, standalone, client, server, webserver
@ -164,7 +167,7 @@ def main():
server = GlancesServer(cached_time=core.cached_time,
config=core.get_config(),
args=args)
print(_("Glances server is running on {0}:{1}").format(args.bind_address, args.port))
print('Glances server is running on {0}:{1}'.format(args.bind_address, args.port))
# Set the server login/password (if -P/--password tag)
if args.password != "":

View File

@ -19,11 +19,12 @@
"""Manage on alert actions."""
# Import system lib
from subprocess import Popen
# Import Glances lib
from glances.core.glances_logging import logger
# Import system lib
from subprocess import Popen
try:
import pystache
except ImportError:
@ -35,11 +36,10 @@ else:
class GlancesActions(object):
"""This class manage action if an alert is reached"""
"""This class manage action if an alert is reached."""
def __init__(self):
"""Init GlancesActions class"""
"""Init GlancesActions class."""
# Dict with the criticity status
# - key: stat_name
# - value: criticity
@ -47,25 +47,26 @@ class GlancesActions(object):
self.status = {}
def get(self, stat_name):
"""Get the stat_name criticity"""
"""Get the stat_name criticity."""
try:
return self.status[stat_name]
except KeyError:
return None
def set(self, stat_name, criticity):
"""Set the stat_name to criticity"""
"""Set the stat_name to criticity."""
self.status[stat_name] = criticity
def run(self, stat_name, criticity, commands, mustache_dict=None):
"""Run the commands (in background)
"""Run the commands (in background).
- stats_name: plugin_name (+ header)
- criticity: criticity of the trigger
- commands: a list of command line with optional {{mustache}}
- mustache_dict: Plugin stats (can be use within {{mustache}})
Return True if the commands have been ran"""
Return True if the commands have been ran.
"""
if self.get(stat_name) == criticity:
# Action already executed => Exit
return False
@ -75,7 +76,7 @@ class GlancesActions(object):
criticity,
mustache_dict))
# Ran all actions in background
# Run all actions in background
for cmd in commands:
# Replace {{arg}} by the dict one (Thk to {Mustache})
if pystache_tag:

View File

@ -22,11 +22,7 @@
# Import system libs
import socket
import sys
try:
import netifaces
netifaces_tag = True
except ImportError:
netifaces_tag = False
try:
from zeroconf import (
__version__ as __zeroconf_version,
@ -42,13 +38,13 @@ except ImportError:
from glances.core.glances_globals import appname
from glances.core.glances_logging import logger
# Zeroconf 0.16 or higher is needed
# Zeroconf 0.17 or higher is needed
if zeroconf_tag:
zeroconf_min_version = (0, 16, 0)
zeroconf_min_version = (0, 17, 0)
zeroconf_version = tuple([int(num) for num in __zeroconf_version.split('.')])
logger.debug("Zeroconf version {0} detected.".format(__zeroconf_version))
if zeroconf_version < zeroconf_min_version:
logger.critical("Please install zeroconf 0.16 or higher.")
logger.critical("Please install zeroconf 0.17 or higher.")
sys.exit(1)
# Global var
@ -57,7 +53,7 @@ zeroconf_type = "_%s._tcp." % appname
class AutoDiscovered(object):
"""Class to manage the auto discovered servers dict"""
"""Class to manage the auto discovered servers dict."""
def __init__(self):
# server_dict is a list of dict (JSON compliant)
@ -65,30 +61,30 @@ class AutoDiscovered(object):
self._server_list = []
def get_servers_list(self):
"""Return the current server list (list of dict)"""
"""Return the current server list (list of dict)."""
return self._server_list
def set_server(self, server_pos, key, value):
"""Set the key to the value for the server_pos (position in the list)"""
"""Set the key to the value for the server_pos (position in the list)."""
self._server_list[server_pos][key] = value
def add_server(self, name, ip, port):
"""Add a new server to the list"""
new_server = {'key': name, # Zeroconf name with both hostname and port
"""Add a new server to the list."""
new_server = {
'key': name, # Zeroconf name with both hostname and port
'name': name.split(':')[0], # Short name
'ip': ip, # IP address seen by the client
'port': port, # TCP port
'username': 'glances', # Default username
'password': '', # Default password
'status': 'UNKNOWN', # Server status: 'UNKNOWN', 'OFFLINE', 'ONLINE', 'PROTECTED'
'type': 'DYNAMIC', # Server type: 'STATIC' or 'DYNAMIC'
}
'type': 'DYNAMIC'} # Server type: 'STATIC' or 'DYNAMIC'
self._server_list.append(new_server)
logger.debug("Updated servers list (%s servers): %s" %
(len(self._server_list), self._server_list))
def remove_server(self, name):
"""Remove a server from the dict"""
"""Remove a server from the dict."""
for i in self._server_list:
if i['key'] == name:
try:
@ -103,22 +99,23 @@ class AutoDiscovered(object):
class GlancesAutoDiscoverListener(object):
"""Zeroconf listener for Glances server"""
"""Zeroconf listener for Glances server."""
def __init__(self):
# Create an instance of the servers list
self.servers = AutoDiscovered()
def get_servers_list(self):
"""Return the current server list (list of dict)"""
"""Return the current server list (list of dict)."""
return self.servers.get_servers_list()
def set_server(self, server_pos, key, value):
"""Set the key to the value for the server_pos (position in the list)"""
"""Set the key to the value for the server_pos (position in the list)."""
self.servers.set_server(server_pos, key, value)
def add_service(self, zeroconf, srv_type, srv_name):
"""Method called when a new Zeroconf client is detected
"""Method called when a new Zeroconf client is detected.
Return True if the zeroconf client is a Glances server
Note: the return code will never be used
"""
@ -141,7 +138,7 @@ class GlancesAutoDiscoverListener(object):
return True
def remove_service(self, zeroconf, srv_type, srv_name):
# Remove the server from the list
"""Remove the server from the list."""
self.servers.remove_server(srv_name)
logger.info(
"Glances server %s removed from the autodetect list" % srv_name)
@ -149,7 +146,7 @@ class GlancesAutoDiscoverListener(object):
class GlancesAutoDiscoverServer(object):
"""Implementation of the Zeroconf protocol (server side for the Glances client)"""
"""Implementation of the Zeroconf protocol (server side for the Glances client)."""
def __init__(self, args=None):
if zeroconf_tag:
@ -169,14 +166,14 @@ class GlancesAutoDiscoverServer(object):
self.zeroconf_enable_tag = False
def get_servers_list(self):
"""Return the current server list (dict of dict)"""
"""Return the current server list (dict of dict)."""
if zeroconf_tag and self.zeroconf_enable_tag:
return self.listener.get_servers_list()
else:
return []
def set_server(self, server_pos, key, value):
"""Set the key to the value for the server_pos (position in the list)"""
"""Set the key to the value for the server_pos (position in the list)."""
if zeroconf_tag and self.zeroconf_enable_tag:
self.listener.set_server(server_pos, key, value)
@ -197,16 +194,15 @@ class GlancesAutoDiscoverClient(object):
except socket.error as e:
logger.error("Cannot start zeroconf: {0}".format(e))
if netifaces_tag:
try:
# -B @ overwrite the dynamic IPv4 choice
if zeroconf_bind_address == '0.0.0.0':
zeroconf_bind_address = self.find_active_ip_address()
else:
logger.error("Couldn't find the active IP address: netifaces library not found.")
except KeyError:
# Issue #528 (no network interface available)
pass
logger.info("Announce the Glances server on the LAN (using {0} IP address)".format(zeroconf_bind_address))
print("Announce the Glances server on the LAN (using {0} IP address)".format(zeroconf_bind_address))
self.info = ServiceInfo(
zeroconf_type, '{0}:{1}.{2}'.format(hostname, args.port, zeroconf_type),
address=socket.inet_aton(zeroconf_bind_address), port=args.port,
@ -215,15 +211,14 @@ class GlancesAutoDiscoverClient(object):
else:
logger.error("Cannot announce Glances server on the network: zeroconf library not found.")
def find_active_ip_address(self):
@staticmethod
def find_active_ip_address():
"""Try to find the active IP addresses."""
try:
import netifaces
# Interface of the default gateway
gateway_itf = netifaces.gateways()['default'][netifaces.AF_INET][1]
# IP address for the interface
return netifaces.ifaddresses(gateway_itf)[netifaces.AF_INET][0]['addr']
except Exception:
return None
def close(self):
if zeroconf_tag:

View File

@ -28,11 +28,6 @@ try:
except ImportError:
# Python 2
from xmlrpclib import Transport, ServerProxy, ProtocolError, Fault
try:
import http.client as httplib
except ImportError:
# Python 2
import httplib
# Import Glances libs
from glances.core.glances_globals import version
@ -43,7 +38,7 @@ from glances.outputs.glances_curses import GlancesCursesClient
class GlancesClientTransport(Transport):
"""This class overwrite the default XML-RPC transport and manage timeout"""
"""This class overwrite the default XML-RPC transport and manage timeout."""
def set_timeout(self, timeout):
self.timeout = timeout
@ -58,8 +53,8 @@ class GlancesClient(object):
self.args = args
self.config = config
# Client mode:
self.set_mode()
# Default client mode
self._client_mode = 'glances'
# Return to browser or exit
self.return_to_browser = return_to_browser
@ -82,29 +77,26 @@ class GlancesClient(object):
self.log_and_exit("Client couldn't create socket {0}: {1}".format(uri, e))
def log_and_exit(self, msg=''):
"""Log and (exit)"""
"""Log and exit."""
if not self.return_to_browser:
logger.critical(msg)
sys.exit(2)
else:
logger.error(msg)
def set_mode(self, mode='glances'):
@property
def client_mode(self):
"""Get the client mode."""
return self._client_mode
@client_mode.setter
def client_mode(self, mode):
"""Set the client mode.
- 'glances' = Glances server (default)
- 'snmp' = SNMP (fallback)
"""
self.mode = mode
return self.mode
def get_mode(self):
"""Get the client mode.
- 'glances' = Glances server (default)
- 'snmp' = SNMP (fallback)
"""
return self.mode
self._client_mode = mode
def login(self):
"""Logon to the server."""
@ -112,15 +104,14 @@ class GlancesClient(object):
if not self.args.snmp_force:
# First of all, trying to connect to a Glances server
self.set_mode('glances')
client_version = None
try:
client_version = self.client.init()
except socket.error as err:
# Fallback to SNMP
logger.error("Connection to Glances server failed (%s)" % err)
self.set_mode('snmp')
fallbackmsg = _("Trying fallback to SNMP...")
self.client_mode = 'snmp'
logger.error("Connection to Glances server failed: {0}".format(err))
fallbackmsg = 'No Glances server found. Trying fallback to SNMP...'
if not self.return_to_browser:
print(fallbackmsg)
else:
@ -134,22 +125,25 @@ class GlancesClient(object):
self.log_and_exit(msg)
return False
if self.get_mode() == 'glances' and version.split('.')[0] == client_version.split('.')[0]:
if self.client_mode == 'glances':
# Check that both client and server are in the same major version
if version.split('.')[0] == client_version.split('.')[0]:
# Init stats
self.stats = GlancesStatsClient(config=self.config, args=self.args)
self.stats.set_plugins(json.loads(self.client.getAllPlugins()))
logger.debug(
"Client version: %s / Server version: %s" % (version, client_version))
elif self.get_mode() == 'glances':
self.log_and_exit("Client and server not compatible: Client version: %s / Server version: %s" % (version, client_version))
logger.debug("Client version: {0} / Server version: {1}".format(version, client_version))
else:
self.log_and_exit("Client and server not compatible: \
Client version: {0} / Server version: {1}".format(version, client_version))
return False
else:
self.set_mode('snmp')
self.client_mode = 'snmp'
if self.get_mode() == 'snmp':
# SNMP mode
if self.client_mode == 'snmp':
logger.info("Trying to grab stats by SNMP...")
# Fallback to SNMP if needed
from glances.core.glances_stats import GlancesStatsClientSNMP
# Init stats
@ -172,13 +166,13 @@ class GlancesClient(object):
def update(self):
"""Update stats from Glances/SNMP server."""
if self.get_mode() == 'glances':
if self.client_mode == 'glances':
return self.update_glances()
elif self.get_mode() == 'snmp':
elif self.client_mode == 'snmp':
return self.update_snmp()
else:
self.end()
logger.critical("Unknown server mode: {0}".format(self.get_mode()))
logger.critical("Unknown server mode: {0}".format(self.client_mode))
sys.exit(2)
def update_glances(self):
@ -222,9 +216,8 @@ class GlancesClient(object):
def serve_forever(self):
"""Main client loop."""
exitkey = False
try:
while True and not exitkey:
# Update the stats
cs_status = self.update()
@ -236,8 +229,11 @@ class GlancesClient(object):
# Export stats using export modules
self.stats.export(self.stats)
except Exception as e:
logger.critical(e)
self.end()
return self.get_mode()
return self.client_mode
def end(self):
"""End of the client session."""

View File

@ -38,7 +38,7 @@ from glances.outputs.glances_curses import GlancesCursesBrowser
class GlancesClientBrowser(object):
"""This class creates and manages the TCP client browser (servers' list)."""
"""This class creates and manages the TCP client browser (servers list)."""
def __init__(self, config=None, args=None):
# Store the arg/config
@ -58,9 +58,9 @@ class GlancesClientBrowser(object):
self.screen = GlancesCursesBrowser(args=self.args)
def get_servers_list(self):
"""
Return the current server list (list of dict)
Merge of static + autodiscover servers list
"""Return the current server list (list of dict).
Merge of static + autodiscover servers list.
"""
ret = []
@ -71,7 +71,16 @@ class GlancesClientBrowser(object):
return ret
def serve_forever(self):
def __get_uri(self, server):
"""Return the URI for the given server dict."""
# Select the connection mode (with or without password)
if server['password'] != "":
return 'http://{0}:{1}@{2}:{3}'.format(server['username'], server['password'],
server['ip'], server['port'])
else:
return 'http://{0}:{1}'.format(server['ip'], server['port'])
def __serve_forever(self):
"""Main client loop."""
while True:
# No need to update the server list
@ -84,15 +93,11 @@ class GlancesClientBrowser(object):
# 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']):
if v['type'] == 'STATIC' and v['status'] in ['UNKNOWN', 'SNMP', 'OFFLINE']:
continue
# Select the connection mode (with or without password)
if v['password'] != "":
uri = 'http://{0}:{1}@{2}:{3}'.format(v['username'], v['password'],
v['ip'], v['port'])
else:
uri = 'http://{0}:{1}'.format(v['ip'], v['port'])
# Get the server URI
uri = self.__get_uri(v)
# Try to connect to the server
t = GlancesClientTransport()
@ -146,46 +151,48 @@ class GlancesClientBrowser(object):
"Server list dictionnary change inside the loop (wait next update)")
# Update the screen (list or Glances client)
if self.screen.get_active() is None:
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: %s" % self.get_servers_list()[self.screen.get_active()])
logger.debug("Selected server: {0}".format(self.get_servers_list()[self.screen.active_server]))
# Connection can take time
# Display a popup
self.screen.display_popup(_("Connect to %s:%s" % (v['name'], v['port'])), duration=1)
self.screen.display_popup(
'Connect to {0}:{1}'.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.get_active()]['password'] is None:
if self.get_servers_list()[self.screen.active_server]['password'] is None:
from hashlib import sha256
# Display a popup to enter password
clear_password = self.screen.display_popup(_("Password needed for %s: " % v['name']), is_input=True)
clear_password = self.screen.display_popup(
'Password needed for {0}: '.format(v['name']), is_input=True)
# Hash with SHA256
encoded_password = sha256(clear_password).hexdigest()
encoded_password = sha256(clear_password.encode('utf-8')).hexdigest()
# Store the password for the selected server
self.set_in_selected('password', encoded_password)
# Display the Glance client on the selected server
logger.info("Connect Glances client to the %s server" %
self.get_servers_list()[self.screen.get_active()]['key'])
logger.info("Connect Glances client to the {0} 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.get_active()]['ip']
args_server.port = self.get_servers_list()[self.screen.get_active()]['port']
args_server.username = self.get_servers_list()[self.screen.get_active()]['username']
args_server.password = self.get_servers_list()[self.screen.get_active()]['password']
client = GlancesClient(config=self.config,
args=args_server,
return_to_browser=True)
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 %s (see log file for additional information)" % v['name']))
self.screen.display_popup(
"Sorry, cannot connect to '{0}'\n"
"See 'glances.log' for more details".format(v['name']))
# Set the ONLINE status for the selected server
self.set_in_selected('status', 'OFFLINE')
@ -195,8 +202,8 @@ class GlancesClientBrowser(object):
connection_type = client.serve_forever()
try:
logger.debug("Disconnect Glances client from the %s server" %
self.get_servers_list()[self.screen.get_active()]['key'])
logger.debug("Disconnect Glances client from the {0} server".format(
self.get_servers_list()[self.screen.active_server]['key']))
except IndexError:
# Server did not exist anymore
pass
@ -208,19 +215,28 @@ class GlancesClientBrowser(object):
self.set_in_selected('status', 'ONLINE')
# Return to the browser (no server selected)
self.screen.set_active(None)
self.screen.active_server = None
def serve_forever(self):
"""Wrapper to the serve_forever function.
This function will restore the terminal to a sane state
before re-raising the exception and generating a traceback.
"""
try:
return self.__serve_forever()
finally:
self.end()
def set_in_selected(self, key, value):
"""Set the (key, value) for the selected server in the list"""
"""Set the (key, value) for the selected server in the list."""
# Static list then dynamic one
if self.screen.get_active() >= len(self.static_server.get_servers_list()):
self.autodiscover_server.set_server(self.screen.get_active() - len(self.static_server.get_servers_list()),
key,
value)
if self.screen.active_server >= len(self.static_server.get_servers_list()):
self.autodiscover_server.set_server(
self.screen.active_server - len(self.static_server.get_servers_list()),
key, value)
else:
self.static_server.set_server(self.screen.get_active(),
key,
value)
self.static_server.set_server(self.screen.active_server, key, value)
def end(self):
"""End of the client browser session."""

View File

@ -23,10 +23,10 @@
import os
import sys
try:
from configparser import RawConfigParser
from configparser import ConfigParser
from configparser import NoOptionError
except ImportError: # Python 2
from ConfigParser import RawConfigParser
from ConfigParser import SafeConfigParser as ConfigParser
from ConfigParser import NoOptionError
# Import Glances lib
@ -37,8 +37,7 @@ from glances.core.glances_globals import (
is_mac,
is_py3,
is_windows,
sys_prefix,
work_path
sys_prefix
)
from glances.core.glances_logging import logger
@ -47,96 +46,177 @@ class Config(object):
"""This class is used to access/read config file, if it exists.
:param location: the custom path to search for config file
:type location: str or None
:param config_dir: the path to search for config file
:type config_dir: str or None
"""
def __init__(self, location=None):
self.location = location
def __init__(self, config_dir=None):
self.config_dir = config_dir
self.config_filename = 'glances.conf'
self.parser = RawConfigParser()
self._loaded_config_file = None
self.load()
def load(self):
"""Load a config file from the list of paths, if it exists."""
for config_file in self.get_config_paths():
if os.path.isfile(config_file) and os.path.getsize(config_file) > 0:
try:
if is_py3:
self.parser.read(config_file, encoding='utf-8')
else:
self.parser.read(config_file)
logger.info("Read configuration file '{0}'".format(config_file))
except UnicodeDecodeError as e:
logger.error("Cannot decode configuration file '{0}': {1}".format(config_file, e))
sys.exit(1)
# Save the loaded configuration file path (issue #374)
self._loaded_config_file = config_file
break
self.parser = ConfigParser()
self.read()
def get_loaded_config_file(self):
"""Return the loaded configuration file"""
return self._loaded_config_file
def get_config_paths(self):
def config_file_paths(self):
r"""Get a list of config file paths.
The list is built taking into account of the OS, priority and location.
* running from source: /path/to/glances/conf
* per-user install: ~/.local/etc/glances (Unix-like only)
* custom path: /path/to/glances
* Linux: ~/.config/glances, /etc/glances
* BSD: ~/.config/glances, /usr/local/etc/glances
* Mac: ~/Library/Application Support/glances, /usr/local/etc/glances
* OS X: ~/Library/Application Support/glances, /usr/local/etc/glances
* Windows: %APPDATA%\glances
The config file will be searched in the following order of priority:
* /path/to/file (via -C flag)
* /path/to/glances/conf
* user's local directory (per-user install settings)
* user's home directory (per-user settings)
* {/usr/local,}/etc directory (system-wide settings)
* system-wide directory (system-wide settings)
"""
paths = []
conf_path = os.path.realpath(
os.path.join(work_path, '..', '..', 'conf'))
if self.location is not None:
paths.append(self.location)
if os.path.exists(conf_path):
paths.append(os.path.join(conf_path, self.config_filename))
if not is_windows:
paths.append(os.path.join(os.path.expanduser('~/.local'), 'etc', appname, self.config_filename))
if self.config_dir:
paths.append(self.config_dir)
if is_linux or is_bsd:
paths.append(os.path.join(
os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser(
'~/.config'),
paths.append(
os.path.join(os.environ.get('XDG_CONFIG_HOME') or
os.path.expanduser('~/.config'),
appname, self.config_filename))
if hasattr(sys, 'real_prefix') or is_bsd:
if is_bsd:
paths.append(
os.path.join(sys.prefix, 'etc', appname, self.config_filename))
else:
paths.append(
os.path.join('/etc', appname, self.config_filename))
elif is_mac:
paths.append(os.path.join(
os.path.expanduser('~/Library/Application Support/'),
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))
paths.append(
os.path.join(sys_prefix, 'etc', appname, self.config_filename))
elif is_windows:
paths.append(os.path.join(
os.environ.get('APPDATA'), appname, self.config_filename))
paths.append(
os.path.join(os.environ.get('APPDATA'), appname, self.config_filename))
return paths
def read(self):
"""Read the config file, if it exists. Using defaults otherwise."""
for config_file in self.config_file_paths():
if os.path.exists(config_file):
try:
if is_py3:
self.parser.read(config_file, encoding='utf-8')
else:
self.parser.read(config_file)
logger.info("Read configuration file '{0}'".format(config_file))
except UnicodeDecodeError as err:
logger.error("Cannot decode configuration file '{0}': {1}".format(config_file, err))
sys.exit(1)
# Save the loaded configuration file path (issue #374)
self._loaded_config_file = config_file
break
# Quicklook
if not self.parser.has_section('quicklook'):
self.parser.add_section('quicklook')
self.parser.set('quicklook', 'cpu_careful', '50')
self.parser.set('quicklook', 'cpu_warning', '70')
self.parser.set('quicklook', 'cpu_critical', '90')
self.parser.set('quicklook', 'mem_careful', '50')
self.parser.set('quicklook', 'mem_warning', '70')
self.parser.set('quicklook', 'mem_critical', '90')
self.parser.set('quicklook', 'swap_careful', '50')
self.parser.set('quicklook', 'swap_warning', '70')
self.parser.set('quicklook', 'swap_critical', '90')
# CPU
if not self.parser.has_section('cpu'):
self.parser.add_section('cpu')
self.parser.set('cpu', 'user_careful', '50')
self.parser.set('cpu', 'user_warning', '70')
self.parser.set('cpu', 'user_critical', '90')
self.parser.set('cpu', 'iowait_careful', '50')
self.parser.set('cpu', 'iowait_warning', '70')
self.parser.set('cpu', 'iowait_critical', '90')
self.parser.set('cpu', 'system_careful', '50')
self.parser.set('cpu', 'system_warning', '70')
self.parser.set('cpu', 'system_critical', '90')
self.parser.set('cpu', 'steal_careful', '50')
self.parser.set('cpu', 'steal_warning', '70')
self.parser.set('cpu', 'steal_critical', '90')
# Per-CPU
if not self.parser.has_section('percpu'):
self.parser.add_section('percpu')
self.parser.set('percpu', 'user_careful', '50')
self.parser.set('percpu', 'user_warning', '70')
self.parser.set('percpu', 'user_critical', '90')
self.parser.set('percpu', 'iowait_careful', '50')
self.parser.set('percpu', 'iowait_warning', '70')
self.parser.set('percpu', 'iowait_critical', '90')
self.parser.set('percpu', 'system_careful', '50')
self.parser.set('percpu', 'system_warning', '70')
self.parser.set('percpu', 'system_critical', '90')
# Load
if not self.parser.has_section('load'):
self.parser.add_section('load')
self.parser.set('load', 'careful', '0.7')
self.parser.set('load', 'warning', '1.0')
self.parser.set('load', 'critical', '5.0')
# Mem
if not self.parser.has_section('mem'):
self.parser.add_section('mem')
self.parser.set('mem', 'careful', '50')
self.parser.set('mem', 'warning', '70')
self.parser.set('mem', 'critical', '90')
# Swap
if not self.parser.has_section('memswap'):
self.parser.add_section('memswap')
self.parser.set('memswap', 'careful', '50')
self.parser.set('memswap', 'warning', '70')
self.parser.set('memswap', 'critical', '90')
# FS
if not self.parser.has_section('fs'):
self.parser.add_section('fs')
self.parser.set('fs', 'careful', '50')
self.parser.set('fs', 'warning', '70')
self.parser.set('fs', 'critical', '90')
# Sensors
if not self.parser.has_section('sensors'):
self.parser.add_section('sensors')
self.parser.set('sensors', 'temperature_core_careful', '60')
self.parser.set('sensors', 'temperature_core_warning', '70')
self.parser.set('sensors', 'temperature_core_critical', '80')
self.parser.set('sensors', 'temperature_hdd_careful', '45')
self.parser.set('sensors', 'temperature_hdd_warning', '52')
self.parser.set('sensors', 'temperature_hdd_critical', '60')
self.parser.set('sensors', 'battery_careful', '80')
self.parser.set('sensors', 'battery_warning', '90')
self.parser.set('sensors', 'battery_critical', '95')
# Process list
if not self.parser.has_section('processlist'):
self.parser.add_section('processlist')
self.parser.set('processlist', 'cpu_careful', '50')
self.parser.set('processlist', 'cpu_warning', '70')
self.parser.set('processlist', 'cpu_critical', '90')
self.parser.set('processlist', 'mem_careful', '50')
self.parser.set('processlist', 'mem_warning', '70')
self.parser.set('processlist', 'mem_critical', '90')
@property
def loaded_config_file(self):
"""Return the loaded configuration file."""
return self._loaded_config_file
def items(self, section):
"""Return the items list of a section."""
return self.parser.items(section)
@ -145,20 +225,16 @@ class Config(object):
"""Return info about the existence of a section."""
return self.parser.has_section(section)
def get_option(self, section, option):
def get_value(self, section, option, default=None):
"""Get the value of an option, if it exists."""
try:
return self.parser.get(section, option)
except NoOptionError:
return default
def get_float_value(self, section, option, default=0.0):
"""Get the float value of an option, if it exists."""
try:
value = self.parser.getfloat(section, option)
return self.parser.getfloat(section, option)
except NoOptionError:
return
else:
return value
def get_raw_option(self, section, option):
"""Get the raw value of an option, if it exists."""
try:
value = self.parser.get(section, option)
except NoOptionError:
return
else:
return value
return float(default)

View File

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
#
# 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 <http://www.gnu.org/licenses/>.
"""CPU percent stats shared between CPU and Quicklook plugins."""
from glances.core.glances_timer import Timer
import psutil
class CpuPercent(object):
"""Get and store the CPU percent."""
def __init__(self, cached_time=1):
self.cpu_percent = 0
# cached_time is the minimum time interval between stats updates
# since last update is passed (will retrieve old cached info instead)
self.timer = Timer(0)
self.cached_time = cached_time
def get(self):
"""Update and/or return the CPU using the psutil library."""
# Never update more than 1 time per cached_time
if self.timer.finished():
self.cpu_percent = psutil.cpu_percent(interval=0.0)
self.timer = Timer(self.cached_time)
return self.cpu_percent
# CpuPercent instance shared between plugins
cpu_percent = CpuPercent()

View File

@ -51,15 +51,3 @@ exports_path = os.path.realpath(os.path.join(work_path, '..', 'exports'))
sys_path = sys.path[:]
sys.path.insert(1, plugins_path)
sys.path.insert(1, exports_path)
def get_locale_path(paths):
for path in paths:
if os.path.exists(path):
return path
# i18n
gettext_domain = appname
i18n_path = os.path.realpath(os.path.join(work_path, '..', '..', 'i18n'))
user_i18n_path = os.path.join(os.path.expanduser('~/.local'), 'share', 'locale')
sys_i18n_path = os.path.join(sys_prefix, 'share', 'locale')
locale_dir = get_locale_path([i18n_path, user_i18n_path, sys_i18n_path])

View File

@ -17,10 +17,15 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Custom logging class"""
"""Custom logging class."""
import logging
import logging.config
try:
# Python 2.6
from logutils.dictconfig import dictConfig
except ImportError:
# Python >= 2.7
from logging.config import dictConfig
import os
import tempfile
@ -72,29 +77,24 @@ LOGGING_CFG = {
def tempfile_name():
"""Return the tempfile name (full path)"""
"""Return the tempfile name (full path)."""
ret = os.path.join(tempfile.gettempdir(), 'glances.log')
if os.access(ret, os.F_OK) and not os.access(ret, os.W_OK):
print("Warning: can't write logs to file {} (permission denied)".format(ret))
print("WARNING: Couldn't write to log file {0}: (Permission denied)".format(ret))
ret = tempfile.mkstemp(prefix='glances', suffix='.tmp', text=True)
print("Create a new log file: {}".format(ret[1]))
print("Create a new log file: {0}".format(ret[1]))
return ret[1]
return ret
def glances_logger():
"""Build and return the logger"""
"""Build and return the logger."""
temp_path = tempfile_name()
_logger = logging.getLogger()
try:
LOGGING_CFG['handlers']['file']['filename'] = temp_path
logging.config.dictConfig(LOGGING_CFG)
except AttributeError:
# dictConfig is only available for Python 2.7 or higher
# Minimal configuration for Python 2.6
logging.basicConfig(filename=temp_path,
level=logging.DEBUG,
format='%(asctime)s -- %(levelname)s -- %(message)s')
dictConfig(LOGGING_CFG)
return _logger
logger = glances_logger()

View File

@ -87,29 +87,26 @@ class GlancesLogs(object):
else:
# Default sort is...
process_auto_by = 'cpu_percent'
glances_processes.setautosortkey(process_auto_by)
return process_auto_by
glances_processes.auto_sort = True
glances_processes.sort_key = process_auto_by
def reset_process_sort(self):
"""Reset the process_auto_by variable."""
"""Reset the process auto sort key."""
# Default sort is...
process_auto_by = 'cpu_percent'
glances_processes.setautosortkey(process_auto_by)
glances_processes.setmanualsortkey(None)
return process_auto_by
glances_processes.auto_sort = True
glances_processes.sort_key = 'cpu_percent'
def add(self, item_state, item_type, item_value,
proc_list=[], proc_desc="",
peak_time=3):
proc_list=None, proc_desc="", peak_time=6):
"""Add a new item to the logs list.
If 'item' is a 'new one', add the new item at the beginning of the logs
list.
If 'item' is a 'new one', add the new item at the beginning of
the logs list.
If 'item' is not a 'new one', update the existing item.
If event < peak_time the the alert is not setoff
If event < peak_time the the alert is not setoff.
"""
proc_list = proc_list or []
# Add or update the log
item_index = self.__itemexist__(item_type)
if item_index < 0:
@ -121,24 +118,23 @@ class GlancesLogs(object):
# Create the new log item
# Time is stored in Epoch format
# Epoch -> DMYHMS = datetime.fromtimestamp(epoch)
item = []
# START DATE
item.append(time.mktime(datetime.now().timetuple()))
item.append(-1) # END DATE
item.append(item_state) # STATE: WARNING|CRITICAL
item.append(item_type) # TYPE: CPU, LOAD, MEM...
item.append(item_value) # MAX
item.append(item_value) # AVG
item.append(item_value) # MIN
item.append(item_value) # SUM
item.append(1) # COUNT
# Process list is sorted automaticaly
# Overwrite the user choise
item = [
time.mktime(datetime.now().timetuple()), # START DATE
-1, # END DATE
item_state, # STATE: WARNING|CRITICAL
item_type, # TYPE: CPU, LOAD, MEM...
item_value, # MAX
item_value, # AVG
item_value, # MIN
item_value, # SUM
1, # COUNT
# Process list is sorted automatically
# Overwrite the user choice
# topprocess = sorted(proc_list, key=lambda process: process[process_auto_by],
# reverse=True)
# item.append(topprocess[0:3]) # TOP 3 PROCESS LIST
item.append([]) # TOP 3 PROCESS LIST
item.append(proc_desc) # MONITORED PROCESSES DESC
# topprocess[0:3], # TOP 3 PROCESS LIST
[], # TOP 3 PROCESS LIST
proc_desc] # MONITORED PROCESSES DESC
# Add the item to the list
self.logs_list.insert(0, item)
@ -190,12 +186,12 @@ class GlancesLogs(object):
def clean(self, critical=False):
"""Clean the logs list by deleting finished items.
By default, only delete WARNING message
If critical = True, also delete CRITICAL message
By default, only delete WARNING message.
If critical = True, also delete CRITICAL message.
"""
# Create a new clean list
clean_logs_list = []
while (self.len() > 0):
while self.len() > 0:
item = self.logs_list.pop()
if item[1] < 0 or (not critical and item[2].startswith("CRITICAL")):
clean_logs_list.insert(0, item)

View File

@ -27,7 +27,7 @@ import tempfile
# Import Glances libs
from glances.core.glances_config import Config
from glances.core.glances_globals import appname, is_windows, psutil_version, version
from glances.core.glances_globals import appname, is_linux, is_windows, psutil_version, version
from glances.core.glances_logging import logger
@ -96,94 +96,104 @@ Start the client browser (browser mode):\n\
parser.add_argument(
'-V', '--version', action='version', version=_version)
parser.add_argument('-d', '--debug', action='store_true', default=False,
dest='debug', help=_('Enable debug mode'))
dest='debug', help='enable debug mode')
parser.add_argument('-C', '--config', dest='conf_file',
help=_('path to the configuration file'))
help='path to the configuration file')
# Enable or disable option on startup
parser.add_argument('--disable-network', action='store_true', default=False,
dest='disable_network', help=_('disable network module'))
dest='disable_network', help='disable network 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'))
dest='disable_diskio', help='disable disk I/O module')
parser.add_argument('--disable-fs', action='store_true', default=False,
dest='disable_fs', help=_('disable filesystem module'))
dest='disable_fs', help='disable filesystem module')
parser.add_argument('--disable-sensors', action='store_true', default=False,
dest='disable_sensors', help=_('disable sensors module'))
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-raid', action='store_true', default=False,
dest='disable_raid', help=_('disable RAID module'))
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('--disable-left-sidebar', action='store_true', default=False,
dest='disable_left_sidebar', help=_('disable network, disk io, FS and sensors modules (need Py3Sensors lib)'))
dest='disable_docker', help='disable Docker module')
parser.add_argument('--disable-left-sidebar', action='store_true',
default=False, dest='disable_left_sidebar',
help='disable network, disk I/O, FS and sensors modules (py3sensors needed)')
parser.add_argument('--disable-process', action='store_true', default=False,
dest='disable_process', help=_('disable process module'))
dest='disable_process', help='disable process module')
parser.add_argument('--disable-log', action='store_true', default=False,
dest='disable_log', help=_('disable log module'))
dest='disable_log', help='disable log module')
parser.add_argument('--disable-quicklook', action='store_true', default=False,
dest='disable_quicklook', help='disable quick look module')
parser.add_argument('--disable-bold', action='store_false', default=True,
dest='disable_bold', help=_('disable bold mode in the terminal'))
dest='disable_bold', help='disable bold mode in the terminal')
parser.add_argument('--enable-process-extended', action='store_true', default=False,
dest='enable_process_extended', help=_('enable extended stats on top process'))
dest='enable_process_extended', help='enable extended stats on top process')
parser.add_argument('--enable-history', action='store_true', default=False,
dest='enable_history', help=_('enable the history mode (need MatPlotLib lib)'))
dest='enable_history', help='enable the history mode (matplotlib needed)')
parser.add_argument('--path-history', default=tempfile.gettempdir(),
dest='path_history', help=_('Set the export path for graph history'))
dest='path_history', help='set the export path for graph history')
# Export modules feature
parser.add_argument('--export-csv', default=None,
dest='export_csv', help=_('export stats to a CSV file'))
dest='export_csv', help='export stats to a CSV file')
parser.add_argument('--export-influxdb', action='store_true', default=False,
dest='export_influxdb', help=_('export stats to an InfluxDB server (need InfluDB lib)'))
dest='export_influxdb', help='export stats to an InfluxDB server (influxdb needed)')
parser.add_argument('--export-statsd', action='store_true', default=False,
dest='export_statsd', help=_('export stats to a Statsd server (need StatsD lib)'))
dest='export_statsd', help='export stats to a StatsD server (statsd needed)')
parser.add_argument('--export-rabbitmq', action='store_true', default=False,
dest='export_rabbitmq', help='export stats to rabbitmq broker (pika needed)')
# Client/Server option
parser.add_argument('-c', '--client', dest='client',
help=_('connect to a Glances server by IPv4/IPv6 address or hostname'))
help='connect to a Glances server by IPv4/IPv6 address or hostname')
parser.add_argument('-s', '--server', action='store_true', default=False,
dest='server', help=_('run Glances in server mode'))
dest='server', help='run Glances in server mode')
parser.add_argument('--browser', action='store_true', default=False,
dest='browser', help=_('start the client browser (list of servers)'))
dest='browser', help='start the client browser (list of servers)')
parser.add_argument('--disable-autodiscover', action='store_true', default=False,
dest='disable_autodiscover', help=_('disable autodiscover feature'))
dest='disable_autodiscover', help='disable autodiscover feature')
parser.add_argument('-p', '--port', default=None, type=int, dest='port',
help=_('define the client/server TCP port [default: {0}]').format(self.server_port))
help='define the client/server TCP port [default: {0}]'.format(self.server_port))
parser.add_argument('-B', '--bind', default='0.0.0.0', dest='bind_address',
help=_('bind server to the given IPv4/IPv6 address or hostname'))
parser.add_argument('--password-badidea', dest='password_arg',
help=_('define password from the command line'))
help='bind server to the given IPv4/IPv6 address or hostname')
parser.add_argument('--password', action='store_true', default=False, dest='password_prompt',
help=_('define a client/server password from the prompt or file'))
help='define a client/server password')
parser.add_argument('--snmp-community', default='public', dest='snmp_community',
help=_('SNMP community'))
help='SNMP community')
parser.add_argument('--snmp-port', default=161, type=int,
dest='snmp_port', help=_('SNMP port'))
dest='snmp_port', help='SNMP port')
parser.add_argument('--snmp-version', default='2c', dest='snmp_version',
help=_('SNMP version (1, 2c or 3)'))
help='SNMP version (1, 2c or 3)')
parser.add_argument('--snmp-user', default='private', dest='snmp_user',
help=_('SNMP username (only for SNMPv3)'))
help='SNMP username (only for SNMPv3)')
parser.add_argument('--snmp-auth', default='password', dest='snmp_auth',
help=_('SNMP authentication key (only for SNMPv3)'))
help='SNMP authentication key (only for SNMPv3)')
parser.add_argument('--snmp-force', action='store_true', default=False,
dest='snmp_force', help=_('force SNMP mode'))
dest='snmp_force', help='force SNMP mode')
parser.add_argument('-t', '--time', default=self.refresh_time, type=float,
dest='time', help=_('set refresh time in seconds [default: {0} sec]').format(self.refresh_time))
dest='time', help='set refresh time in seconds [default: {0} sec]'.format(self.refresh_time))
parser.add_argument('-w', '--webserver', action='store_true', default=False,
dest='webserver', help=_('run Glances in web server mode (need Bootle lib)'))
dest='webserver', help='run Glances in web server mode (bottle needed)')
# Display options
parser.add_argument('-q', '--quiet', default=False, action='store_true',
dest='quiet', help='do not display the curses interface')
parser.add_argument('-f', '--process-filter', default=None, type=str,
dest='process_filter', help=_('set the process filter pattern (regular expression)'))
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'))
dest='process_short_name', help='force short name for processes name')
if not is_windows:
parser.add_argument('--hide-kernel-threads', action='store_true', default=False,
dest='no_kernel_threads', help=_('hide kernel threads in process list'))
dest='no_kernel_threads', help='hide kernel threads in process list')
if is_linux:
parser.add_argument('--tree', action='store_true', default=False,
dest='process_tree', help=_('display processes as a tree'))
dest='process_tree', help='display processes as a tree')
parser.add_argument('-b', '--byte', action='store_true', default=False,
dest='byte', help=_('display network rate in byte per second'))
dest='byte', help='display network rate in byte per second')
parser.add_argument('-1', '--percpu', action='store_true', default=False,
dest='percpu', help=_('start Glances in per CPU mode'))
dest='percpu', help='start Glances in per CPU mode')
parser.add_argument('--fs-free-space', action='store_false', default=False,
dest='fs_free_space', help=_('display FS free space instead of used'))
dest='fs_free_space', help='display FS free space instead of used')
parser.add_argument('--theme-white', action='store_true', default=False,
dest='theme_white', help=_('optimize display for white background'))
dest='theme_white', help='optimize display colors for white background')
return parser
@ -217,22 +227,15 @@ Start the client browser (browser mode):\n\
# Server or client login/password
args.username = self.username
if args.password_arg is not None:
from hashlib import sha256
# Password is given as an argument
# Hash with SHA256
# Only the SHA will be transmit on the network
args.password = sha256(args.password_arg).hexdigest()
elif args.password_prompt:
if args.password_prompt:
# Interactive or file password
if args.server:
args.password = self.__get_password(
description=_(
"Define the password for the Glances server"),
description='Define the password for the Glances server',
confirm=True)
elif args.client:
args.password = self.__get_password(
description=_("Enter the Glances server password"),
description='Enter the Glances server password',
clear=True)
else:
# Default is no password
@ -260,6 +263,11 @@ Start the client browser (browser mode):\n\
sys.exit(2)
logger.debug("History output path is set to {0}".format(args.path_history))
# Disable HDDTemp if sensors are disabled
if args.disable_sensors:
args.disable_hddtemp = True
logger.debug("Sensors and HDDTemp are disabled")
return args
def __hash_password(self, plain_password):

View File

@ -49,11 +49,12 @@ class MonitorList(object):
__monitor_list = []
def __init__(self, config):
"""Init the monitoring list from the configuration file."""
"""Init the monitoring list from the configuration file, if it exists."""
self.config = config
if self.config is not None and self.config.has_section('monitor'):
# Process monitoring list
logger.debug("Monitor list configuration detected")
self.__set_monitor_list('monitor', 'list')
else:
self.__monitor_list = []
@ -67,14 +68,13 @@ class MonitorList(object):
value = {}
key = "list_" + str(l) + "_"
try:
description = self.config.get_raw_option(section, key + "description")
regex = self.config.get_raw_option(section, key + "regex")
command = self.config.get_raw_option(section, key + "command")
countmin = self.config.get_raw_option(section, key + "countmin")
countmax = self.config.get_raw_option(section, key + "countmax")
description = self.config.get_value(section, key + 'description')
regex = self.config.get_value(section, key + 'regex')
command = self.config.get_value(section, key + 'command')
countmin = self.config.get_value(section, key + 'countmin')
countmax = self.config.get_value(section, key + 'countmax')
except Exception as e:
logger.error("Cannot read monitored list: {0}".format(e))
pass
else:
if description is not None and regex is not None:
# Build the new item
@ -127,26 +127,26 @@ class MonitorList(object):
# Iter upon the monitored list
for i in range(0, len(self.get())):
# Search monitored processes by a regular expression
processlist = glances_processes.getlist()
processlist = glances_processes.getalllist()
monitoredlist = [p for p in processlist if re.search(self.regex(i), p['cmdline']) is not None]
self.__monitor_list[i]['count'] = len(monitoredlist)
if self.command(i) is None:
# If there is no command specified in the conf file
# then display CPU and MEM %
self.__monitor_list[i]['result'] = 'CPU: {0:.1f}% | MEM: {1:.1f}%'.format(
sum([p['cpu_percent'] for p in monitoredlist]),
sum([p['memory_percent'] for p in monitoredlist]))
continue
else:
if self.command(i) is not None:
# Execute the user command line
try:
self.__monitor_list[i]['result'] = subprocess.check_output(self.command(i),
shell=True)
except subprocess.CalledProcessError:
self.__monitor_list[i]['result'] = _("Error: ") + self.command(i)
self.__monitor_list[i]['result'] = 'Error: ' + self.command(i)
except Exception:
self.__monitor_list[i]['result'] = _("Cannot execute command")
self.__monitor_list[i]['result'] = 'Cannot execute command'
if self.command(i) is None or self.__monitor_list[i]['result'] == '':
# If there is no command specified in the conf file
# then display CPU and MEM %
self.__monitor_list[i]['result'] = 'CPU: {0:.1f}% | MEM: {1:.1f}%'.format(
sum([p['cpu_percent'] for p in monitoredlist]),
sum([p['memory_percent'] for p in monitoredlist]))
return self.__monitor_list

View File

@ -110,11 +110,11 @@ class GlancesPassword(object):
# password_plain is the plain SHA-256 password
# password_hashed is the salt + SHA-256 password
password_sha = hashlib.sha256(getpass.getpass(_("Password: ")).encode('utf-8')).hexdigest()
password_sha = hashlib.sha256(getpass.getpass('Password: ').encode('utf-8')).hexdigest()
password_hashed = self.hash_password(password_sha)
if confirm:
# password_confirm is the clear password (only used to compare)
password_confirm = hashlib.sha256(getpass.getpass(_("Password (confirm): ")).encode('utf-8')).hexdigest()
password_confirm = hashlib.sha256(getpass.getpass('Password (confirm): ').encode('utf-8')).hexdigest()
if not self.check_password(password_hashed, password_confirm):
logger.critical("Sorry, passwords do not match. Exit.")
@ -128,8 +128,8 @@ class GlancesPassword(object):
# Save the hashed password to the password file
if not clear:
save_input = input(_("Do you want to save the password? [Yes/No]: "))
if len(save_input) > 0 and save_input[0].upper() == _('Y'):
save_input = input('Do you want to save the password? [Yes/No]: ')
if len(save_input) > 0 and save_input[0].upper() == 'Y':
self.save_password(password_hashed)
return password

View File

@ -19,47 +19,47 @@
# Import Python lib
import collections
import operator
import os
import re
# Import psutil
import psutil
# Import Glances lib
from glances.core.glances_globals import is_bsd, is_linux, is_mac, is_windows
from glances.core.glances_logging import logger
from glances.core.glances_timer import getTimeSinceLastUpdate, Timer
from glances.core.glances_timer import Timer, getTimeSinceLastUpdate
# Import psutil
import psutil
def is_kernel_thread(proc):
""" Return True if proc is a kernel thread, False instead. """
"""Return True if proc is a kernel thread, False instead."""
try:
return os.getpgid(proc.pid) == 0
except OSError: # Python >= 3.3 raises ProcessLookupError, which inherits OSError
# Python >= 3.3 raises ProcessLookupError, which inherits OSError
except OSError:
# return False is process is dead
return False
class ProcessTreeNode(object):
"""
Represent a process tree.
"""Represent a process tree.
We avoid recursive algorithm to manipulate the tree because function calls are expensive with CPython.
We avoid recursive algorithm to manipulate the tree because function
calls are expensive with CPython.
"""
def __init__(self, process=None, stats=None, sort_key=None, root=False):
def __init__(self, process=None, stats=None, sort_key=None, sort_reverse=True, root=False):
self.process = process
self.stats = stats
self.children = []
self.children_sorted = False
self.sort_key = sort_key
self.reverse_sorting = (self.sort_key != "name")
self.sort_reverse = sort_reverse
self.is_root = root
def __str__(self):
""" Return the tree as a string for debugging. """
"""Return the tree as a string for debugging."""
lines = []
nodes_to_print = collections.deque([collections.deque([("#", self)])])
while nodes_to_print:
@ -69,7 +69,8 @@ class ProcessTreeNode(object):
if current_node.is_root:
lines.append(indent_str)
else:
lines.append("%s[%s]" % (indent_str, current_node.process.name()))
lines.append("%s[%s]" %
(indent_str, current_node.process.name()))
indent_str = " " * (len(lines[-1]) - 1)
children_nodes_to_print = collections.deque()
for child in current_node.children:
@ -77,14 +78,18 @@ class ProcessTreeNode(object):
tree_char = "└─"
else:
tree_char = "├─"
children_nodes_to_print.appendleft((indent_str + tree_char, child))
children_nodes_to_print.appendleft(
(indent_str + tree_char, child))
if children_nodes_to_print:
nodes_to_print.append(children_nodes_to_print)
return "\n".join(lines)
def set_sorting(self, key, reverse):
""" Set sorting key or func for user with __iter__ (affects the whole tree from this node). """
if (self.sort_key != key) or (self.reverse_sorting != reverse):
"""Set sorting key or func for use with __iter__.
This affects the whole tree from this node.
"""
if self.sort_key != key or self.sort_reverse != reverse:
nodes_to_flag_unsorted = collections.deque([self])
while nodes_to_flag_unsorted:
current_node = nodes_to_flag_unsorted.pop()
@ -94,8 +99,8 @@ class ProcessTreeNode(object):
nodes_to_flag_unsorted.extend(current_node.children)
def get_weight(self):
""" Return "weight" of a process and all its children for sorting. """
if self.sort_key == "name":
"""Return 'weight' of a process and all its children for sorting."""
if self.sort_key == 'name' or self.sort_key == 'username':
return self.stats[self.sort_key]
# sum ressource usage for self and children
@ -128,53 +133,63 @@ class ProcessTreeNode(object):
return total
def __iter__(self):
""" Iterator returning ProcessTreeNode in sorted order, recursively. """
"""Iterator returning ProcessTreeNode in sorted order, recursively."""
if not self.is_root:
yield self
if not self.children_sorted:
# optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for,
# and once before displaying)
self.children.sort(key=self.__class__.get_weight, reverse=self.reverse_sorting)
self.children.sort(
key=self.__class__.get_weight, reverse=self.sort_reverse)
self.children_sorted = True
for child in self.children:
for n in iter(child):
yield n
def iter_children(self, exclude_incomplete_stats=True):
"""
Iterator returning ProcessTreeNode in sorted order (only children of this node, non recursive).
"""Iterator returning ProcessTreeNode in sorted order.
If exclude_incomplete_stats is True, exclude processes not having full statistics.
It can happen after a resort (change of sort key) because process stats are not grabbed immediately,
but only at next full update.
Return only children of this node, non recursive.
If exclude_incomplete_stats is True, exclude processes not
having full statistics. It can happen after a resort (change of
sort key) because process stats are not grabbed immediately, but
only at next full update.
"""
if not self.children_sorted:
# optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for,
# and once before displaying)
self.children.sort(key=self.__class__.get_weight, reverse=self.reverse_sorting)
self.children.sort(
key=self.__class__.get_weight, reverse=self.sort_reverse)
self.children_sorted = True
for child in self.children:
if (not exclude_incomplete_stats) or ("time_since_update" in child.stats):
if not exclude_incomplete_stats or "time_since_update" in child.stats:
yield child
def find_process(self, process):
""" Search in tree for the ProcessTreeNode owning process, return it or None if not found. """
"""Search in tree for the ProcessTreeNode owning process.
Return it or None if not found.
"""
nodes_to_search = collections.deque([self])
while nodes_to_search:
current_node = nodes_to_search.pop()
if (not current_node.is_root) and (current_node.process.pid == process.pid):
if not current_node.is_root and current_node.process.pid == process.pid:
return current_node
nodes_to_search.extend(current_node.children)
@staticmethod
def build_tree(process_dict, sort_key, hide_kernel_threads):
""" Build a process tree using using parent/child relationships, and return the tree root node. """
def build_tree(process_dict, sort_key, sort_reverse, hide_kernel_threads):
"""Build a process tree using using parent/child relationships.
Return the tree root node.
"""
tree_root = ProcessTreeNode(root=True)
nodes_to_add_last = collections.deque()
# first pass: add nodes whose parent are in the tree
for process, stats in process_dict.items():
new_node = ProcessTreeNode(process, stats, sort_key)
new_node = ProcessTreeNode(process, stats, sort_key, sort_reverse)
try:
parent_process = process.parent()
except psutil.NoSuchProcess:
@ -192,13 +207,16 @@ class ProcessTreeNode(object):
# parent is not in tree, add this node later
nodes_to_add_last.append(new_node)
# next pass(es): add nodes to their parents if it could not be done in previous pass
# next pass(es): add nodes to their parents if it could not be done in
# previous pass
while nodes_to_add_last:
node_to_add = nodes_to_add_last.popleft() # pop from left and append to right to avoid infinite loop
# pop from left and append to right to avoid infinite loop
node_to_add = nodes_to_add_last.popleft()
try:
parent_process = node_to_add.process.parent()
except psutil.NoSuchProcess:
# parent is dead, consider no parent, add this node at the top level
# parent is dead, consider no parent, add this node at the top
# level
tree_root.children.append(node_to_add)
else:
if parent_process is None:
@ -242,10 +260,11 @@ class GlancesProcesses(object):
self.process_tree = None
# Init stats
self.resetsort()
self.auto_sort = True
self._sort_key = 'cpu_percent'
self.allprocesslist = []
self.processlist = []
self.processcount = {
'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0}
self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0}
# Tag to enable/disable the processes stats (to reduce the Glances CPU consumption)
# Default is to enable the processes stats
@ -254,13 +273,12 @@ class GlancesProcesses(object):
# Extended stats for top process is enable by default
self.disable_extended_tag = False
# Maximum number of processes showed in the UI interface
# None if no limit
self.max_processes = None
# Maximum number of processes showed in the UI (None if no limit)
self._max_processes = None
# Process filter is a regular expression
self.process_filter = None
self.process_filter_re = None
self._process_filter = None
self._process_filter_re = None
# Whether or not to hide kernel threads
self.no_kernel_threads = False
@ -283,86 +301,82 @@ class GlancesProcesses(object):
"""Disable extended process stats."""
self.disable_extended_tag = True
def set_max_processes(self, value):
"""Set the maximum number of processes showed in the UI interfaces"""
self.max_processes = value
return self.max_processes
@property
def max_processes(self):
"""Get the maximum number of processes showed in the UI."""
return self._max_processes
def get_max_processes(self):
"""Get the maximum number of processes showed in the UI interfaces"""
return self.max_processes
@max_processes.setter
def max_processes(self, value):
"""Set the maximum number of processes showed in the UI."""
self._max_processes = value
def set_process_filter(self, value):
"""Set the process filter"""
@property
def process_filter(self):
"""Get the process filter."""
return self._process_filter
@process_filter.setter
def process_filter(self, value):
"""Set the process filter."""
logger.info("Set process filter to {0}".format(value))
self.process_filter = value
self._process_filter = value
if value is not None:
try:
self.process_filter_re = re.compile(value)
logger.debug("Process filter regex compilation OK: {0}".format(self.get_process_filter()))
self._process_filter_re = re.compile(value)
logger.debug("Process filter regex compilation OK: {0}".format(self.process_filter))
except Exception:
logger.error("Cannot compile process filter regex: {0}".format(value))
self.process_filter_re = None
self._process_filter_re = None
else:
self.process_filter_re = None
return self.process_filter
self._process_filter_re = None
def get_process_filter(self):
"""Get the process filter"""
return self.process_filter
def get_process_filter_re(self):
"""Get the process regular expression compiled"""
return self.process_filter_re
@property
def process_filter_re(self):
"""Get the process regular expression compiled."""
return self._process_filter_re
def is_filtered(self, value):
"""Return True if the value should be filtered"""
if self.get_process_filter() is None:
"""Return True if the value should be filtered."""
if self.process_filter is None:
# No filter => Not filtered
return False
else:
# logger.debug(self.get_process_filter() + " <> " + value + " => " + str(self.get_process_filter_re().match(value) is None))
return self.get_process_filter_re().match(value) is None
# logger.debug(self.process_filter + " <> " + value + " => " + str(self.process_filter_re.match(value) is None))
return self.process_filter_re.match(value) is None
def disable_kernel_threads(self):
""" Ignore kernel threads in process list. """
"""Ignore kernel threads in process list."""
self.no_kernel_threads = True
def enable_tree(self):
""" Enable process tree. """
"""Enable process tree."""
self._enable_tree = True
def is_tree_enabled(self):
""" Return True if process tree is enabled, False instead. """
"""Return True if process tree is enabled, False instead."""
return self._enable_tree
def __get_process_stats(self, proc,
mandatory_stats=True,
standard_stats=True,
extended_stats=False):
@property
def sort_reverse(self):
"""Return True to sort processes in reverse 'key' order, False instead."""
if self.sort_key == 'name' or self.sort_key == 'username':
return False
return True
def __get_mandatory_stats(self, proc, procstat):
"""
Get process stats of the proc processes (proc is returned psutil.process_iter())
mandatory_stats: need for the sorting/filter step
Get mandatory_stats: need for the sorting/filter step.
=> cpu_percent, memory_percent, io_counters, name, cmdline
standard_stats: for all the displayed processes
=> username, status, memory_info, cpu_times
extended_stats: only for top processes (see issue #403)
=> connections (UDP/TCP), memory_swap...
"""
# Process ID (always)
procstat = proc.as_dict(attrs=['pid'])
if mandatory_stats:
procstat['mandatory_stats'] = True
# Process CPU, MEM percent and name
try:
procstat.update(
proc.as_dict(attrs=['cpu_percent', 'memory_percent', 'name', 'cpu_times'], ad_value=''))
except psutil.NoSuchProcess:
# Correct issue #414
return None
procstat.update(proc.as_dict(
attrs=['username', 'cpu_percent', 'memory_percent',
'name', 'cpu_times'], ad_value=''))
if procstat['cpu_percent'] == '' or procstat['memory_percent'] == '':
# Do not display process if we cannot get the basic
# cpu_percent or memory_percent stats
@ -391,7 +405,7 @@ class GlancesProcesses(object):
# Get the process IO counters
proc_io = proc.io_counters()
io_new = [proc_io.read_bytes, proc_io.write_bytes]
except (psutil.AccessDenied, psutil.NoSuchProcess):
except (psutil.AccessDenied, psutil.NoSuchProcess, NotImplementedError):
# Access denied to process IO (no root account)
# NoSuchProcess (process die between first and second grab)
# Put 0 in all values (for sort) and io_tag = 0 (for
@ -413,7 +427,14 @@ class GlancesProcesses(object):
# Append the IO tag (for display)
procstat['io_counters'] += [io_tag]
if standard_stats:
return procstat
def __get_standard_stats(self, proc, procstat):
"""
Get standard_stats: for all the displayed processes.
=> username, status, memory_info, cpu_times
"""
procstat['standard_stats'] = True
# Process username (cached with internal cache)
@ -440,7 +461,14 @@ class GlancesProcesses(object):
else:
procstat['status'] = str(procstat['status'])[:1].upper()
if extended_stats and not self.disable_extended_tag:
return procstat
def __get_extended_stats(self, proc, procstat):
"""
Get extended_stats: only for top processes (see issue #403).
=> connections (UDP/TCP), memory_swap...
"""
procstat['extended_stats'] = True
# CPU affinity (Windows and Linux only)
@ -520,18 +548,32 @@ class GlancesProcesses(object):
else:
procstat['ionice'] = None
# logger.debug(procstat)
return procstat
def __get_process_stats(self, proc,
mandatory_stats=True,
standard_stats=True,
extended_stats=False):
"""Get stats of running processes."""
# Process ID (always)
procstat = proc.as_dict(attrs=['pid'])
if mandatory_stats:
procstat = self.__get_mandatory_stats(proc, procstat)
if procstat is not None and standard_stats:
procstat = self.__get_standard_stats(proc, procstat)
if procstat is not None and extended_stats and not self.disable_extended_tag:
procstat = self.__get_extended_stats(proc, procstat)
return procstat
def update(self):
"""
Update the processes stats
"""
"""Update the processes stats."""
# Reset the stats
self.processlist = []
self.processcount = {
'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0}
self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0}
# Do not process if disable tag is set
if self.disable_tag:
@ -544,15 +586,14 @@ class GlancesProcesses(object):
processdict = {}
for proc in psutil.process_iter():
# Ignore kernel threads if needed
if (self.no_kernel_threads and (not is_windows)
and is_kernel_thread(proc)):
if self.no_kernel_threads and not is_windows and is_kernel_thread(proc):
continue
# If self.get_max_processes() is None: Only retreive mandatory stats
# If self.max_processes is None: Only retreive mandatory stats
# Else: retreive mandatory and standard stats
s = self.__get_process_stats(proc,
mandatory_stats=True,
standard_stats=self.get_max_processes() is None)
standard_stats=self.max_processes is None)
# Continue to the next process if it has to be filtered
if s is None or (self.is_filtered(s['cmdline']) and self.is_filtered(s['name'])):
continue
@ -586,12 +627,13 @@ class GlancesProcesses(object):
if self._enable_tree:
self.process_tree = ProcessTreeNode.build_tree(processdict,
self.getsortkey(),
self.sort_key,
self.sort_reverse,
self.no_kernel_threads)
for i, node in enumerate(self.process_tree):
# Only retreive stats for visible processes (get_max_processes)
if (self.get_max_processes() is not None) and (i >= self.get_max_processes()):
# Only retreive stats for visible processes (max_processes)
if self.max_processes is not None and i >= self.max_processes:
break
# add standard stats
@ -607,30 +649,32 @@ class GlancesProcesses(object):
else:
# Process optimization
# Only retreive stats for visible processes (get_max_processes)
if self.get_max_processes() is not None:
# Only retreive stats for visible processes (max_processes)
if self.max_processes is not None:
# Sort the internal dict and cut the top N (Return a list of tuple)
# tuple=key (proc), dict (returned by __get_process_stats)
try:
processiter = sorted(
processdict.items(), key=lambda x: x[1][self.getsortkey()], reverse=True)
processiter = sorted(processdict.items(),
key=lambda x: x[1][self.sort_key],
reverse=self.sort_reverse)
except (KeyError, TypeError) as e:
logger.error("Cannot sort process list by %s (%s)" % (self.getsortkey(), e))
logger.error("Cannot sort process list by {0}: {1}".format(self.sort_key, e))
logger.error("%s" % str(processdict.items()[0]))
# Fallback to all process (issue #423)
processloop = processdict.items()
first = False
else:
processloop = processiter[0:self.get_max_processes()]
processloop = processiter[0:self.max_processes]
first = True
else:
# Get all processes stats
processloop = processdict.items()
first = False
for i in processloop:
# Already existing mandatory stats
procstat = i[1]
if self.get_max_processes() is not None:
if self.max_processes is not None:
# Update with standard stats
# and extended stats but only for TOP (first) process
s = self.__get_process_stats(i[0],
@ -647,6 +691,9 @@ class GlancesProcesses(object):
# Next...
first = False
# Build the all processes list used by the monitored list
self.allprocesslist = processdict.values()
# Clean internals caches if timeout is reached
if self.cache_timer.finished():
self.username_cache = {}
@ -658,6 +705,10 @@ class GlancesProcesses(object):
"""Get the number of processes."""
return self.processcount
def getalllist(self):
"""Get the allprocesslist."""
return self.allprocesslist
def getlist(self, sortedby=None):
"""Get the processlist."""
return self.processlist
@ -666,65 +717,14 @@ class GlancesProcesses(object):
"""Get the process tree."""
return self.process_tree
def getsortkey(self):
"""Get the current sort key"""
if self.getmanualsortkey() is not None:
return self.getmanualsortkey()
else:
return self.getautosortkey()
@property
def sort_key(self):
"""Get the current sort key."""
return self._sort_key
def getmanualsortkey(self):
"""Get the current sort key for manual sort."""
return self.processmanualsort
def getautosortkey(self):
"""Get the current sort key for automatic sort."""
return self.processautosort
def setmanualsortkey(self, sortedby):
"""Set the current sort key for manual sort."""
self.processmanualsort = sortedby
if self._enable_tree and (self.process_tree is not None):
self.process_tree.set_sorting(sortedby, sortedby != "name")
return self.processmanualsort
def setautosortkey(self, sortedby):
"""Set the current sort key for automatic sort."""
self.processautosort = sortedby
return self.processautosort
def resetsort(self):
"""Set the default sort: Auto"""
self.setmanualsortkey(None)
self.setautosortkey('cpu_percent')
def getsortlist(self, sortedby=None):
"""Get the sorted processlist."""
if sortedby is None:
# No need to sort...
return self.processlist
sortedreverse = True
if sortedby == 'name':
sortedreverse = False
if sortedby == 'io_counters':
# Specific case for io_counters
# Sum of io_r + io_w
try:
# Sort process by IO rate (sum IO read + IO write)
self.processlist.sort(key=lambda process: process[sortedby][0] -
process[sortedby][2] + process[sortedby][1] -
process[sortedby][3],
reverse=sortedreverse)
except Exception:
self.processlist.sort(key=operator.itemgetter('cpu_percent'),
reverse=sortedreverse)
else:
# Others sorts
self.processlist.sort(key=operator.itemgetter(sortedby),
reverse=sortedreverse)
return self.processlist
@sort_key.setter
def sort_key(self, key):
"""Set the current sort key."""
self._sort_key = key
glances_processes = GlancesProcesses()

View File

@ -100,7 +100,7 @@ class GlancesXMLRPCHandler(SimpleXMLRPCRequestHandler):
self.send_error(401, 'Authentication failed')
return False
def log_message(self, format, *args):
def log_message(self, log_format, *args):
# No message displayed on the server side
pass

View File

@ -50,7 +50,7 @@ class GlancesSNMPClient(object):
self.auth = auth
def __buid_result(self, varBinds):
"""Build the results"""
"""Build the results."""
ret = {}
for name, val in varBinds:
if str(val) == '':

View File

@ -19,6 +19,8 @@
"""Manage the Glances standalone session."""
from time import sleep
# Import Glances libs
from glances.core.glances_globals import is_windows
from glances.core.glances_logging import logger
@ -32,15 +34,16 @@ class GlancesStandalone(object):
"""This class creates and manages the Glances standalone session."""
def __init__(self, config=None, args=None):
# Quiet mode
self._quiet = args.quiet
self.refresh_time = args.time
# Init stats
self.stats = GlancesStats(config=config, args=args)
# Default number of processes to displayed is set to 50
glances_processes.set_max_processes(50)
# If process extended stats is disabled by user
if not args.enable_process_extended:
logger.info("Extended stats for top process are disabled (default behavior)")
logger.debug("Extended stats for top process are disabled")
glances_processes.disable_extended()
else:
logger.debug("Extended stats for top process are enabled")
@ -48,36 +51,67 @@ class GlancesStandalone(object):
# Manage optionnal process filter
if args.process_filter is not None:
glances_processes.set_process_filter(args.process_filter)
glances_processes.process_filter = args.process_filter
if (not is_windows) and args.no_kernel_threads:
# Ignore kernel threads in process list
glances_processes.disable_kernel_threads()
try:
if args.process_tree:
# Enable process tree view
glances_processes.enable_tree()
except AttributeError:
pass
# Initial system informations update
self.stats.update()
if self.quiet:
logger.info("Quiet mode is ON: Nothing will be displayed")
# In quiet mode, nothing is displayed
glances_processes.max_processes = 0
else:
# Default number of processes to displayed is set to 50
glances_processes.max_processes = 50
# Init screen
self.screen = GlancesCursesStandalone(args=args)
def serve_forever(self):
@property
def quiet(self):
return self._quiet
def __serve_forever(self):
"""Main loop for the CLI."""
while True:
# Update system informations
self.stats.update()
if not self.quiet:
# Update the screen
self.screen.update(self.stats)
else:
# Wait...
sleep(self.refresh_time)
# Export stats using export modules
self.stats.export(self.stats)
def serve_forever(self):
"""Wrapper to the serve_forever function.
This function will restore the terminal to a sane state
before re-raising the exception and generating a traceback.
"""
try:
return self.__serve_forever()
finally:
self.end()
def end(self):
"""End of the standalone CLI."""
if not self.quiet:
self.screen.end()
# Exit from export modules

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Manage the Glances server static list """
"""Manage the Glances server static list."""
# System lib
from socket import gaierror, gethostbyname
@ -28,7 +28,7 @@ from glances.core.glances_logging import logger
class GlancesStaticServer(object):
"""Manage the static servers list for the client browser"""
"""Manage the static servers list for the client browser."""
_section = "serverlist"
@ -39,8 +39,7 @@ class GlancesStaticServer(object):
self._server_list = self.load(config)
def load(self, config):
"""Load the server list from the configuration file"""
"""Load the server list from the configuration file."""
server_list = []
if config is None:
@ -54,7 +53,7 @@ class GlancesStaticServer(object):
postfix = 'server_%s_' % str(i)
# Read the server name (mandatory)
for s in ['name', 'port', 'alias']:
new_server[s] = config.get_raw_option(self._section, '%s%s' % (postfix, s))
new_server[s] = config.get_value(self._section, '%s%s' % (postfix, s))
if new_server['name'] is not None:
# Manage optionnal information
if new_server['port'] is None:
@ -85,9 +84,9 @@ class GlancesStaticServer(object):
return server_list
def get_servers_list(self):
"""Return the current server list (dict of dict)"""
"""Return the current server list (dict of dict)."""
return self._server_list
def set_server(self, server_pos, key, value):
"""Set the key to the value for the server_pos (position in the list)"""
"""Set the key to the value for the server_pos (position in the list)."""
self._server_list[server_pos][key] = value

View File

@ -25,7 +25,7 @@ import re
import sys
import threading
from glances.core.glances_globals import plugins_path, exports_path, sys_path
from glances.core.glances_globals import exports_path, plugins_path, sys_path
from glances.core.glances_logging import logger
# SNMP OID regexp pattern to short system name dict
@ -152,13 +152,17 @@ class GlancesStats(object):
# For standalone and server modes
# For each plugins, call the update method
for p in self._plugins:
# logger.debug("Update %s stats" % p)
# logger.debug("Update %s stats" % p)
self._plugins[p].update()
def export(self, input_stats={}):
def export(self, input_stats=None):
"""Export all the stats.
Each export module is ran in a dedicated thread."""
Each export module is ran in a dedicated thread.
"""
# threads = []
input_stats = input_stats or {}
for e in self._exports:
logger.debug("Export stats using the %s module" % e)
thread = threading.Thread(target=self._exports[e].update,
@ -167,11 +171,11 @@ class GlancesStats(object):
thread.start()
def getAll(self):
"""Return all the stats (list)"""
"""Return all the stats (list)."""
return [self._plugins[p].get_raw() for p in self._plugins]
def getAllAsDict(self):
"""Return all the stats (dict)"""
"""Return all the stats (dict)."""
# Python > 2.6
# {p: self._plugins[p].get_raw() for p in self._plugins}
ret = {}
@ -181,21 +185,21 @@ class GlancesStats(object):
def getAllLimits(self):
"""Return the plugins limits list."""
return [self._plugins[p].get_limits() for p in self._plugins]
return [self._plugins[p].limits for p in self._plugins]
def getAllLimitsAsDict(self):
"""Return all the stats limits (dict)"""
"""Return all the stats limits (dict)."""
ret = {}
for p in self._plugins:
ret[p] = self._plugins[p].get_limits()
ret[p] = self._plugins[p].limits
return ret
def getAllViews(self):
"""Return the plugins views"""
"""Return the plugins views."""
return [self._plugins[p].get_views() for p in self._plugins]
def getAllViewsAsDict(self):
"""Return all the stats views (dict)"""
"""Return all the stats views (dict)."""
ret = {}
for p in self._plugins:
ret[p] = self._plugins[p].get_views()
@ -203,7 +207,7 @@ class GlancesStats(object):
def get_plugin_list(self):
"""Return the plugin list."""
self._plugins
return self._plugins
def get_plugin(self, plugin_name):
"""Return the plugin name."""
@ -213,7 +217,7 @@ class GlancesStats(object):
return None
def end(self):
"""End of the Glances stats"""
"""End of the Glances stats."""
# Close the export module
for e in self._exports:
self._exports[e].exit()
@ -231,8 +235,10 @@ class GlancesStatsServer(GlancesStats):
# all_stats is a dict of dicts filled by the server
self.all_stats = collections.defaultdict(dict)
def update(self, input_stats={}):
def update(self, input_stats=None):
"""Update the stats."""
input_stats = input_stats or {}
# Force update of all the stats
GlancesStats.update(self)
@ -240,7 +246,7 @@ class GlancesStatsServer(GlancesStats):
self.all_stats = self._set_stats(input_stats)
def _set_stats(self, input_stats):
"""Set the stats to the input_stats one"""
"""Set the stats to the input_stats one."""
# Build the all_stats with the get_raw() method of the plugins
ret = collections.defaultdict(dict)
for p in self._plugins:
@ -248,11 +254,11 @@ class GlancesStatsServer(GlancesStats):
return ret
def getAll(self):
"""Return the stats as a list"""
"""Return the stats as a list."""
return self.all_stats
def getAllAsDict(self):
"""Return the stats as a dict"""
"""Return the stats as a dict."""
# Python > 2.6
# return {p: self.all_stats[p] for p in self._plugins}
ret = {}
@ -359,7 +365,7 @@ class GlancesStatsClientSNMP(GlancesStats):
return ret
def get_system_name(self, oid_system_name):
"""Get the short os name from the OS name OID string"""
"""Get the short os name from the OS name OID string."""
short_system_name = None
if oid_system_name == '':
@ -383,7 +389,8 @@ class GlancesStatsClientSNMP(GlancesStats):
# For each plugins, call the update method
for p in self._plugins:
# Set the input method to SNMP
self._plugins[p].set_input('snmp', self.system_name)
self._plugins[p].input_method = 'snmp'
self._plugins[p].short_system_name = self.system_name
try:
self._plugins[p].update()
except Exception as e:

View File

@ -34,7 +34,7 @@ class GlancesWebServer(object):
# Init stats
self.stats = GlancesStats(config)
if (not is_windows) and args.no_kernel_threads:
if not is_windows and args.no_kernel_threads:
# Ignore kernel threads in process list
glances_processes.disable_kernel_threads()

View File

@ -72,27 +72,26 @@ class Export(GlancesExport):
plugins = stats.getAllPlugins()
# Loop over available plugin
i = 0
for plugin in plugins:
for i, plugin in enumerate(plugins):
if plugin in self.plugins_to_export():
if type(all_stats[i]) is list:
for item in all_stats[i]:
if isinstance(all_stats[i], list):
for stat in all_stats[i]:
# First line: header
if self.first_line:
fieldnames = item.keys()
csv_header += map(lambda x: plugin+'_'+item[item['key']]+'_'+x, item)
csv_header += ('{0}_{1}_{2}'.format(
plugin, stat[stat['key']], item) for item in stat)
# Others lines: stats
fieldvalues = item.values()
fieldvalues = stat.values()
csv_data += fieldvalues
elif type(all_stats[i]) is dict:
elif isinstance(all_stats[i], dict):
# First line: header
if self.first_line:
fieldnames = all_stats[i].keys()
csv_header += map(lambda x: plugin+'_'+x, fieldnames)
csv_header += ('{0}_{1}'.format(plugin, fieldname)
for fieldname in fieldnames)
# Others lines: stats
fieldvalues = all_stats[i].values()
csv_data += fieldvalues
i += 1
# Export to CSV
if self.first_line:

View File

@ -32,7 +32,7 @@ from glances.core.glances_logging import logger
class GlancesExport(object):
"""Main class for Glances' export IF."""
"""Main class for Glances export IF."""
def __init__(self, config=None, args=None):
"""Init the export class."""
@ -53,34 +53,47 @@ class GlancesExport(object):
logger.debug("Finalise export interface %s" % self.export_name)
def plugins_to_export(self):
"""Return the list of plugins to export"""
return ['cpu', 'load', 'mem', 'memswap', 'network', 'diskio', 'fs', 'processcount']
"""Return the list of plugins to export."""
return ['cpu',
'percpu',
'load',
'mem',
'memswap',
'network',
'diskio',
'fs',
'processcount',
'ip',
'system',
'uptime']
def update(self, stats):
"""Update stats to a server.
The method buil two list: names and values
and call the export method to export the stats"""
The method builds two lists: names and values
and calls the export method to export the stats.
"""
if not self.export_enable:
return False
# Get the stats
all_stats = stats.getAll()
all_limits = stats.getAllLimits()
plugins = stats.getAllPlugins()
# Loop over available plugin
i = 0
for plugin in plugins:
# Loop over available plugins
for i, plugin in enumerate(plugins):
if plugin in self.plugins_to_export():
if type(all_stats[i]) is list:
if isinstance(all_stats[i], list):
for item in all_stats[i]:
export_names = map(
lambda x: item[item['key']] + '.' + x, item.keys())
export_values = item.values()
item.update(all_limits[i])
export_names = list('{0}.{1}'.format(item[item['key']], key)
for key in item.keys())
export_values = list(item.values())
self.export(plugin, export_names, export_values)
elif type(all_stats[i]) is dict:
export_names = all_stats[i].keys()
export_values = all_stats[i].values()
elif isinstance(all_stats[i], dict):
export_names = list(all_stats[i].keys()) + list(all_limits[i].keys())
export_values = list(all_stats[i].values()) + list(all_limits[i].values())
self.export(plugin, export_names, export_values)
i += 1
return True

View File

@ -39,23 +39,21 @@ else:
class GlancesHistory(object):
"""This class define the object to manage stats history"""
"""This class define the object to manage stats history."""
def __init__(self, output_folder):
self.output_folder = output_folder
def get_output_folder(self):
"""Return the output folder where the graph are generated"""
"""Return the output folder where the graph are generated."""
return self.output_folder
def graph_enabled(self):
"""Return True if Glances can generaate history graphs"""
"""Return True if Glances can generate history graphs."""
return matplotlib_check
def reset(self, stats):
"""
Reset all the history
"""
"""Reset all the history."""
if not self.graph_enabled():
return False
for p in stats.getAllPlugins():
@ -65,9 +63,7 @@ class GlancesHistory(object):
return True
def get_graph_color(self, item):
"""
Get the item's color
"""
"""Get the item's color."""
try:
ret = item['color']
except KeyError:
@ -76,15 +72,11 @@ class GlancesHistory(object):
return ret
def get_graph_legend(self, item):
"""
Get the item's legend
"""
"""Get the item's legend."""
return item['name']
def get_graph_yunit(self, item, pre_label=''):
"""
Get the item's Y unit
"""
"""Get the item's Y unit."""
try:
unit = " (%s)" % item['y_unit']
except KeyError:
@ -96,9 +88,9 @@ class GlancesHistory(object):
return "%s%s" % (label, unit)
def generate_graph(self, stats):
"""
Generate graphs from plugins history
Return the number of output files generated by the function
"""Generate graphs from plugins history.
Return the number of output files generated by the function.
"""
if not self.graph_enabled():
return 0
@ -176,8 +168,7 @@ class GlancesHistory(object):
fig.set_size_inches(20, 10)
plt.legend(handles, labels, loc=1, prop={'size': 9})
plt.xlabel('Date')
plt.savefig(
os.path.join(self.output_folder, 'glances_%s.png' % (p)), dpi=72)
plt.savefig(os.path.join(self.output_folder, 'glances_%s.png' % p), dpi=72)
index_all += 1
plt.close()

View File

@ -20,14 +20,21 @@
"""InfluxDB interface class."""
# Import sys libs
from influxdb import InfluxDBClient, client
import sys
try:
from configparser import NoOptionError, NoSectionError
except ImportError: # Python 2
from ConfigParser import NoOptionError, NoSectionError
# Import Glances lib
from glances.core.glances_logging import logger
from ConfigParser import NoSectionError, NoOptionError
from glances.exports.glances_export import GlancesExport
from influxdb import InfluxDBClient
from influxdb.client import InfluxDBClientError
from influxdb.influxdb08 import InfluxDBClient as InfluxDBClient08
from influxdb.influxdb08.client import InfluxDBClientError as InfluxDBClientError08
class Export(GlancesExport):
@ -38,11 +45,12 @@ class Export(GlancesExport):
GlancesExport.__init__(self, config=config, args=args)
# Load the InfluxDB configuration file
self.influxdb_host = None
self.influxdb_port = None
self.influxdb_user = None
self.influxdb_password = None
self.influxdb_db = None
self.host = None
self.port = None
self.user = None
self.password = None
self.db = None
self.prefix = None
self.export_enable = self.load_conf()
if not self.export_enable:
sys.exit(2)
@ -51,15 +59,15 @@ class Export(GlancesExport):
self.client = self.init()
def load_conf(self, section="influxdb"):
"""Load the InfluxDb configuration in the Glances configuration file"""
"""Load the InfluxDb configuration in the Glances configuration file."""
if self.config is None:
return False
try:
self.influxdb_host = self.config.get_raw_option(section, "host")
self.influxdb_port = self.config.get_raw_option(section, "port")
self.influxdb_user = self.config.get_raw_option(section, "user")
self.influxdb_password = self.config.get_raw_option(section, "password")
self.influxdb_db = self.config.get_raw_option(section, "db")
self.host = self.config.get_value(section, 'host')
self.port = self.config.get_value(section, 'port')
self.user = self.config.get_value(section, 'user')
self.password = self.config.get_value(section, 'password')
self.db = self.config.get_value(section, 'db')
except NoSectionError:
logger.critical("No InfluxDB configuration found")
return False
@ -68,39 +76,55 @@ class Export(GlancesExport):
return False
else:
logger.debug("Load InfluxDB from the Glances configuration file")
# Prefix is optional
try:
self.prefix = self.config.get_value(section, 'prefix')
except NoOptionError:
pass
return True
def init(self):
"""Init the connection to the InfluxDB server"""
"""Init the connection to the InfluxDB server."""
if not self.export_enable:
return None
db = InfluxDBClient(self.influxdb_host,
self.influxdb_port,
self.influxdb_user,
self.influxdb_password,
self.influxdb_db)
try:
get_all_db = db.get_database_list()[0].values()
except client.InfluxDBClientError as e:
logger.critical("Can not connect to InfluxDB database '%s' (%s)" % (self.influxdb_db, e))
db = InfluxDBClient(host=self.host,
port=self.port,
username=self.user,
password=self.password,
database=self.db)
get_all_db = [i['name'] for i in db.get_list_database()]
except InfluxDBClientError:
# https://github.com/influxdb/influxdb-python/issues/138
logger.info("Trying fallback to InfluxDB v0.8")
db = InfluxDBClient08(host=self.host,
port=self.port,
username=self.user,
password=self.password,
database=self.db)
get_all_db = [i['name'] for i in db.get_list_database()]
except InfluxDBClientError08 as e:
logger.critical("Cannot connect to InfluxDB database '%s' (%s)" % (self.db, e))
sys.exit(2)
if self.influxdb_db in get_all_db:
if self.db in get_all_db:
logger.info(
"Stats will be exported to InfluxDB server: {0}".format(db._baseurl))
else:
logger.critical("InfluxDB database '%s' did not exist. Please create it" % self.influxdb_db)
logger.critical("InfluxDB database '%s' did not exist. Please create it" % self.db)
sys.exit(2)
return db
def export(self, name, columns, points):
"""Write the points to the InfluxDB server"""
data = [
{
"name": name,
"columns": columns,
"points": [points]
}]
"""Write the points to the InfluxDB server."""
# Manage prefix
if self.prefix is not None:
name = self.prefix + '.' + name
# logger.info(self.prefix)
# Create DB input
data = [{'name': name, 'columns': columns, 'points': [points]}]
# Write input to the InfluxDB database
try:
self.client.write_points(data)
except Exception as e:

View File

@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
#
# 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 <http://www.gnu.org/licenses/>.
"""JMS interface class."""
# Import sys libs
import datetime
import socket
import sys
from numbers import Number
# Import Glances lib
from glances.core.glances_logging import logger
try:
from configparser import NoOptionError, NoSectionError
except ImportError: # Python 2
from ConfigParser import NoOptionError, NoSectionError
from glances.exports.glances_export import GlancesExport
# Import pika for RabbitMQ
import pika
class Export(GlancesExport):
"""This class manages the rabbitMQ export module."""
def __init__(self, config=None, args=None):
"""Init the RabbitMQ export IF."""
GlancesExport.__init__(self, config=config, args=args)
# Load the rabbitMQ configuration file
self.rabbitmq_host = None
self.rabbitmq_port = None
self.rabbitmq_user = None
self.rabbitmq_password = None
self.rabbitmq_queue = None
self.hostname = socket.gethostname()
self.export_enable = self.load_conf()
if not self.export_enable:
sys.exit(2)
# Init the rabbitmq client
self.client = self.init()
def load_conf(self, section="rabbitmq"):
"""Load the rabbitmq configuration in the Glances configuration file."""
if self.config is None:
return False
try:
self.rabbitmq_host = self.config.get_value(section, 'host')
self.rabbitmq_port = self.config.get_value(section, 'port')
self.rabbitmq_user = self.config.get_value(section, 'user')
self.rabbitmq_password = self.config.get_value(section, 'password')
self.rabbitmq_queue = self.config.get_value(section, 'queue')
except NoSectionError:
logger.critical("No rabbitmq configuration found")
return False
except NoOptionError as e:
logger.critical("Error in the RabbitM configuration (%s)" % e)
return False
else:
logger.debug("Load RabbitMQ from the Glances configuration file")
return True
def init(self):
"""Init the connection to the rabbitmq server."""
if not self.export_enable:
return None
try:
parameters = pika.URLParameters(
'amqp://' + self.rabbitmq_user +
':' + self.rabbitmq_password +
'@' + self.rabbitmq_host +
':' + self.rabbitmq_port + '/')
connection = pika.BlockingConnection(parameters)
channel = connection.channel()
return channel
except Exception as e:
logger.critical("Connection to rabbitMQ failed : %s " % e)
return None
def export(self, name, columns, points):
"""Write the points in RabbitMQ."""
data = ('hostname=' + self.hostname + ', name=' + name +
', dateinfo=' + datetime.datetime.utcnow().isoformat())
for i in range(0, len(columns)):
if not isinstance(points[i], Number):
continue
else:
data += ", " + columns[i] + "=" + str(points[i])
logger.debug(data)
try:
self.client.basic_publish(exchange='', routing_key=self.rabbitmq_queue, body=data)
except Exception as e:
logger.error("Can not export stats to RabbitMQ (%s)" % e)

View File

@ -20,15 +20,19 @@
"""Statsd interface class."""
# Import sys libs
from statsd import StatsClient
from numbers import Number
import sys
from numbers import Number
try:
from configparser import NoOptionError, NoSectionError
except ImportError: # Python 2
from ConfigParser import NoOptionError, NoSectionError
# Import Glances lib
from glances.core.glances_logging import logger
from ConfigParser import NoSectionError, NoOptionError
from glances.exports.glances_export import GlancesExport
from statsd import StatsClient
class Export(GlancesExport):
@ -56,12 +60,12 @@ class Export(GlancesExport):
prefix=self.prefix)
def load_conf(self, section="statsd"):
"""Load the Statsd configuration in the Glances configuration file"""
"""Load the Statsd configuration in the Glances configuration file."""
if self.config is None:
return False
try:
self.host = self.config.get_raw_option(section, "host")
self.port = self.config.get_raw_option(section, "port")
self.host = self.config.get_value(section, 'host')
self.port = self.config.get_value(section, 'port')
except NoSectionError:
logger.critical("No Statsd configuration found")
return False
@ -72,13 +76,13 @@ class Export(GlancesExport):
logger.debug("Load Statsd from the Glances configuration file")
# Prefix is optional
try:
self.prefix = self.config.get_raw_option(section, "prefix")
except NoOptionError as e:
self.prefix = self.config.get_value(section, 'prefix')
except NoOptionError:
pass
return True
def init(self, prefix='glances'):
"""Init the connection to the Statsd server"""
"""Init the connection to the Statsd server."""
if not self.export_enable:
return None
return StatsClient(self.host,
@ -86,7 +90,7 @@ class Export(GlancesExport):
prefix=prefix)
def export(self, name, columns, points):
"""Export the stats to the Statsd server"""
"""Export the stats to the Statsd server."""
for i in range(0, len(columns)):
if not isinstance(points[i], Number):
continue

View File

@ -1,61 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="refresh" content="{{refresh_time}}">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Glances</title>
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link rel="stylesheet" type="text/css" href="normalize.css" />
<link rel="stylesheet" type="text/css" href="bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="style.css" />
<script src="modernizr.custom.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="pull-left">
% include('plugin_text', plugin_name="system", stats=stats['system'])
</div>
<div class="pull-right">
% include('plugin_text', plugin_name="uptime", stats=stats['uptime'])
</div>
</div>
</div>
<div class="row">
<div class="col-sm-3">
% include('plugin_table', plugin_name="cpu", stats=stats['cpu'])
</div>
<div class="col-sm-3 col-lg-2 col-lg-offset-1">
% include('plugin_table', plugin_name="load", stats=stats['load'])
</div>
<div class="col-sm-3 col-lg-3">
% include('plugin_table', plugin_name="mem", stats=stats['mem'])
</div>
<div class="col-sm-3 col-lg-2 col-lg-offset-1">
% include('plugin_table', plugin_name="memswap", stats=stats['memswap'])
</div>
</div>
<div class="row">
<div class="col-sm-3">
% include('plugin_table', plugin_name="network", stats=stats['network'])
% include('plugin_table', plugin_name="diskio", stats=stats['diskio'])
% include('plugin_table', plugin_name="fs", stats=stats['fs'])
% include('plugin_table', plugin_name="sensors", stats=stats['sensors'])
</div>
<div class="col-sm-9">
% include('plugin_table', plugin_name="alert", stats=stats['alert'])
% include('plugin_text', plugin_name="processcount", stats=stats['processcount'])
% include('plugin_table', plugin_name="docker", stats=stats['docker'])
<div class="row">
<div class="col-sm-9">
% include('plugin_table', plugin_name="monitor", stats=stats['monitor'])
</div>
</div>
% include('plugin_table', plugin_name="processlist", stats=stats['processlist'])
</div>
</div>
</div>
</body>
</html>

View File

@ -1,22 +0,0 @@
% if stats['msgdict'] != []:
<section id="{{ plugin_name }}" class="plugin">
<table class="table">
<tbody>
<tr>
% for msg in stats['msgdict']:
% if msg['msg'].startswith('\n'):
</tr>
<tr>
% else:
% if stats['display']:
<td class="{{ msg['decoration'].lower() }} {{ 'hidden-xs hidden-sm' if msg['optional'] else '' }}">
{{ msg['msg'] }}
</td>
% end
% end
% end
</tr>
</tbody>
</table>
</section>
% end

View File

@ -1,11 +0,0 @@
% if stats['msgdict'] != []:
<section id="{{ plugin_name }}" class="plugin">
% for msg in stats['msgdict']:
% if stats['display']:
<span class="{{ msg['decoration'].lower() }} {{ 'hidden-xs hidden-sm' if msg['optional'] else '' }}">
{{ msg['msg'] }}
</span>
% end
% end
</section>
% end

View File

@ -20,54 +20,78 @@
"""Manage bars for Glances output."""
# Import system lib
import locale
from math import modf
# Global vars
curses_bars = [" ", "", "", "", "", "", "", "", ""]
class Bar(object):
"""Manage bar (progression or status)
r"""Manage bar (progression or status).
import sys
import time
b = Bar(10)
for p in range(0, 100):
b.set_percent(p)
b.percent = p
print("\r%s" % b),
time.sleep(0.1)
sys.stdout.flush()
"""
def __init__(self, size):
def __init__(self, size,
pre_char='[',
post_char=']',
empty_char='_',
with_text=True):
# Bar size
self.__size = size
# Bar current percent
self.__percent = 0
# Char used for the decoration
self.__pre_char = pre_char
self.__post_char = post_char
self.__empty_char = empty_char
self.__with_text = with_text
# Char used for the bar
self.curses_bars = self.__get_curses_bars()
def get_size(self):
def __get_curses_bars(self):
# Return the chars used to display the bar
if locale.getdefaultlocale()[1] == 'UTF-8':
return [" ", "", "", "", "", "", "", "", ""]
else:
return [" ", " ", " ", " ", "|", "|", "|", "|", "|"]
@property
def size(self, with_decoration=False):
# Return the bar size, with or without decoration
if with_decoration:
return self.__size
if self.__with_text:
return self.__size - 6
def set_size(self, size):
self.__size = size
return self.__size
# @size.setter
# def size(self, value):
# self.__size = value
def get_percent(self):
@property
def percent(self):
return self.__percent
def set_percent(self, percent):
assert percent >= 0
assert percent <= 100
self.__percent = percent
return self.__percent
@percent.setter
def percent(self, value):
assert value >= 0
assert value <= 100
self.__percent = value
def __str__(self):
"""Return the bars"""
frac, whole = modf(self.get_size() * self.get_percent() / 100.0)
ret = curses_bars[8] * int(whole)
"""Return the bars."""
frac, whole = modf(self.size * self.percent / 100.0)
ret = self.curses_bars[8] * int(whole)
if frac > 0:
ret += curses_bars[int(frac * 8)]
ret += self.curses_bars[int(frac * 8)]
whole += 1
ret += '_' * int(self.get_size() - whole)
return ret
ret += self.__empty_char * int(self.size - whole)
if self.__with_text:
ret = '{0}{1:>5}%'.format(ret, self.percent)
return self.__pre_char + ret + self.__post_char

View File

@ -23,12 +23,11 @@ import json
import os
import sys
# Import Glances libs
from glances.core.glances_globals import is_windows
from glances.core.glances_logging import logger
# Import mandatory Bottle lib
try:
from bottle import Bottle, template, static_file, TEMPLATE_PATH, abort, response, request
from bottle import Bottle, static_file, abort, response, request
except ImportError:
logger.critical('Bottle module not found. Glances cannot start in web server mode.')
sys.exit(2)
@ -53,9 +52,6 @@ class GlancesBottle(object):
# Define routes
self._route()
# Update the template path (glances/outputs/bottle)
TEMPLATE_PATH.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'bottle'))
# Path where the statics files are stored
self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static')
@ -63,10 +59,17 @@ class GlancesBottle(object):
"""Define route."""
self._app.route('/', method="GET", callback=self._index)
self._app.route('/<refresh_time:int>', method=["GET", "POST"], callback=self._index)
self._app.route('/<filename:re:.*\.css>', method="GET", callback=self._css)
self._app.route('/<filename:re:.*\.js>', method="GET", callback=self._js)
self._app.route('/<filename:re:.*\.js.map>', method="GET", callback=self._js_map)
self._app.route('/<filename:re:.*\.html>', method="GET", callback=self._html)
self._app.route('/<filename:re:.*\.png>', method="GET", callback=self._images)
self._app.route('/favicon.ico', method="GET", callback=self._favicon)
# REST API
self._app.route('/api/2/help', method="GET", callback=self._api_help)
self._app.route('/api/2/pluginslist', method="GET", callback=self._api_plugins)
self._app.route('/api/2/all', method="GET", callback=self._api_all)
self._app.route('/api/2/all/limits', method="GET", callback=self._api_all_limits)
@ -86,7 +89,7 @@ class GlancesBottle(object):
self.plugins_list = self.stats.getAllPlugins()
# Bind the Bottle TCP address/port
bindmsg = _("Glances web server started on http://{0}:{1}/").format(self.args.bind_address, self.args.port)
bindmsg = 'Glances web server started on http://{0}:{1}/'.format(self.args.bind_address, self.args.port)
logger.info(bindmsg)
print(bindmsg)
self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug)
@ -105,7 +108,12 @@ class GlancesBottle(object):
self.stats.update()
# Display
return self.display(self.stats, refresh_time=refresh_time)
return static_file("index.html", root=os.path.join(self.STATIC_PATH, 'html'))
def _html(self, filename):
"""Bottle callback for *.html files."""
# Return the static file
return static_file(filename, root=os.path.join(self.STATIC_PATH, 'html'))
def _css(self, filename):
"""Bottle callback for *.css files."""
@ -117,16 +125,60 @@ class GlancesBottle(object):
# Return the static file
return static_file(filename, root=os.path.join(self.STATIC_PATH, 'js'))
def _js_map(self, filename):
"""Bottle callback for *.js.map files."""
# Return the static file
return static_file(filename, root=os.path.join(self.STATIC_PATH, 'js'))
def _images(self, filename):
"""Bottle callback for *.png files."""
# Return the static file
return static_file(filename, root=os.path.join(self.STATIC_PATH, 'images'))
def _favicon(self):
"""Bottle callback for favicon."""
# Return the static file
return static_file('favicon.ico', root=self.STATIC_PATH)
def _api_help(self):
"""Glances API RESTFul implementation.
Return the help data or 404 error.
"""
response.content_type = 'application/json'
# Update the stat
view_data = self.stats.get_plugin("help").get_view_data()
try:
plist = json.dumps(view_data, sort_keys=True)
except Exception as e:
abort(404, "Cannot get help view data (%s)" % str(e))
return plist
def _api_plugins(self):
"""
Glances API RESTFul implementation
Return the plugin list
or 404 error
@api {get} /api/2/pluginslist Get plugins list
@apiVersion 2.0
@apiName pluginslist
@apiGroup plugin
@apiSuccess {String[]} Plugins list.
@apiSuccessExample Success-Response:
HTTP/1.1 200 OK
[
"load",
"help",
"ip",
"memswap",
"processlist",
...
]
@apiError Cannot get plugin list.
@apiErrorExample Error-Response:
HTTP/1.1 404 Not Found
"""
response.content_type = 'application/json'
@ -140,8 +192,8 @@ class GlancesBottle(object):
return plist
def _api_all(self):
"""
Glances API RESTFul implementation
"""Glances API RESTFul implementation.
Return the JSON representation of all the plugins
HTTP/200 if OK
HTTP/400 if plugin is not found
@ -149,19 +201,28 @@ class GlancesBottle(object):
"""
response.content_type = 'application/json'
if not self.args.debug:
# Update the stat
self.stats.update()
try:
# Get the JSON value of the stat value
# Get the JSON value of the stat ID
statval = json.dumps(self.stats.getAllAsDict())
except Exception as e:
abort(404, "Cannot get stats (%s)" % str(e))
return statval
else:
path = "~/glances/"
if is_windows:
path = "D:\\glances\\"
filepath = path + "debug.json"
f = open(filepath)
return f.read()
def _api_all_limits(self):
"""
Glances API RESTFul implementation
"""Glances API RESTFul implementation.
Return the JSON representation of all the plugins limits
HTTP/200 if OK
HTTP/400 if plugin is not found
@ -177,8 +238,8 @@ class GlancesBottle(object):
return limits
def _api_all_views(self):
"""
Glances API RESTFul implementation
"""Glances API RESTFul implementation.
Return the JSON representation of all the plugins views
HTTP/200 if OK
HTTP/400 if plugin is not found
@ -194,8 +255,8 @@ class GlancesBottle(object):
return limits
def _api(self, plugin):
"""
Glances API RESTFul implementation
"""Glances API RESTFul implementation.
Return the JSON representation of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
@ -217,8 +278,8 @@ class GlancesBottle(object):
return statval
def _api_limits(self, plugin):
"""
Glances API RESTFul implementation
"""Glances API RESTFul implementation.
Return the JSON limits of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
@ -234,14 +295,14 @@ class GlancesBottle(object):
try:
# Get the JSON value of the stat limits
ret = self.stats.get_plugin(plugin).get_limits()
ret = self.stats.get_plugin(plugin).limits
except Exception as e:
abort(404, "Cannot get limits for plugin %s (%s)" % (plugin, str(e)))
return ret
def _api_views(self, plugin):
"""
Glances API RESTFul implementation
"""Glances API RESTFul implementation.
Return the JSON views of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
@ -263,8 +324,8 @@ class GlancesBottle(object):
return ret
def _api_item(self, plugin, item):
"""
Glances API RESTFul implementation
"""Glances API RESTFul implementation.
Return the JSON represenation of the couple plugin/item
HTTP/200 if OK
HTTP/400 if plugin is not found
@ -287,8 +348,8 @@ class GlancesBottle(object):
return plist
def _api_value(self, plugin, item, value):
"""
Glances API RESTFul implementation
"""Glances API RESTFul implementation.
Return the process stats (dict) for the given item=value
HTTP/200 if OK
HTTP/400 if plugin is not found
@ -309,33 +370,6 @@ class GlancesBottle(object):
else:
return pdict
def display(self, stats, refresh_time=None):
"""Display stats on the web page.
stats: Stats database to display
"""
stats = {
'system': self.stats.get_plugin('system').get_stats_display(args=self.args),
'uptime': self.stats.get_plugin('uptime').get_stats_display(args=self.args),
'cpu': self.stats.get_plugin('cpu').get_stats_display(args=self.args),
'load': self.stats.get_plugin('load').get_stats_display(args=self.args),
'mem': self.stats.get_plugin('mem').get_stats_display(args=self.args),
'memswap': self.stats.get_plugin('memswap').get_stats_display(args=self.args),
'network': self.stats.get_plugin('network').get_stats_display(args=self.args),
'diskio': self.stats.get_plugin('diskio').get_stats_display(args=self.args),
'fs': self.stats.get_plugin('fs').get_stats_display(args=self.args),
'raid': self.stats.get_plugin('raid').get_stats_display(args=self.args),
'sensors': self.stats.get_plugin('sensors').get_stats_display(args=self.args),
'alert': self.stats.get_plugin('alert').get_stats_display(args=self.args),
'processcount': self.stats.get_plugin('processcount').get_stats_display(args=self.args),
'monitor': self.stats.get_plugin('monitor').get_stats_display(args=self.args),
'processlist': self.stats.get_plugin('processlist').get_stats_display(args=self.args),
'docker': self.stats.get_plugin('docker').get_stats_display(args=self.args)
}
return template('base', refresh_time=refresh_time, stats=stats)
class EnableCors(object):
name = 'enable_cors'

View File

@ -80,10 +80,10 @@ class Screen(object):
def subwin(self, x, y):
return self
def keypad(self, id):
def keypad(self, screen_id):
return None
def nodelay(self, id):
def nodelay(self, screen_id):
return None
def getch(self):
@ -170,8 +170,8 @@ class WCurseLight(object):
def napms(self, t):
time.sleep(t / 1000 if t > 1000 else 1)
def init_pair(self, id, fg, bk):
self.colors[id] = [max(fg, 0), max(bk, 0)]
def init_pair(self, color_id, fg, bk):
self.colors[color_id] = [max(fg, 0), max(bk, 0)]
def color_pair(self, id):
return id
def color_pair(self, color_id):
return color_id

View File

@ -20,6 +20,7 @@
"""Curses interface class."""
# Import system lib
import re
import sys
# Import Glances lib
@ -36,7 +37,8 @@ if not is_windows:
import curses.panel
from curses.textpad import Textbox
except ImportError:
logger.critical("Curses module not found. Glances cannot start in standalone mode.")
logger.critical(
"Curses module not found. Glances cannot start in standalone mode.")
sys.exit(1)
else:
from glances.outputs.glances_colorconsole import WCurseLight
@ -45,9 +47,9 @@ else:
class _GlancesCurses(object):
"""
This class manages the curses display (and key pressed).
Note: It is a private class, use GlancesCursesClient or GlancesCursesBrowser
"""This class manages the curses display (and key pressed).
Note: It is a private class, use GlancesCursesClient or GlancesCursesBrowser.
"""
def __init__(self, args=None):
@ -126,6 +128,7 @@ class _GlancesCurses(object):
self.no_color = curses.color_pair(1)
self.default_color = curses.color_pair(3) | A_BOLD
self.nice_color = curses.color_pair(9) | A_BOLD
self.cpu_time_color = curses.color_pair(9) | A_BOLD
self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD
self.ifWARNING_color = curses.color_pair(5) | A_BOLD
self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD
@ -139,6 +142,7 @@ class _GlancesCurses(object):
self.no_color = curses.A_NORMAL
self.default_color = curses.A_NORMAL
self.nice_color = A_BOLD
self.cpu_time_color = A_BOLD
self.ifCAREFUL_color = curses.A_UNDERLINE
self.ifWARNING_color = A_BOLD
self.ifCRITICAL_color = curses.A_REVERSE
@ -160,6 +164,7 @@ class _GlancesCurses(object):
'PROCESS': self.default_color2,
'STATUS': self.default_color2,
'NICE': self.nice_color,
'CPU_TIME': self.cpu_time_color,
'CAREFUL': self.ifCAREFUL_color2,
'WARNING': self.ifWARNING_color2,
'CRITICAL': self.ifCRITICAL_color2,
@ -175,9 +180,6 @@ class _GlancesCurses(object):
# Init refresh time
self.__refresh_time = args.time
# Init process sort method
self.args.process_sorted_by = 'auto'
# Init edit filter tag
self.edit_filter = False
@ -200,7 +202,8 @@ class _GlancesCurses(object):
'Stats history disabled because MatPlotLib is not installed')
def set_cursor(self, value):
"""Configure the curse cursor apparence
"""Configure the curse cursor apparence.
0: invisible
1: visible
2: very visible
@ -248,21 +251,24 @@ class _GlancesCurses(object):
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('/'):
# '/' > 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
self.args.process_sorted_by = 'auto'
glances_processes.resetsort()
# 'a' > Sort processes automatically and reset to 'cpu_percent'
glances_processes.auto_sort = True
glances_processes.sort_key = 'cpu_percent'
elif self.pressedkey == ord('b'):
# 'b' > Switch between bit/s and Byte/s for network IO
# self.net_byteps_tag = not self.net_byteps_tag
self.args.byte = not self.args.byte
elif self.pressedkey == ord('c'):
# 'c' > Sort processes by CPU usage
self.args.process_sorted_by = 'cpu_percent'
glances_processes.setmanualsortkey(self.args.process_sorted_by)
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
@ -290,22 +296,25 @@ class _GlancesCurses(object):
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)
self.args.process_sorted_by = 'io_counters'
glances_processes.setmanualsortkey(self.args.process_sorted_by)
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
self.args.process_sorted_by = 'memory_percent'
glances_processes.setmanualsortkey(self.args.process_sorted_by)
glances_processes.auto_sort = False
glances_processes.sort_key = 'memory_percent'
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
self.args.process_sorted_by = 'name'
glances_processes.setmanualsortkey(self.args.process_sorted_by)
glances_processes.auto_sort = False
glances_processes.sort_key = 'name'
elif self.pressedkey == ord('r'):
# 'r' > Reset history
self.reset_history_tag = not self.reset_history_tag
@ -317,13 +326,17 @@ class _GlancesCurses(object):
self.args.disable_sensors = not self.args.disable_sensors
elif self.pressedkey == ord('t'):
# 't' > Sort processes by TIME usage
self.args.process_sorted_by = 'cpu_times'
glances_processes.setmanualsortkey(self.args.process_sorted_by)
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' > View cumulative network IO (instead of bitrate)
# '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
@ -357,31 +370,29 @@ class _GlancesCurses(object):
curses.endwin()
def init_line_column(self):
"""Init the line and column position for the curses inteface"""
self.line = 0
self.column = 0
self.next_line = 0
self.next_column = 0
"""Init the line and column position for the curses inteface."""
self.init_line()
self.init_column()
def init_line(self):
"""Init the line position for the curses inteface"""
"""Init the line position for the curses inteface."""
self.line = 0
self.next_line = 0
def init_column(self):
"""Init the column position for the curses inteface"""
"""Init the column position for the curses inteface."""
self.column = 0
self.next_column = 0
def new_line(self):
"""New line in the curses interface"""
"""New line in the curses interface."""
self.line = self.next_line
def new_column(self):
"""New column in the curses interface"""
"""New column in the curses interface."""
self.column = self.next_column
def display(self, stats, cs_status="None"):
def display(self, stats, cs_status=None):
"""Display stats on the screen.
stats: Stats database to display
@ -426,6 +437,10 @@ class _GlancesCurses(object):
stats_memswap = stats.get_plugin('memswap').get_stats_display()
stats_network = stats.get_plugin('network').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(
@ -452,11 +467,10 @@ class _GlancesCurses(object):
max_processes_displayed -= 4
if max_processes_displayed < 0:
max_processes_displayed = 0
if glances_processes.get_max_processes() is None or \
glances_processes.get_max_processes() != max_processes_displayed:
logger.debug("Set number of displayed processes to %s" %
max_processes_displayed)
glances_processes.set_max_processes(max_processes_displayed)
if (glances_processes.max_processes is None or
glances_processes.max_processes != max_processes_displayed):
logger.debug("Set number of displayed processes to {0}".format(max_processes_displayed))
glances_processes.max_processes = max_processes_displayed
stats_processlist = stats.get_plugin(
'processlist').get_stats_display(args=self.args)
@ -472,55 +486,93 @@ class _GlancesCurses(object):
# ... and exit
return False
# ==================================
# Display first line (system+uptime)
# ==================================
# Space between column
self.space_between_column = 0
self.new_line()
l = self.get_stats_display_width(
stats_system) + self.get_stats_display_width(stats_uptime) + self.space_between_column
self.display_plugin(stats_system, display_optional=(screen_x >= l))
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)
self.display_plugin(
stats_system, display_optional=(screen_x >= l_uptime))
self.new_column()
self.display_plugin(stats_ip)
# Space between column
self.space_between_column = 3
self.new_column()
self.display_plugin(stats_uptime)
# Display second line (CPU|PERCPU+LOAD+MEM+SWAP+<SUMMARY>)
# CPU|PERCPU
# ========================================================
# Display second line (<SUMMARY>+CPU|PERCPU+LOAD+MEM+SWAP)
# ========================================================
self.init_column()
self.new_line()
# Init quicklook
stats_quicklook = {'msgdict': []}
# Start with the mandatory stats:
# CPU | PERCPU
if self.args.percpu:
l = self.get_stats_display_width(stats_percpu)
cpu_width = self.get_stats_display_width(stats_percpu)
quicklook_adapt = 114
else:
l = self.get_stats_display_width(stats_cpu)
l += self.get_stats_display_width(stats_load) + self.get_stats_display_width(
stats_mem) + self.get_stats_display_width(stats_memswap)
# Space between column
space_number = int(stats_load['msgdict'] != [
]) + int(stats_mem['msgdict'] != []) + int(stats_memswap['msgdict'] != [])
if space_number == 0:
cpu_width = self.get_stats_display_width(
stats_cpu, without_option=(screen_x < 80))
quicklook_adapt = 108
l = cpu_width
# MEM & SWAP & LOAD
l += self.get_stats_display_width(stats_mem,
without_option=(screen_x < 100))
l += self.get_stats_display_width(stats_memswap)
l += self.get_stats_display_width(stats_load)
# Quicklook plugin size is dynamic
l_ql = 0
if screen_x > 126 and not self.args.disable_quicklook:
# Limit the size to be align with the process
quicklook_width = min(screen_x - quicklook_adapt, 87)
try:
stats_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:
l_ql = self.get_stats_display_width(stats_quicklook)
# Display Quicklook
self.display_plugin(stats_quicklook)
self.new_column()
# Compute space between column
space_number = int(stats_quicklook['msgdict'] != [])
space_number += int(stats_mem['msgdict'] != [])
space_number += int(stats_memswap['msgdict'] != [])
space_number += int(stats_load['msgdict'] != [])
if space_number < 1:
space_number = 1
if screen_x > (space_number * self.space_between_column + l):
self.space_between_column = int((screen_x - l) / space_number)
# Display
self.space_between_column = int((screen_x - l_ql - l) / space_number)
# Display others stats
if self.args.percpu:
self.display_plugin(stats_percpu)
else:
self.display_plugin(stats_cpu, display_optional=(screen_x >= 80))
self.new_column()
self.display_plugin(stats_load)
self.new_column()
self.display_plugin(stats_mem, display_optional=(
screen_x >= (space_number * self.space_between_column + l)))
self.display_plugin(stats_mem, display_optional=(screen_x >= 100))
self.new_column()
self.display_plugin(stats_memswap)
self.new_column()
self.display_plugin(stats_load)
# Space between column
self.space_between_column = 3
# Backup line position
self.saved_line = self.next_line
# ==================================================================
# Display left sidebar (NETWORK+DISKIO+FS+SENSORS+Current time)
# ==================================================================
self.init_column()
if (not (self.args.disable_network and self.args.disable_diskio
and self.args.disable_fs and self.args.disable_raid
and self.args.disable_sensors)) \
and not self.args.disable_left_sidebar:
if not (self.args.disable_network and self.args.disable_diskio and
self.args.disable_fs and self.args.disable_raid and
self.args.disable_sensors) and not self.args.disable_left_sidebar:
self.new_line()
self.display_plugin(stats_network)
self.new_line()
@ -534,6 +586,9 @@ class _GlancesCurses(object):
self.new_line()
self.display_plugin(stats_now)
# ====================================
# Display right stats (process and co)
# ====================================
# If space available...
if screen_x > 52:
# Restore line position
@ -546,7 +601,7 @@ class _GlancesCurses(object):
self.display_plugin(stats_docker)
self.new_line()
self.display_plugin(stats_processcount)
if glances_processes.get_process_filter() is None and cs_status == 'None':
if glances_processes.process_filter is None and cs_status is None:
# Do not display stats monitor list if a filter exist
self.new_line()
self.display_plugin(stats_monitor)
@ -562,34 +617,34 @@ class _GlancesCurses(object):
# Generate history graph
if self.history_tag and self.args.enable_history:
self.display_popup(
_("Generate graphs history in %s\nPlease wait...") % self.glances_history.get_output_folder())
'Generate graphs history in {0}\nPlease wait...'.format(
self.glances_history.get_output_folder()))
self.display_popup(
_("Generate graphs history in %s\nDone: %s graphs generated") % (self.glances_history.get_output_folder(), self.glances_history.generate_graph(stats)))
'Generate graphs history in {0}\nDone: {1} graphs generated'.format(
self.glances_history.get_output_folder(),
self.glances_history.generate_graph(stats)))
elif self.reset_history_tag and self.args.enable_history:
self.display_popup(_("Reset history"))
self.display_popup('Reset history')
self.glances_history.reset(stats)
elif (self.history_tag or self.reset_history_tag) and not self.args.enable_history:
try:
self.glances_history.graph_enabled()
except Exception:
self.display_popup(
_("History disabled\nEnable it using --enable-history"))
self.display_popup('History disabled\nEnable it using --enable-history')
else:
self.display_popup(
_("History disabled\nPlease install MatPlotLib"))
self.display_popup('History disabled\nPlease install matplotlib')
self.history_tag = False
self.reset_history_tag = False
# Display edit filter popup
# Only in standalone mode (cs_status == 'None')
if self.edit_filter and cs_status == 'None':
new_filter = self.display_popup(_("Process filter pattern: "),
is_input=True,
input_value=glances_processes.get_process_filter())
glances_processes.set_process_filter(new_filter)
# Only in standalone mode (cs_status is None)
if self.edit_filter and cs_status is None:
new_filter = self.display_popup(
'Process filter pattern: ', is_input=True,
input_value=glances_processes.process_filter)
glances_processes.process_filter = new_filter
elif self.edit_filter and cs_status != 'None':
self.display_popup(
_("Process filter only available in standalone mode"))
self.display_popup('Process filter only available in standalone mode')
self.edit_filter = False
return True
@ -601,18 +656,20 @@ class _GlancesCurses(object):
input_size=30,
input_value=None):
"""
Display a centered popup.
If is_input is False:
Display a centered popup with the given message during duration seconds
If size_x and size_y: set the popup size
else set it automatically
Return True if the popup could be displayed
If is_input is True:
Display a centered popup with the given message and a input field
If size_x and size_y: set the popup size
else set it automatically
Return the input string or None if the field is empty
"""
# Center the popup
sentence_list = message.split('\n')
if size_x is None:
@ -637,10 +694,8 @@ class _GlancesCurses(object):
popup.border()
# Add the message
y = 0
for m in message.split('\n'):
for y, m in enumerate(message.split('\n')):
popup.addnstr(2 + y, 2, m, len(m))
y += 1
if is_input and not is_windows:
# Create a subwindow for the text field
@ -683,7 +738,7 @@ class _GlancesCurses(object):
# Exit if:
# - the plugin_stats message is empty
# - the display tag = False
if not plugin_stats['msgdict'] or not plugin_stats['display']:
if plugin_stats is None or not plugin_stats['msgdict'] or not plugin_stats['display']:
# Exit
return 0
@ -711,7 +766,7 @@ class _GlancesCurses(object):
# New line
if m['msg'].startswith('\n'):
# Go to the next line
y = y + 1
y += 1
# Return to the first column
x = display_x
continue
@ -743,24 +798,25 @@ class _GlancesCurses(object):
try:
# Python 2: we need to decode to get real screen size because utf-8 special tree chars
# occupy several bytes
offset = len(m['msg'].decode("utf-8"))
offset = len(m['msg'].decode("utf-8", "replace"))
except AttributeError:
# Python 3: strings are strings and bytes are bytes, all is
# good
offset = len(m['msg'])
x = x + offset
x += offset
if x > x_max:
x_max = x
# Compute the next Glances column/line position
self.next_column = max(self.next_column, x_max + self.space_between_column)
self.next_column = max(
self.next_column, x_max + self.space_between_column)
self.next_line = max(self.next_line, y + self.space_between_line)
def erase(self):
"""Erase the content of the screen."""
self.term_window.erase()
def flush(self, stats, cs_status="None"):
def flush(self, stats, cs_status=None):
"""Clear and update the screen.
stats: Stats database to display
@ -772,7 +828,7 @@ class _GlancesCurses(object):
self.erase()
self.display(stats, cs_status=cs_status)
def update(self, stats, cs_status="None", return_to_browser=False):
def update(self, stats, cs_status=None, return_to_browser=False):
"""Update the screen.
Wait for __refresh_time sec / catch key every 100 ms.
@ -818,11 +874,11 @@ class _GlancesCurses(object):
try:
if without_option:
# Size without options
c = len(max(''.join([(i['msg'] if not i['optional'] else "")
c = len(max(''.join([(re.sub(r'[^\x00-\x7F]+', ' ', i['msg']) if not i['optional'] else "")
for i in curse_msg['msgdict']]).split('\n'), key=len))
else:
# Size with all options
c = len(max(''.join([i['msg']
c = len(max(''.join([re.sub(r'[^\x00-\x7F]+', ' ', i['msg'])
for i in curse_msg['msgdict']]).split('\n'), key=len))
except Exception:
return 0
@ -844,21 +900,21 @@ class _GlancesCurses(object):
class GlancesCursesStandalone(_GlancesCurses):
"""Class for the Glances' curse standalone"""
"""Class for the Glances curse standalone."""
pass
class GlancesCursesClient(_GlancesCurses):
"""Class for the Glances' curse client"""
"""Class for the Glances curse client."""
pass
class GlancesCursesBrowser(_GlancesCurses):
"""Class for the Glances' curse client browser"""
"""Class for the Glances curse client browser."""
def __init__(self, args=None):
# Init the father class
@ -881,48 +937,44 @@ class GlancesCursesBrowser(_GlancesCurses):
self.__refresh_time = args.time
# Init the cursor position for the client browser
self.cursor_init()
self.cursor_position = 0
# Active Glances server number
self.set_active()
self._active_server = None
def set_active(self, index=None):
"""Set the active server or None if no server selected"""
self.active_server = index
return self.active_server
@property
def active_server(self):
"""Return the active server or None if it's the browser list."""
return self._active_server
def get_active(self):
"""Return the active server (the one display in front) or None if it is the browser list"""
return self.active_server
@active_server.setter
def active_server(self, index):
"""Set the active server or None if no server selected."""
self._active_server = index
def cursor_init(self):
"""Init the cursor position to the top of the list"""
return self.cursor_set(0)
def cursor_set(self, pos):
"""Set the cursor position and return it"""
self.cursor_position = pos
@property
def cursor(self):
"""Get the cursor position."""
return self.cursor_position
def cursor_get(self):
"""Return the cursor position"""
return self.cursor_position
@cursor.setter
def cursor(self, position):
"""Set the cursor position."""
self.cursor_position = position
def cursor_up(self, servers_list):
"""Set the cursor to position N-1 in the list"""
"""Set the cursor to position N-1 in the list."""
if self.cursor_position > 0:
self.cursor_position -= 1
else:
self.cursor_position = len(servers_list) - 1
return self.cursor_position
def cursor_down(self, servers_list):
"""Set the cursor to position N-1 in the list"""
"""Set the cursor to position N-1 in the list."""
if self.cursor_position < len(servers_list) - 1:
self.cursor_position += 1
else:
self.cursor_position = 0
return self.cursor_position
def __catch_key(self, servers_list):
# Catch the browser pressed key
@ -936,11 +988,10 @@ class GlancesCursesBrowser(_GlancesCurses):
sys.exit(0)
elif self.pressedkey == 10:
# 'ENTER' > Run Glances on the selected server
logger.debug("Server number %s selected" % (self.cursor_get() + 1))
self.set_active(self.cursor_get())
logger.debug("Server number {0} selected".format(self.cursor + 1))
self.active_server = self.cursor
elif self.pressedkey == 259:
# 'UP' > Up in the server list
logger
self.cursor_up(servers_list)
elif self.pressedkey == 258:
# 'DOWN' > Down in the server list
@ -957,6 +1008,7 @@ class GlancesCursesBrowser(_GlancesCurses):
servers_list: Dict of dict with servers stats
"""
# Flush display
logger.debug("Servers list: {}".format(servers_list))
self.flush(servers_list)
# Wait
@ -974,17 +1026,19 @@ class GlancesCursesBrowser(_GlancesCurses):
# Wait 100ms...
curses.napms(100)
return self.get_active()
return self.active_server
def flush(self, servers_list):
"""Update the servers' list screen.
servers_list: List of dict with servers stats
"""
self.erase()
self.display(servers_list)
def display(self, servers_list):
"""Display the servers list
"""Display the servers list.
Return:
True if the stats have been displayed
False if the stats have not been displayed (no server available)
@ -1003,17 +1057,16 @@ class GlancesCursesBrowser(_GlancesCurses):
# Display top header
if len(servers_list) == 0:
if self.first_scan and not self.args.disable_autodiscover:
msg = _("Glances is scanning your network (please wait)...")
msg = 'Glances is scanning your network. Please wait...'
self.first_scan = False
else:
msg = _("No Glances servers available")
msg = 'No Glances server available'
elif len(servers_list) == 1:
msg = _("One Glances server available")
msg = 'One Glances server available'
else:
msg = _("%d Glances servers available" %
len(servers_list))
msg = '{0} Glances servers available'.format(len(servers_list))
if self.args.disable_autodiscover:
msg += ' ' + _("(auto discover is disabled)")
msg += ' ' + '(auto discover is disabled)'
self.term_window.addnstr(y, x,
msg,
screen_x - x,
@ -1023,41 +1076,39 @@ class GlancesCursesBrowser(_GlancesCurses):
return False
# Display the Glances server list
#================================
# ================================
# Table of table
# Item description: [stats_id, column name, column size]
column_def = [
['name', _('Name'), 16],
['name', 'Name', 16],
['alias', None, None],
['load_min5', _('LOAD'), 6],
['cpu_percent', _('CPU%'), 5],
['mem_percent', _('MEM%'), 5],
['status', _('STATUS'), 8],
['ip', _('IP'), 15],
# ['port', _('PORT'), 5],
['hr_name', _('OS'), 16],
['load_min5', 'LOAD', 6],
['cpu_percent', 'CPU%', 5],
['mem_percent', 'MEM%', 5],
['status', 'STATUS', 8],
['ip', 'IP', 15],
# ['port', 'PORT', 5],
['hr_name', 'OS', 16],
]
y = 2
# Display table header
cpt = 0
xc = x + 2
for c in column_def:
for cpt, c in enumerate(column_def):
if xc < screen_x and y < screen_y and c[1] is not None:
self.term_window.addnstr(y, xc,
c[1],
screen_x - x,
self.colors_list['BOLD'])
xc += c[2] + self.space_between_column
cpt += 1
y += 1
# If a servers has been deleted from the list...
# ... and if the cursor is in the latest position
if self.cursor_get() > len(servers_list) - 1:
if self.cursor > len(servers_list) - 1:
# Set the cursor position to the latest item
self.cursor_set(len(servers_list) - 1)
self.cursor = len(servers_list) - 1
# Display table
line = 0
@ -1075,7 +1126,7 @@ class GlancesCursesBrowser(_GlancesCurses):
try:
if c[0] == 'alias' and v[c[0]] is not None:
server_stat['name'] = v[c[0]]
except KeyError as e:
except KeyError:
pass
# Display line for server stats
@ -1083,25 +1134,18 @@ class GlancesCursesBrowser(_GlancesCurses):
xc = x
# Is the line selected ?
if line == self.cursor_get():
if line == self.cursor:
# Display cursor
self.term_window.addnstr(y, xc,
">",
screen_x - xc,
self.colors_list['BOLD'])
# Display alias instead of name
server_stat
self.term_window.addnstr(
y, xc, ">", screen_x - xc, self.colors_list['BOLD'])
# Display the line
xc += 2
for c in column_def:
if xc < screen_x and y < screen_y and c[1] is not None:
# Display server stats
self.term_window.addnstr(y, xc,
"%s" % server_stat[c[0]],
c[2],
self.colors_list[v['status']])
self.term_window.addnstr(
y, xc, format(server_stat[c[0]]), c[2], self.colors_list[v['status']])
xc += c[2] + self.space_between_column
cpt += 1
# Next line, next server...

View File

@ -3,31 +3,48 @@ body {
color: #BBB;
font-family: "Lucida Sans Typewriter", "Lucida Console", Monaco, "Bitstream Vera Sans Mono", monospace;
}
.plugin {
margin-bottom: 20px;
}
.plugin table {
.table {
display: table;
width: 100%;
}
.plugin table tr td:not(:first-child) {
.table-row-group {
display: table-row-group
}
.table-row {
display: table-row;
}
.table-cell {
display: table-cell;
text-align: right;
}
.underline{
.plugin {
margin-bottom: 20px;
}
.plugin.table-row-group .table-row:last-child .table-cell {
padding-bottom: 20px;
}
.underline {
text-decoration: underline
}
.bold{
.bold {
font-weight: bold;
}
.sort{
.sort {
font-weight: bold;
}
.sort:after{
content: '\25BC'
color: white;
}
.text-right {
text-align: right;
}
.text-left {
text-align: left;
}
.sidebar .table-cell:not(.text-left) {
padding-left: 10px;
}
/* Theme */
@ -35,6 +52,10 @@ body {
font-weight: bold;
color: white;
}
.highlight {
font-weight: bold;
color: magenta;
}
.ok {
color: green;
}
@ -53,10 +74,10 @@ body {
color: white;
}
.warning {
color: orange;
color: magenta;
}
.warning_log {
background-color: orange;
background-color: magenta;
color: white;
}
.critical {
@ -77,25 +98,85 @@ body {
}
/* Plugins */
#cpu table tr td:nth-child(3),
#mem table tr td:nth-child(3),
#monitor table tr td:nth-child(3) {
text-align: left;
padding-left: 20px;
}
#processlist table tr td {
text-align: right;
}
#processlist table tr td,
#docker table tr td {
#processlist .table-cell, #containers .table-cell {
padding: 0px 5px 0px 5px;
white-space: nowrap;
}
#processlist table tr td:nth-child(6),
#processlist table tr td:nth-child(12) {
gl-monitor-list {
display: block;
}
gl-monitor-list .table-cell {
text-align: left;
}
#docker table tr td:nth-child(2),
#docker table tr td:nth-child(6) {
text-align: left;
/* Loading page */
#loading-page .glances-logo {
background: url('glances.png') no-repeat center center;
background-size: contain;
}
@media (max-width: 750px) {
#loading-page .glances-logo {
height: 400px;
}
}
@media (min-width: 750px) {
#loading-page .glances-logo {
height: 500px;
}
}
/*
Loading animation
source : https://github.com/lukehaas/css-loaders
*/
#loading-page .loader:before,
#loading-page .loader:after,
#loading-page .loader {
border-radius: 50%;
width: 1em;
height: 1em;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation: loader 1.8s infinite ease-in-out;
animation: loader 1.8s infinite ease-in-out;
}
#loading-page .loader {
margin: auto;
font-size: 10px;
position: relative;
text-indent: -9999em;
-webkit-animation-delay: 0.16s;
animation-delay: 0.16s;
}
#loading-page .loader:before {
left: -3.5em;
}
#loading-page .loader:after {
left: 3.5em;
-webkit-animation-delay: 0.32s;
animation-delay: 0.32s;
}
#loading-page .loader:before,
#loading-page .loader:after {
content: '';
position: absolute;
top: 0;
}
@-webkit-keyframes loader {
0%, 80%, 100% {
box-shadow: 0 2.5em 0 -1.3em #56CA69;
}
40% {
box-shadow: 0 2.5em 0 0 #56CA69;
}
}
@keyframes loader {
0%, 80%, 100% {
box-shadow: 0 2.5em 0 -1.3em #56CA69;
}
40% {
box-shadow: 0 2.5em 0 0 #56CA69;
}
}

View File

@ -0,0 +1,4 @@
<div class="table-cell {{ descriptionClass }}">{{ process.description }}</div>
<div class="table-cell">{{ process.count > 1 ? process.count : '' }}</div>
<div class="table-cell">{{ process.count > 0 ? 'RUNNING' : 'NOT RUNNING' }}</div>
<div class="table-cell">{{ process.result }}</div>

View File

@ -0,0 +1,63 @@
<div class="row">
<div class="col-sm-2 col-lg-10">{{help.version}} {{help.psutil_version}}</div>
</div>
<div class="row">&nbsp;</div>
<div class="row">
<div class="col-sm-2 col-lg-10">{{help.configuration_file}}</div>
</div>
<div class="row">&nbsp;</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.sort_auto}}</div>
<div class="col-sm-2 col-lg-3">{{help.sort_network}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.sort_cpu}}</div>
<div class="col-sm-2 col-lg-3">{{help.show_hide_alert}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.show_mem}}</div>
<div class="col-sm-2 col-lg-3">{{help.delete_warning_alerts}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.sort_proc}}</div>
<div class="col-sm-2 col-lg-3">{{help.delete_warning_critical_alerts}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.sort_io}}</div>
<div class="col-sm-2 col-lg-3">{{help.percpu}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.show_hide_help}}</div>
<div class="col-sm-2 col-lg-3">{{help.show_hide_diskio}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.view_network_io_combination}}</div>
<div class="col-sm-2 col-lg-3">{{help.view_cumulative_network}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.show_hide_network}}</div>
<div class="col-sm-2 col-lg-3">{{help.show_hide_filesytem_freespace}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.show_hide_sensors}}</div>
<div class="col-sm-2 col-lg-3">{{help.generate_graphs}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.show_hide_left_sidebar}}</div>
<div class="col-sm-2 col-lg-3">{{help.reset_history}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.enable_disable_process_stats}}</div>
<div class="col-sm-2 col-lg-3">{{help.quit}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.enable_disable_top_extends_stats}}</div>
<div class="col-sm-2 col-lg-3">{{help.enable_disable_short_processname}}</div>
</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.enable_disable_docker}}</div>
</div>
<div class="row">&nbsp;</div>
<div class="row">
<div class="col-sm-2 col-lg-3">{{help.edit_pattern_filter}}</div>
</div>

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html ng-app="glancesApp">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Glances</title>
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link rel="stylesheet" type="text/css" href="normalize.css" />
<link rel="stylesheet" type="text/css" href="bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="style.css" />
<script type="text/javascript" src="vendors/angular.min.js"></script>
<script type="text/javascript" src="vendors/angular-route.min.js"></script>
<script type="text/javascript" src="vendors/lodash.min.js"></script>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript" src="filters.js"></script>
<script type="text/javascript" src="variables.js"></script>
<script type="text/javascript" src="directives.js"></script>
<script type="text/javascript" src="stats_controller.js"></script>
</head>
<body ng-view="" ng-keydown="onKeyDown($event)">
</body>
</html>

View File

@ -0,0 +1,7 @@
<div class="table">
<div class="table-row" ng-repeat="alert in result['alert']">
<div class="table-cell text-left">
{{alert.begin | date : 'yyyy-MM-dd H:mm:ss'}} ({{ alert.ongoing ? 'ongoing' : alert.duration }}) - {{alert[2]}} on <span class="{{ alert[2] | lowercase }}">{{alert[3]}}</span> ({{alert[4]}})
</div>
</div>
</div>

View File

@ -0,0 +1,2 @@
<span class="title" ng-show="!result['alert'].length">No warning or critical alert detected</span>
<span class="title" ng-show="result['alert'].length">Warning or critical alerts (lasts {{result['alert'].length}} entries)</span>

View File

@ -0,0 +1,44 @@
<div class="table" ng-show="!show.per_cpu">
<div class="table-row">
<div class="table-cell text-left title">CPU</div>
<div class="table-cell">{{result["cpu"].total}}%</div>
</div>
<div class="table-row">
<div class="table-cell text-left">user:</div>
<div class="table-cell" ng-class="getAlertLog('cpu', 'cpu_user_', result['cpu'].user)">
{{result["cpu"].user}}%
</div>
</div>
<div class="table-row">
<div class="table-cell text-left">system:</div>
<div class="table-cell" ng-class="getAlertLog('cpu', 'cpu_system_', result['cpu'].system)">
{{result["cpu"].system}}%
</div>
</div>
<div class="table-row">
<div class="table-cell text-left">idle:</div>
<div class="table-cell">{{result["cpu"].idle}}%</div>
</div>
</div>
<div class="table" ng-show="show.per_cpu">
<div class="table-row">
<div class="table-cell text-left title">PER CPU</div>
<div class="table-cell" ng-repeat="percpu in result.percpu">{{percpu.total}}%</div>
</div>
<div class="table-row">
<div class="table-cell text-left">user:</div>
<div class="table-cell" ng-repeat="percpu in result.percpu" ng-class="getAlert('percpu', 'percpu_user_', percpu.user)">
{{percpu.user}}%
</div>
</div>
<div class="table-row">
<div class="table-cell text-left">system:</div>
<div class="table-cell" ng-repeat="percpu in result.percpu" ng-class="getAlert('percpu', 'percpu_system_', percpu.system)">
{{percpu.system}}%
</div>
</div>
<div class="table-row">
<div class="table-cell text-left">idle:</div>
<div class="table-cell" ng-repeat="percpu in result.percpu">{{percpu.idle}}%</div>
</div>
</div>

View File

@ -0,0 +1,26 @@
<div class="table">
<div class="table-row">
<div class="table-cell text-left">nice:</div>
<div class="table-cell">
{{result["cpu"].nice}}%
</div>
</div>
<div class="table-row" ng-if="result['cpu'].irq != undefined">
<div class="table-cell text-left">irq:</div>
<div class="table-cell">
{{result["cpu"].irq}}%
</div>
</div>
<div class="table-row" ng-if="result['cpu'].iowait != undefined">
<div class="table-cell text-left">iowait:</div>
<div class="table-cell" ng-class="getAlertLog('cpu', 'cpu_iowait_', result['cpu'].iowait)">
{{result["cpu"].iowait}}%
</div>
</div>
<div class="table-row" ng-if="result['cpu'].steal != undefined">
<div class="table-cell text-left">steal:</div>
<div class="table-cell" ng-class="getAlert('cpu', 'cpu_steal_', result['cpu'].steal)">
{{result["cpu"].steal}}%
</div>
</div>
</div>

View File

@ -0,0 +1,10 @@
<div class="table-row">
<div class="table-cell text-left title">DISK I/O</div>
<div class="table-cell">R/s</div>
<div class="table-cell">W/s</div>
</div>
<div class="table-row" ng-repeat="disk in result['diskio'] | orderBy: 'disk_name'">
<div class="table-cell text-left">{{disk.disk_name | min_size}}</div>
<div class="table-cell">{{disk.read_bytes | bytes}}</div>
<div class="table-cell">{{disk.write_bytes | bytes}}</div>
</div>

View File

@ -0,0 +1,20 @@
<span class="title">CONTAINERS</span> {{ result['docker']['containers'].length }} (served by Docker {{ result['docker']['version']['Version'] }})
<div class="table">
<div class="table-row">
<div class="table-cell">Id</div>
<div class="table-cell text-left">Name</div>
<div class="table-cell">Status</div>
<div class="table-cell">CPU%</div>
<div class="table-cell">MEM</div>
<div class="table-cell text-left">Command</div>
</div>
<div class="table-row" ng-repeat="container in result['docker']['containers']">
<div class="table-cell">{{ container.Id | limitTo:12 }}</div>
<div class="table-cell text-left">{{ container.Names[0] }}</div>
<div class="table-cell" ng-class="container.Status == 'Paused' ? 'careful' : 'ok'">{{ container.Status }}</div>
<div class="table-cell">{{ container.cpu.total | number:1 }}</div>
<div class="table-cell">{{ container.memory.usage | bytes }}</div>
<div class="table-cell text-left">{{ container.Command }}</div>
</div>
</div>

View File

@ -0,0 +1,12 @@
<div class="table-row">
<div class="table-cell text-left title">FILE SYS</div>
<div class="table-cell">Used</div>
<div class="table-cell">Total</div>
</div>
<div class="table-row" ng-repeat="fs in result['fs'] | orderBy: 'mnt_point'">
<div class="table-cell text-left">{{fs.mnt_point}} ({{fs.device_name}})</div>
<div class="table-cell" ng-class="getAlert('fs', 'fs_', fs.percent)">
{{fs.size - fs.free | bytes}}
</div>
<div class="table-cell">{{fs.size | bytes}}</div>
</div>

View File

@ -0,0 +1 @@
&nbsp;-&nbsp;<span class="title">IP</span> <span>{{result["ip"].address}}/{{result["ip"].mask_cidr}}</span>

View File

@ -0,0 +1,24 @@
<div class="table">
<div class="table-row">
<div class="table-cell text-left title">LOAD</div>
<div class="table-cell">{{result["load"].cpucore}}-core</div>
</div>
<div class="table-row">
<div class="table-cell text-left">1 min:</div>
<div class="table-cell">
{{result["load"].min1 | number : 2}}
</div>
</div>
<div class="table-row">
<div class="table-cell text-left">5 min:</div>
<div class="table-cell" ng-class="getAlert('load', 'load_', result['load'].min5, 100 * result['load'].cpucore)">
{{result["load"].min5 | number : 2}}
</div>
</div>
<div class="table-row">
<div class="table-cell text-left">15 min:</div>
<div class="table-cell" ng-class="getAlertLog('load', 'load_', result['load'].min15, 100 * result['load'].cpucore)">
{{result["load"].min15 | number : 2}}
</div>
</div>
</div>

View File

@ -0,0 +1,20 @@
<div class="table">
<div class="table-row">
<div class="table-cell text-left title">MEM</div>
<div class="table-cell">{{result["mem"].percent}}%</div>
</div>
<div class="table-row">
<div class="table-cell text-left">total:</div>
<div class="table-cell">{{result["mem"].total | bytes}}</div>
</div>
<div class="table-row">
<div class="table-cell text-left">used:</div>
<div class="table-cell" ng-class="getAlertLog('mem', 'mem_', result['mem'].percent)">
{{result["mem"].used | bytes:2}}
</div>
</div>
<div class="table-row">
<div class="table-cell text-left">free:</div>
<div class="table-cell">{{result["mem"].free | bytes}}</div>
</div>
</div>

View File

@ -0,0 +1,18 @@
<div class="table">
<div class="table-row">
<div class="table-cell text-left">active:</div>
<div class="table-cell">{{result["mem"].active | bytes}}</div>
</div>
<div class="table-row">
<div class="table-cell text-left">inactive:</div>
<div class="table-cell">{{result["mem"].inactive | bytes}}</div>
</div>
<div class="table-row" ng-if="result['mem'].buffers != undefined">
<div class="table-cell text-left">buffers:</div>
<div class="table-cell">{{result["mem"].buffers | bytes}}</div>
</div>
<div class="table-row" ng-if="result['mem'].cached != undefined">
<div class="table-cell text-left">cached:</div>
<div class="table-cell">{{result["mem"].cached | bytes}}</div>
</div>
</div>

View File

@ -0,0 +1,20 @@
<div class="table">
<div class="table-row">
<div class="table-cell text-left title">SWAP</div>
<div class="table-cell">{{result["memswap"].percent}}%</div>
</div>
<div class="table-row">
<div class="table-cell text-left">total:</div>
<div class="table-cell">{{result["memswap"].total | bytes}}</div>
</div>
<div class="table-row">
<div class="table-cell text-left">used:</div>
<div class="table-cell" ng-class="getAlertLog('memswap', 'memswap_', result['memswap'].percent)">
{{result["memswap"].used | bytes}}
</div>
</div>
<div class="table-row">
<div class="table-cell text-left">free:</div>
<div class="table-cell">{{result["memswap"].free | bytes}}</div>
</div>
</div>

View File

@ -0,0 +1,3 @@
<div class="table">
<gl-monitor-process class="table-row" ng-repeat="process in processes" process="process"></gl-monitor-process>
</div>

View File

@ -0,0 +1,12 @@
<div class="table-row">
<div class="table-cell text-left title">NETWORK</div>
<div class="table-cell">Rx/s</div>
<div class="table-cell">Tx/s</div>
</div>
<div class="table-row" ng-repeat="network in result['network'] | orderBy: 'interface_name'">
<div class="table-cell text-left">{{network.interface_name | min_size}}</div>
<div class="table-cell" ng-if="show.network_by_bytes">{{network.rx | bytes}}</div>
<div class="table-cell" ng-if="show.network_by_bytes">{{network.tx | bytes}}</div>
<div class="table-cell" ng-if="!show.network_by_bytes">{{network.rx | bits}}</div>
<div class="table-cell" ng-if="!show.network_by_bytes">{{network.tx | bits}}</div>
</div>

View File

@ -0,0 +1,6 @@
<span class="title">TASKS</span>
<span>{{result["processcount"].total}} ({{result["processcount"].thread}} thr),</span>
<span>{{result["processcount"].running}} run,</span>
<span>{{result["processcount"].sleeping}} slp,</span>
<span>{{result["processcount"].stopped}} oth</span>
<span> sorted {{ sorter.auto ? 'automatically' : '' }} by {{ sorter.getColumnLabel(sorter.column) }}, flat view</span>

View File

@ -0,0 +1,33 @@
<div class="table">
<div class="table-row">
<div sortable-th sorter="sorter" column="cpu_percent" class="table-cell">CPU%</div>
<div sortable-th sorter="sorter" column="memory_percent" class="table-cell">MEM%</div>
<div class="table-cell hidden-xs hidden-sm">VIRT</div>
<div class="table-cell hidden-xs hidden-sm">RES</div>
<div class="table-cell">PID</div>
<div sortable-th sorter="sorter" column="username" class="table-cell text-left">USER</div>
<div class="table-cell">NI</div>
<div class="table-cell">S</div>
<div sortable-th sorter="sorter" column="timemillis" class="table-cell hidden-xs hidden-sm">TIME+</div>
<div sortable-th sorter="sorter" column="io_read" class="table-cell hidden-xs hidden-sm">IOR/s</div>
<div sortable-th sorter="sorter" column="io_write" class="table-cell hidden-xs hidden-sm">IOW/s</div>
<div sortable-th sorter="sorter" column="name" class="table-cell text-left">Command</div>
</div>
<div class="table-row" ng-repeat="process in result['processlist'] | orderBy:sorter.column:sorter.isReverseColumn(sorter.column)">
<div class="table-cell" ng-class="getAlert('processlist', 'processlist_cpu_', process.cpu_percent)">{{process.cpu_percent | number:1}}</div>
<div class="table-cell" ng-class="getAlert('processlist', 'processlist_mem_', process.memory_percent)">{{process.memory_percent | number:1}}</div>
<div class="table-cell hidden-xs hidden-sm">{{process.memvirt | bytes}}</div>
<div class="table-cell hidden-xs hidden-sm">{{process.memres | bytes}}</div>
<div class="table-cell">{{process.pid}}</div>
<div class="table-cell text-left">{{process.username}}</div>
<div class="table-cell" ng-class="{nice: isNice(process.nice)}">{{process.nice | exclamation}}</div>
<div class="table-cell" ng-class="{status: process.status == 'R'}">{{process.status}}</div>
<div class="table-cell hidden-xs hidden-sm">
<span ng-show="process.timeplus.hours > 0" class="highlight">{{ process.timeplus.hours }}h</span>{{ process.timeplus.minutes | leftPad:2:'0' }}:{{ process.timeplus.seconds | leftPad:2:'0' }}<span ng-show="process.timeplus.hours <= 0">.{{ process.timeplus.milliseconds | leftPad:2:'0' }}</span>
</div>
<div class="table-cell hidden-xs hidden-sm">{{process.io_read}}</div>
<div class="table-cell hidden-xs hidden-sm">{{process.io_write}}</div>
<div class="table-cell text-left" ng-if="show.short_process_name">{{process.name}}</div>
<div class="table-cell text-left" ng-if="!show.short_process_name">{{process.cmdline}}</div>
</div>
</div>

View File

@ -0,0 +1,13 @@
<div class="table-row">
<div class="table-cell text-left title">SENSORS</div>
</div>
<div class="table-row" ng-repeat="sensor in result['sensors']">
<div class="table-cell text-left">{{ sensor.label }}</div>
<div class="table-cell">{{ sensor.unit }}</div>
<div class="table-cell" ng-if="sensor.type != 'battery'" ng-class="getAlert('sensors', 'sensors_' + sensor.type + '_', sensor.value)">
{{ sensor.value }}
</div>
<div class="table-cell" ng-if="sensor.type == 'battery'" ng-class="getAlert('sensors', 'sensors_' + sensor.type + '_', 100 - sensor.value)">
{{ sensor.value }}
</div>
</div>

View File

@ -0,0 +1,3 @@
<span class="title">{{ result["system"].hostname }}</span>
<span ng-if="is_linux" class="hidden-xs hidden-sm">({{ result["system"].hr_name }} / {{ result["system"].os_name }} {{ result["system"].os_version }})</span>
<span ng-if="!is_linux" class="hidden-xs hidden-sm">({{ result["system"].os_name }} {{ result["system"].os_version }} {{ result["system"].platform }})</span>

View File

@ -0,0 +1 @@
<span>Uptime: {{result["uptime"]}}</span>

View File

@ -0,0 +1,64 @@
<div ng-show="!result" class="container-fluid" id="loading-page">
<div class="glances-logo"></div>
<div class="loader">Loading...</div>
</div>
<div ng-show="help_screen" class="container-fluid" ng-include src="'help.html'"></div>
<div ng-show="result && !help_screen" class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="pull-left">
<section id="system" class="plugin" ng-include src="'plugins/system.html'"></section>
</div>
<div class="pull-left">
<section id="ip" class="plugin" ng-if="result['ip'].address != undefined" ng-include src="'plugins/ip.html'"></section>
</div>
<div class="pull-right">
<section id="uptime" class="plugin" ng-include src="'plugins/uptime.html'"></section>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-3 col-md-2">
<section id="cpu" class="plugin" ng-include src="'plugins/cpu.html'"></section>
</div>
<div class="hidden-xs hidden-sm col-md-2">
<section id="cpu_more" class="plugin" ng-if="result['cpu'].nice != undefined" ng-include src="'plugins/cpu_more.html'"></section>
</div>
<div class="col-sm-3 col-md-2">
<section id="load" class="plugin" ng-if="result['load'].cpucore != undefined" ng-include src="'plugins/load.html'"></section>
</div>
<div class="col-sm-3 col-md-2">
<section id="mem" class="plugin" ng-include src="'plugins/mem.html'"></section>
</div>
<div class="hidden-xs hidden-sm col-md-2">
<section id="mem_more" class="plugin" ng-include src="'plugins/mem_more.html'"></section>
</div>
<div class="col-sm-3 col-md-2">
<section id="memswap" class="plugin" ng-include src="'plugins/memswap.html'"></section>
</div>
</div>
<div class="row">
<div class="col-sm-3 sidebar" ng-show="show.sidebar">
<div class="table">
<section id="network" class="plugin table-row-group" ng-show="show.network" ng-include src="'plugins/network.html'"></section>
<section id="diskio" class="plugin table-row-group" ng-show="show.diskio" ng-include src="'plugins/diskio.html'"></section>
<section id="fs" class="plugin table-row-group" ng-show="show.fs" ng-include src="'plugins/fs.html'"></section>
<section id="sensors" class="plugin table-row-group" ng-show="show.sensors && result['sensors'].length > 0" ng-include src="'plugins/sensors.html'"></section>
</div>
</div>
<div class="col-sm-9">
<section id="containers" class="plugin" ng-if="result['docker']['containers'].length" ng-include src="'plugins/docker.html'"></section>
<section id="alerts" ng-show="show.alert" ng-include src="'plugins/alerts.html'"></section>
<section id="alert" class="plugin" ng-show="show.alert" ng-include src="'plugins/alert.html'"></section>
<section id="processcount" class="plugin" ng-include src="'plugins/processcount.html'"></section>
<div class="row">
<div class="col-lg-9">
<gl-monitor-list class="plugin" processes="result['monitor']"></gl-monitor-list>
</div>
</div>
<section id="processlist" class="plugin" ng-include src="'plugins/processlist.html'"></section>
</div>
</div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -0,0 +1,13 @@
var glancesApp = angular.module('glancesApp', ['ngRoute'])
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/', {
templateUrl : 'stats.html',
controller : 'statsController'
}).when('/:refresh_time', {
templateUrl : 'stats.html',
controller : 'statsController'
});
$locationProvider.html5Mode(true);
});

View File

@ -0,0 +1,78 @@
glancesApp.directive("sortableTh", function() {
return {
restrict: 'A',
scope: {
sorter: '='
},
link: function (scope, element, attrs) {
scope.$watch(function() {
return scope.sorter.column;
}, function(newValue, oldValue) {
if (angular.isArray(newValue)) {
if (newValue.indexOf(attrs.column) !== -1) {
element.addClass('sort');
} else {
element.removeClass('sort');
}
} else {
if (attrs.column === newValue) {
element.addClass('sort');
} else {
element.removeClass('sort');
}
}
});
element.on('click', function() {
scope.sorter.column = attrs.column;
scope.$apply();
});
}
};
});
glancesApp.directive("glMonitorList", function() {
return {
restrict: 'AE',
scope: {
processes: '='
},
templateUrl: 'plugins/monitor.html',
controller: function() {
}
}
})
glancesApp.directive("glMonitorProcess", function() {
return {
restrict: 'AE',
require: "^glMonitorList",
templateUrl: 'components/monitor_process.html',
scope: {
process: '='
},
link: function(scope, element, attrs) {
count = scope.process.count;
countMin = scope.process.countmin;
countMax = scope.process.countmax;
if (count > 0) {
if ((countMin == null || count >= countMin) && (countMax == null || count <= countMax)) {
scope.descriptionClass = 'ok';
} else {
scope.descriptionClass = 'careful';
}
} else {
scope.descriptionClass = countMin == null ? 'ok' : 'critical';
}
}
}
});

View File

@ -0,0 +1,86 @@
glancesApp.filter('min_size', function() {
return function(input) {
var max = 8;
if (input.length > max) {
return "_" + input.substring(input.length - max)
}
return input
};
});
glancesApp.filter('exclamation', function() {
return function(input) {
if (input === undefined || input === '') {
return '?';
}
return input;
};
});
glancesApp.filter('bytes', function() {
return function (bytes, low_precision) {
low_precision = low_precision || false;
if (isNaN(parseFloat(bytes)) || !isFinite(bytes) || bytes == 0){
return '0';
}
var symbols = ['K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
var prefix = {
'Y': 1208925819614629174706176,
'Z': 1180591620717411303424,
'E': 1152921504606846976,
'P': 1125899906842624,
'T': 1099511627776,
'G': 1073741824,
'M': 1048576,
'K': 1024
};
var reverseSymbols = _(symbols).reverse().value();
for (var i = 0; i < reverseSymbols.length; i++) {
var symbol = reverseSymbols[i];
var value = bytes / prefix[symbol];
if(value > 1) {
var decimal_precision = 0;
if(value < 10) {
decimal_precision = 2;
}
else if(value < 100) {
decimal_precision = 1;
}
if(low_precision) {
if(symbol == 'MK') {
decimal_precision = 0;
}
else {
decimal_precision = _.min([1, decimal_precision]);
}
}
else if(symbol == 'K') {
decimal_precision = 0;
}
return parseFloat(value).toFixed(decimal_precision) + symbol;
}
}
return bytes;
}
});
glancesApp.filter('bits', function($filter) {
return function (bits, low_precision) {
bits = Math.round(bits) * 8;
return $filter('bytes')(bits, low_precision) + 'b';
}
});
glancesApp.filter('leftPad', function($filter) {
return function (value, length, chars) {
length = length || 0;
chars = chars || ' ';
return _.padLeft(value, length, chars);
}
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,294 @@
glancesApp.controller('statsController', function($scope, $http, $interval, $q, $routeParams, $filter) {
$scope.limitSuffix = ['critical', 'careful', 'warning'];
$scope.refreshTime = 3;
$scope.pluginLimits = [];
$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.help_screen = false;
$scope.show = {
'diskio' : true,
'network' : true,
'fs' : true,
'sensors' : true,
'sidebar' : true,
'alert' : true,
'short_process_name': true,
'per_cpu': false,
'warning_alerts':true,
'warning_critical_alerts':true,
'process_stats':true,
'top_extended_stats':true,
'docker_stats':true,
'network_io_combination':false,
'network_io_cumulative':false,
'filesystem_freespace':false,
'network_by_bytes':true
};
$scope.init_refresh_time = function() {
if ($routeParams != undefined && $routeParams.refresh_time != undefined) {
var new_refresh_time = parseInt($routeParams.refresh_time)
if (new_refresh_time >= 1) {
$scope.refreshTime = new_refresh_time
}
}
}
$scope.init_limits = function() {
$http.get('/api/2/all/limits').success(function(response, status, headers, config) {
$scope.pluginLimits = response
}).error(function(response, status, headers, config) {
console.log('error : ' + response+ status + headers + config);
});
}
$scope.init_help = function() {
$http.get('/api/2/help').success(function(response, status, headers, config) {
$scope.help = response
});
}
$scope.show_hide = function(bloc) {
if(bloc == 'help') {
$scope.help_screen = !$scope.help_screen
} else {
$scope.show[bloc] = !$scope.show[bloc]
}
}
var canceler = undefined;
/**
* Refresh all the data of the view
*/
$scope.refreshData = function() {
canceler = $q.defer();
$http.get('/api/2/all', {timeout: canceler.promise}).success(function(response, status, headers, config) {
function timemillis(array) {
var sum = 0.0
for (var i = 0; i < array.length; i++) {
sum += array[i] * 1000.0;
}
return sum;
}
function timedelta(input) {
var sum = timemillis(input);
var d = new Date(sum);
return {
hours: d.getUTCHours(), // TODO : multiple days ( * (d.getDay() * 24)))
minutes: d.getUTCMinutes(),
seconds: d.getUTCSeconds(),
milliseconds: parseInt("" + d.getUTCMilliseconds() / 10)
};
};
function durationBetweenTwoDates(startDate, endDate) {
var duration = endDate - startDate;
var seconds = parseInt((duration/1000)%60)
, minutes = parseInt((duration/(1000*60))%60)
, hours = parseInt((duration/(1000*60*60))%24);
return _.padLeft(hours,2,'0') + ":" + _.padLeft(minutes,2,'0') + ":" + _.padLeft(seconds,2,'0');
}
for (var i = 0; i < response['processlist'].length; i++) {
var process = response['processlist'][i]
process.memvirt = process.memory_info[1]
process.memres = process.memory_info[0]
process.timeplus = timedelta(process.cpu_times)
process.timemillis = timemillis(process.cpu_times)
process.io_read = '?';
process.io_write = '?';
if (process.io_counters) {
process.io_read = (process.io_counters[0] - process.io_counters[2]) / process.time_since_update;
if (process.io_read != 0) {
process.io_read = $filter('bytes')(process.io_read);
}
process.io_write = (process.io_counters[1] - process.io_counters[3]) / process.time_since_update;
if (process.io_write != 0) {
process.io_write = $filter('bytes')(process.io_write);
}
}
}
for (var i = 0; i < response['alert'].length; i++) {
var alert = response['alert'][i];
alert.begin = alert[0] * 1000;
alert.end = alert[1] * 1000;
alert.ongoing = alert[1] == -1;
if (!alert.ongoing) {
alert.duration = durationBetweenTwoDates(alert.begin, alert.end);
}
}
$scope.is_bsd = response['system'].os_name === 'FreeBSD';
$scope.is_linux = response['system'].os_name === 'Linux';
$scope.is_mac = response['system'].os_name === 'Darwin';
$scope.is_windows = response['system'].os_name === 'Windows';
$scope.result = response;
canceler.resolve()
}).error(function(d, status, headers, config) {
console.log('error status:' + status + " - headers = " + headers);
canceler.resolve()
});
}
$scope.isNice = function(nice) {
if(nice !== undefined && (($scope.is_windows && nice != 32) || (!$scope.is_windows && nice != 0))) {
return true;
}
return false;
}
$scope.getAlert = function(pluginName, limitNamePrefix, current, maximum, log) {
current = current || 0;
maximum = maximum || 100;
log = log || false;
log_str = log ? '_log' : '';
var value = (current * 100) / maximum;
if ($scope.pluginLimits != undefined && $scope.pluginLimits[pluginName] != undefined) {
for (var i = 0; i < $scope.limitSuffix.length; i++) {
var limitName = limitNamePrefix + $scope.limitSuffix[i]
var limit = $scope.pluginLimits[pluginName][limitName]
if (value >= limit) {
var pos = limitName.lastIndexOf("_")
var className = limitName.substring(pos + 1)
return className + log_str;
}
}
}
return "ok" + log_str;
}
$scope.getAlertLog = function(pluginName, limitNamePrefix, current, maximum) {
return $scope.getAlert(pluginName, limitNamePrefix, current, maximum, true);
}
$scope.init_refresh_time();
$scope.init_limits();
$scope.init_help();
var stop;
$scope.configure_refresh = function () {
if (!angular.isDefined(stop)) {
//$scope.refreshData();
stop = $interval(function() {
$scope.refreshData();
}, $scope.refreshTime * 1000); // in milliseconds
}
}
$scope.$watch(
function() { return $scope.refreshTime; },
function(newValue, oldValue) {
$scope.stop_refresh();
$scope.configure_refresh();
}
);
$scope.stop_refresh = function() {
if (angular.isDefined(stop)) {
$interval.cancel(stop);
stop = undefined;
}
};
$scope.$on('$destroy', function() {
// Make sure that the interval is destroyed too
$scope.stop_refresh();
});
$scope.onKeyDown = function($event) {
if ($event.keyCode == keycodes.a) { // a Sort processes automatically
$scope.sorter.column = "cpu_percent";
$scope.sorter.auto = true;
} else if ($event.keyCode == keycodes.c) {//c Sort processes by CPU%
$scope.sorter.column = "cpu_percent";
$scope.sorter.auto = false;
} else if ($event.keyCode == keycodes.m) {//m Sort processes by MEM%
$scope.sorter.column = "memory_percent";
$scope.sorter.auto = false;
} else if ($event.keyCode == keycodes.p) {//p Sort processes by name
$scope.sorter.column = "name";
$scope.sorter.auto = false;
} else if ($event.keyCode == keycodes.i) {//i Sort processes by I/O rate
$scope.sorter.column = ['io_read', 'io_write'];
$scope.sorter.auto = false;
} else if ($event.keyCode == keycodes.t) {//t Sort processes by CPU times
$scope.sorter.column = "timemillis";
$scope.sorter.auto = false;
} else if ($event.keyCode == keycodes.u) {//t Sort processes by user
$scope.sorter.column = "username";
$scope.sorter.auto = false;
} else if ($event.keyCode == keycodes.d) {//d Show/hide disk I/O stats
$scope.show_hide('diskio')
} else if ($event.keyCode == keycodes.f) {//f Show/hide filesystem stats
$scope.show_hide('fs')
} else if ($event.keyCode == keycodes.n) {//n sort_by Show/hide network stats
$scope.show_hide('network')
} else if ($event.keyCode == keycodes.s) {//s Show/hide sensors stats
$scope.show_hide('sensors')
} else if ($event.keyCode == keycodes.TWO && $event.shiftKey) {//2 Show/hide left sidebar
$scope.show_hide('sidebar')
} else if ($event.keyCode == keycodes.z) {//z Enable/disable processes stats
$scope.show_hide('process_stats')
} else if ($event.keyCode == keycodes.e) {//e Enable/disable top extended stats
$scope.show_hide('top_extended_stats')
} else if ($event.keyCode == keycodes.SLASH) {// SLASH Enable/disable short processes name
$scope.show_hide('short_process_name')
} else if ($event.keyCode == keycodes.D && $event.shiftKey) {//D Enable/disable Docker stats
$scope.show_hide('docker_stats')
} else if ($event.keyCode == keycodes.b) {//b Bytes or bits for network I/O
$scope.show_hide('network_by_bytes')
} else if ($event.keyCode == keycodes.l) {//l Show/hide alert logs
$scope.show_hide('alert')
} else if ($event.keyCode == keycodes.w) {//w Delete warning alerts
$scope.show_hide('warning_alerts')
} else if ($event.keyCode == keycodes.x) {//x Delete warning and critical alerts
$scope.show_hide('warning_critical_alerts')
} else if ($event.keyCode == keycodes.ONE && $event.shiftKey) {//1 Global CPU or per-CPU stats
$scope.show_hide('per_cpu')
} else if ($event.keyCode == keycodes.h) {//h Show/hide this help screen
$scope.show_hide('help')
} else if ($event.keyCode == keycodes.T && $event.shiftKey) {//T View network I/O as combination
$scope.show_hide('network_io_combination')
} else if ($event.keyCode == keycodes.u) {//u View cumulative network I/O
$scope.show_hide('network_io_cumulative')
} else if ($event.keyCode == keycodes.F && $event.shiftKey) {//F Show filesystem free space
$scope.show_hide('filesystem_freespace')
} else if ($event.keyCode == keycodes.g) {//g Generate graphs for current history
// not available
} else if ($event.keyCode == keycodes.r) {//r Reset history
// not available
} else if ($event.keyCode == keycodes.q) {//q Quit (Esc and Ctrl-C also work)
// not available
}
}
});

View File

@ -0,0 +1,30 @@
var keycodes = {
'a' : '65',
'c' : '67',
'm' : '77',
'p' : '80',
'i' : '73',
't' : '84',
'u' : '85',
'd' : '68',
'f' : '70',
'n' : '78',
's' : '83',
'TWO': '50',
'z' : '90',
'e' : '69',
'SLASH': '191',
'D' : '68',
'b' : '66',
'l' : '76',
'w' : '87',
'x' : '88',
'ONE': '49',
'h' : '72',
'T' : '84',
'u' : '85',
'F' : '70',
'g' : '71',
'r' : '82',
'q' : '81'
}

View File

@ -0,0 +1,15 @@
/*
AngularJS v1.2.28
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(n,e,A){'use strict';function x(s,g,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,w){function y(){p&&(p.remove(),p=null);k&&(k.$destroy(),k=null);l&&(h.leave(l,function(){p=null}),p=l,l=null)}function v(){var b=s.current&&s.current.locals;if(e.isDefined(b&&b.$template)){var b=a.$new(),d=s.current;l=w(b,function(d){h.enter(d,null,l||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||g()});y()});k=d.scope=b;k.$emit("$viewContentLoaded");k.$eval(u)}else y()}
var k,l,p,t=b.autoscroll,u=b.onload||"";a.$on("$routeChangeSuccess",v);v()}}}function z(e,g,h){return{restrict:"ECA",priority:-400,link:function(a,c){var b=h.current,f=b.locals;c.html(f.$template);var w=e(c.contents());b.controller&&(f.$scope=a,f=g(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));w(a)}}}n=e.module("ngRoute",["ng"]).provider("$route",function(){function s(a,c){return e.extend(new (e.extend(function(){},
{prototype:a})),c)}function g(a,e){var b=e.caseInsensitiveMatch,f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var h={};this.when=function(a,c){h[a]=e.extend({reloadOnSearch:!0},c,a&&g(a,c));if(a){var b=
"/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";h[b]=e.extend({redirectTo:a},g(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,g,n,v,k){function l(){var d=p(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!u)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)u=!1,a.$broadcast("$routeChangeStart",
d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)?c.path(t(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?g.get(d):g.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=k.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=
b,c=n.get(b,{cache:v}).then(function(a){return a.data})));e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function p(){var a,b;e.forEach(h,function(f,h){var q;if(q=!b){var g=c.path();q=f.keys;var l={};if(f.regexp)if(g=f.regexp.exec(g)){for(var k=1,p=g.length;k<p;++k){var n=q[k-1],r=g[k];n&&r&&(l[n.name]=r)}q=l}else q=null;
else q=null;q=a=q}q&&(b=s(f,{params:e.extend({},c.search(),a),pathParams:a}),b.$$route=f)});return b||h[null]&&s(h[null],{params:{},pathParams:{}})}function t(a,c){var b=[];e.forEach((a||"").split(":"),function(a,d){if(0===d)b.push(a);else{var e=a.match(/(\w+)(?:[?*])?(.*)/),f=e[1];b.push(c[f]);b.push(e[2]||"");delete c[f]}});return b.join("")}var u=!1,r={routes:h,reload:function(){u=!0;a.$evalAsync(l)}};a.$on("$locationChangeSuccess",l);return r}]});n.provider("$routeParams",function(){this.$get=
function(){return{}}});n.directive("ngView",x);n.directive("ngView",z);x.$inject=["$route","$anchorScroll","$animate"];z.$inject=["$compile","$controller","$route"]})(window,window.angular);
//# sourceMappingURL=angular-route.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,218 @@
/*
AngularJS v1.2.28
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(W,X,u){'use strict';function z(b){return function(){var a=arguments[0],c,a="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.2.28/"+(b?b+"/":"")+a;for(c=1;c<arguments.length;c++)a=a+(1==c?"?":"&")+"p"+(c-1)+"="+encodeURIComponent("function"==typeof arguments[c]?arguments[c].toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof arguments[c]?"undefined":"string"!=typeof arguments[c]?JSON.stringify(arguments[c]):arguments[c]);return Error(a)}}function Sa(b){if(null==b||Ja(b))return!1;
var a=b.length;return 1===b.nodeType&&a?!0:G(b)||L(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function r(b,a,c){var d;if(b)if(N(b))for(d in b)"prototype"==d||("length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d))||a.call(c,b[d],d);else if(L(b)||Sa(b))for(d=0;d<b.length;d++)a.call(c,b[d],d);else if(b.forEach&&b.forEach!==r)b.forEach(a,c);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function Xb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function Sc(b,
a,c){for(var d=Xb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function Yb(b){return function(a,c){b(c,a)}}function ib(){for(var b=na.length,a;b;){b--;a=na[b].charCodeAt(0);if(57==a)return na[b]="A",na.join("");if(90==a)na[b]="0";else return na[b]=String.fromCharCode(a+1),na.join("")}na.unshift("0");return na.join("")}function Zb(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function E(b){var a=b.$$hashKey;r(arguments,function(a){a!==b&&r(a,function(a,c){b[c]=a})});Zb(b,a);return b}function U(b){return parseInt(b,
10)}function $b(b,a){return E(new (E(function(){},{prototype:b})),a)}function v(){}function ga(b){return b}function aa(b){return function(){return b}}function F(b){return"undefined"===typeof b}function D(b){return"undefined"!==typeof b}function T(b){return null!=b&&"object"===typeof b}function G(b){return"string"===typeof b}function jb(b){return"number"===typeof b}function va(b){return"[object Date]"===Ba.call(b)}function N(b){return"function"===typeof b}function kb(b){return"[object RegExp]"===Ba.call(b)}
function Ja(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function Tc(b){return!(!b||!(b.nodeName||b.prop&&b.attr&&b.find))}function Uc(b,a,c){var d=[];r(b,function(b,f,g){d.push(a.call(c,b,f,g))});return d}function Ta(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function Ua(b,a){var c=Ta(b,a);0<=c&&b.splice(c,1);return a}function Ka(b,a,c,d){if(Ja(b)||b&&b.$evalAsync&&b.$watch)throw Va("cpws");if(a){if(b===a)throw Va("cpi");c=c||[];
d=d||[];if(T(b)){var e=Ta(c,b);if(-1!==e)return d[e];c.push(b);d.push(a)}if(L(b))for(var f=a.length=0;f<b.length;f++)e=Ka(b[f],null,c,d),T(b[f])&&(c.push(b[f]),d.push(e)),a.push(e);else{var g=a.$$hashKey;L(a)?a.length=0:r(a,function(b,c){delete a[c]});for(f in b)e=Ka(b[f],null,c,d),T(b[f])&&(c.push(b[f]),d.push(e)),a[f]=e;Zb(a,g)}}else if(a=b)L(b)?a=Ka(b,[],c,d):va(b)?a=new Date(b.getTime()):kb(b)?(a=RegExp(b.source,b.toString().match(/[^\/]*$/)[0]),a.lastIndex=b.lastIndex):T(b)&&(a=Ka(b,{},c,d));
return a}function ha(b,a){if(L(b)){a=a||[];for(var c=0;c<b.length;c++)a[c]=b[c]}else if(T(b))for(c in a=a||{},b)!lb.call(b,c)||"$"===c.charAt(0)&&"$"===c.charAt(1)||(a[c]=b[c]);return a||b}function Ca(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&"object"==c)if(L(b)){if(!L(a))return!1;if((c=b.length)==a.length){for(d=0;d<c;d++)if(!Ca(b[d],a[d]))return!1;return!0}}else{if(va(b))return va(a)?isNaN(b.getTime())&&isNaN(a.getTime())||b.getTime()===
a.getTime():!1;if(kb(b)&&kb(a))return b.toString()==a.toString();if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||Ja(b)||Ja(a)||L(a))return!1;c={};for(d in b)if("$"!==d.charAt(0)&&!N(b[d])){if(!Ca(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c.hasOwnProperty(d)&&"$"!==d.charAt(0)&&a[d]!==u&&!N(a[d]))return!1;return!0}return!1}function Bb(b,a){var c=2<arguments.length?wa.call(arguments,2):[];return!N(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,c.concat(wa.call(arguments,
0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function Vc(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)?c=u:Ja(a)?c="$WINDOW":a&&X===a?c="$DOCUMENT":a&&(a.$evalAsync&&a.$watch)&&(c="$SCOPE");return c}function oa(b,a){return"undefined"===typeof b?u:JSON.stringify(b,Vc,a?" ":null)}function ac(b){return G(b)?JSON.parse(b):b}function Wa(b){"function"===typeof b?b=!0:b&&0!==b.length?(b=x(""+b),b=!("f"==b||"0"==b||"false"==b||"no"==b||"n"==b||"[]"==b)):b=!1;
return b}function ia(b){b=A(b).clone();try{b.empty()}catch(a){}var c=A("<div>").append(b).html();try{return 3===b[0].nodeType?x(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+x(b)})}catch(d){return x(c)}}function bc(b){try{return decodeURIComponent(b)}catch(a){}}function cc(b){var a={},c,d;r((b||"").split("&"),function(b){b&&(c=b.replace(/\+/g,"%20").split("="),d=bc(c[0]),D(d)&&(b=D(c[1])?bc(c[1]):!0,lb.call(a,d)?L(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Cb(b){var a=
[];r(b,function(b,d){L(b)?r(b,function(b){a.push(Da(d,!0)+(!0===b?"":"="+Da(b,!0)))}):a.push(Da(d,!0)+(!0===b?"":"="+Da(b,!0)))});return a.length?a.join("&"):""}function mb(b){return Da(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Da(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function Wc(b,a){function c(a){a&&d.push(a)}var d=[b],e,f,g=["ng:app","ng-app","x-ng-app",
"data-ng-app"],h=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;r(g,function(a){g[a]=!0;c(X.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(r(b.querySelectorAll("."+a),c),r(b.querySelectorAll("."+a+"\\:"),c),r(b.querySelectorAll("["+a+"]"),c))});r(d,function(a){if(!e){var b=h.exec(" "+a.className+" ");b?(e=a,f=(b[2]||"").replace(/\s+/g,",")):r(a.attributes,function(b){!e&&g[b.name]&&(e=a,f=b.value)})}});e&&a(e,f?[f]:[])}function dc(b,a){var c=function(){b=A(b);if(b.injector()){var c=b[0]===X?
"document":ia(b);throw Va("btstrpd",c.replace(/</,"&lt;").replace(/>/,"&gt;"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");c=ec(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animate",function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(W&&!d.test(W.name))return c();W.name=W.name.replace(d,"");Xa.resumeBootstrap=function(b){r(b,function(b){a.push(b)});c()}}function nb(b,a){a=
a||"_";return b.replace(Xc,function(b,d){return(d?a:"")+b.toLowerCase()})}function Db(b,a,c){if(!b)throw Va("areq",a||"?",c||"required");return b}function Ya(b,a,c){c&&L(b)&&(b=b[b.length-1]);Db(N(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ea(b,a){if("hasOwnProperty"===b)throw Va("badname",a);}function fc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g<f;g++)d=a[g],b&&(b=(e=b)[d]);return!c&&N(b)?Bb(e,b):b}function Eb(b){var a=
b[0];b=b[b.length-1];if(a===b)return A(a);var c=[a];do{a=a.nextSibling;if(!a)break;c.push(a)}while(a!==b);return A(c)}function Yc(b){var a=z("$injector"),c=z("ng");b=b.angular||(b.angular={});b.$$minErr=b.$$minErr||z;return b.module||(b.module=function(){var b={};return function(e,f,g){if("hasOwnProperty"===e)throw c("badname","module");f&&b.hasOwnProperty(e)&&(b[e]=null);return b[e]||(b[e]=function(){function b(a,d,e){return function(){c[e||"push"]([a,d,arguments]);return n}}if(!f)throw a("nomod",
e);var c=[],d=[],l=b("$injector","invoke"),n={_invokeQueue:c,_runBlocks:d,requires:f,name:e,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:b("$provide","value"),constant:b("$provide","constant","unshift"),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),config:l,run:function(a){d.push(a);return this}};g&&l(g);return n}())}}())}
function Zc(b){E(b,{bootstrap:dc,copy:Ka,extend:E,equals:Ca,element:A,forEach:r,injector:ec,noop:v,bind:Bb,toJson:oa,fromJson:ac,identity:ga,isUndefined:F,isDefined:D,isString:G,isFunction:N,isObject:T,isNumber:jb,isElement:Tc,isArray:L,version:$c,isDate:va,lowercase:x,uppercase:La,callbacks:{counter:0},$$minErr:z,$$csp:Za});$a=Yc(W);try{$a("ngLocale")}catch(a){$a("ngLocale",[]).provider("$locale",ad)}$a("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:bd});a.provider("$compile",
gc).directive({a:cd,input:hc,textarea:hc,form:dd,script:ed,select:fd,style:gd,option:hd,ngBind:id,ngBindHtml:jd,ngBindTemplate:kd,ngClass:ld,ngClassEven:md,ngClassOdd:nd,ngCloak:od,ngController:pd,ngForm:qd,ngHide:rd,ngIf:sd,ngInclude:td,ngInit:ud,ngNonBindable:vd,ngPluralize:wd,ngRepeat:xd,ngShow:yd,ngStyle:zd,ngSwitch:Ad,ngSwitchWhen:Bd,ngSwitchDefault:Cd,ngOptions:Dd,ngTransclude:Ed,ngModel:Fd,ngList:Gd,ngChange:Hd,required:ic,ngRequired:ic,ngValue:Id}).directive({ngInclude:Jd}).directive(Fb).directive(jc);
a.provider({$anchorScroll:Kd,$animate:Ld,$browser:Md,$cacheFactory:Nd,$controller:Od,$document:Pd,$exceptionHandler:Qd,$filter:kc,$interpolate:Rd,$interval:Sd,$http:Td,$httpBackend:Ud,$location:Vd,$log:Wd,$parse:Xd,$rootScope:Yd,$q:Zd,$sce:$d,$sceDelegate:ae,$sniffer:be,$templateCache:ce,$timeout:de,$window:ee,$$rAF:fe,$$asyncCallback:ge})}])}function ab(b){return b.replace(he,function(a,b,d,e){return e?d.toUpperCase():d}).replace(ie,"Moz$1")}function Gb(b,a,c,d){function e(b){var e=c&&b?[this.filter(b)]:
[this],k=a,m,l,n,q,p,s;if(!d||null!=b)for(;e.length;)for(m=e.shift(),l=0,n=m.length;l<n;l++)for(q=A(m[l]),k?q.triggerHandler("$destroy"):k=!k,p=0,q=(s=q.children()).length;p<q;p++)e.push(Fa(s[p]));return f.apply(this,arguments)}var f=Fa.fn[b],f=f.$original||f;e.$original=f;Fa.fn[b]=e}function S(b){if(b instanceof S)return b;G(b)&&(b=$(b));if(!(this instanceof S)){if(G(b)&&"<"!=b.charAt(0))throw Hb("nosel");return new S(b)}if(G(b)){var a=b;b=X;var c;if(c=je.exec(a))b=[b.createElement(c[1])];else{var d=
b,e;b=d.createDocumentFragment();c=[];if(Ib.test(a)){d=b.appendChild(d.createElement("div"));e=(ke.exec(a)||["",""])[1].toLowerCase();e=da[e]||da._default;d.innerHTML="<div>&#160;</div>"+e[1]+a.replace(le,"<$1></$2>")+e[2];d.removeChild(d.firstChild);for(a=e[0];a--;)d=d.lastChild;a=0;for(e=d.childNodes.length;a<e;++a)c.push(d.childNodes[a]);d=b.firstChild;d.textContent=""}else c.push(d.createTextNode(a));b.textContent="";b.innerHTML="";b=c}Jb(this,b);A(X.createDocumentFragment()).append(this)}else Jb(this,
b)}function Kb(b){return b.cloneNode(!0)}function Ma(b){Lb(b);var a=0;for(b=b.childNodes||[];a<b.length;a++)Ma(b[a])}function lc(b,a,c,d){if(D(d))throw Hb("offargs");var e=pa(b,"events");pa(b,"handle")&&(F(a)?r(e,function(a,c){bb(b,c,a);delete e[c]}):r(a.split(" "),function(a){F(c)?(bb(b,a,e[a]),delete e[a]):Ua(e[a]||[],c)}))}function Lb(b,a){var c=b.ng339,d=cb[c];d&&(a?delete cb[c].data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),lc(b)),delete cb[c],b.ng339=u))}function pa(b,a,c){var d=
b.ng339,d=cb[d||-1];if(D(c))d||(b.ng339=d=++me,d=cb[d]={}),d[a]=c;else return d&&d[a]}function Mb(b,a,c){var d=pa(b,"data"),e=D(c),f=!e&&D(a),g=f&&!T(a);d||g||pa(b,"data",d={});if(e)d[a]=c;else if(f){if(g)return d&&d[a];E(d,a)}else return d}function Nb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function ob(b,a){a&&b.setAttribute&&r(a.split(" "),function(a){b.setAttribute("class",$((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g,
" ").replace(" "+$(a)+" "," ")))})}function pb(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");r(a.split(" "),function(a){a=$(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});b.setAttribute("class",$(c))}}function Jb(b,a){if(a){a=a.nodeName||!D(a.length)||Ja(a)?[a]:a;for(var c=0;c<a.length;c++)b.push(a[c])}}function mc(b,a){return qb(b,"$"+(a||"ngController")+"Controller")}function qb(b,a,c){9==b.nodeType&&(b=b.documentElement);for(a=L(a)?a:[a];b;){for(var d=
0,e=a.length;d<e;d++)if((c=A.data(b,a[d]))!==u)return c;b=b.parentNode||11===b.nodeType&&b.host}}function nc(b){for(var a=0,c=b.childNodes;a<c.length;a++)Ma(c[a]);for(;b.firstChild;)b.removeChild(b.firstChild)}function oc(b,a){var c=rb[a.toLowerCase()];return c&&pc[b.nodeName]&&c}function ne(b,a){var c=function(c,e){c.preventDefault||(c.preventDefault=function(){c.returnValue=!1});c.stopPropagation||(c.stopPropagation=function(){c.cancelBubble=!0});c.target||(c.target=c.srcElement||X);if(F(c.defaultPrevented)){var f=
c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;f.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented||!1===c.returnValue};var g=ha(a[e||c.type]||[]);r(g,function(a){a.call(b,c)});8>=R?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function Na(b,a){var c=typeof b,d;"function"==c||"object"==c&&null!==b?"function"==typeof(d=
b.$$hashKey)?d=b.$$hashKey():d===u&&(d=b.$$hashKey=(a||ib)()):d=b;return c+":"+d}function db(b,a){if(a){var c=0;this.nextUid=function(){return++c}}r(b,this.put,this)}function qc(b){var a,c;"function"===typeof b?(a=b.$inject)||(a=[],b.length&&(c=b.toString().replace(oe,""),c=c.match(pe),r(c[1].split(qe),function(b){b.replace(re,function(b,c,d){a.push(d)})})),b.$inject=a):L(b)?(c=b.length-1,Ya(b[c],"fn"),a=b.slice(0,c)):Ya(b,"fn",!0);return a}function ec(b){function a(a){return function(b,c){if(T(b))r(b,
Yb(a));else return a(b,c)}}function c(a,b){Ea(a,"service");if(N(b)||L(b))b=n.instantiate(b);if(!b.$get)throw eb("pget",a);return l[a+h]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[],c,d,f,h;r(a,function(a){if(!m.get(a)){m.put(a,!0);try{if(G(a))for(c=$a(a),b=b.concat(e(c.requires)).concat(c._runBlocks),d=c._invokeQueue,f=0,h=d.length;f<h;f++){var g=d[f],k=n.get(g[0]);k[g[1]].apply(k,g[2])}else N(a)?b.push(n.invoke(a)):L(a)?b.push(n.invoke(a)):Ya(a,"module")}catch(p){throw L(a)&&(a=
a[a.length-1]),p.message&&(p.stack&&-1==p.stack.indexOf(p.message))&&(p=p.message+"\n"+p.stack),eb("modulerr",a,p.stack||p.message||p);}}});return b}function f(a,b){function c(d){if(a.hasOwnProperty(d)){if(a[d]===g)throw eb("cdep",d+" <- "+k.join(" <- "));return a[d]}try{return k.unshift(d),a[d]=g,a[d]=b(d)}catch(e){throw a[d]===g&&delete a[d],e;}finally{k.shift()}}function d(a,b,e){var f=[],h=qc(a),g,k,p;k=0;for(g=h.length;k<g;k++){p=h[k];if("string"!==typeof p)throw eb("itkn",p);f.push(e&&e.hasOwnProperty(p)?
e[p]:c(p))}L(a)&&(a=a[g]);return a.apply(b,f)}return{invoke:d,instantiate:function(a,b){var c=function(){},e;c.prototype=(L(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return T(e)||N(e)?e:c},get:c,annotate:qc,has:function(b){return l.hasOwnProperty(b+h)||a.hasOwnProperty(b)}}}var g={},h="Provider",k=[],m=new db([],!0),l={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,["$injector",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,aa(b))}),constant:a(function(a,
b){Ea(a,"constant");l[a]=b;q[a]=b}),decorator:function(a,b){var c=n.get(a+h),d=c.$get;c.$get=function(){var a=p.invoke(d,c);return p.invoke(b,null,{$delegate:a})}}}},n=l.$injector=f(l,function(){throw eb("unpr",k.join(" <- "));}),q={},p=q.$injector=f(q,function(a){a=n.get(a+h);return p.invoke(a.$get,a)});r(e(b),function(a){p.invoke(a||v)});return p}function Kd(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;
r(a,function(a){b||"a"!==x(a.nodeName)||(b=a)});return b}function f(){var b=c.hash(),d;b?(d=g.getElementById(b))?d.scrollIntoView():(d=e(g.getElementsByName(b)))?d.scrollIntoView():"top"===b&&a.scrollTo(0,0):a.scrollTo(0,0)}var g=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(f)});return f}]}function ge(){this.$get=["$$rAF","$timeout",function(b,a){return b.supported?function(a){return b(a)}:function(b){return a(b,0,!1)}}]}function se(b,a,c,d){function e(a){try{a.apply(null,
wa.call(arguments,1))}finally{if(s--,0===s)for(;J.length;)try{J.pop()()}catch(b){c.error(b)}}}function f(a,b){(function ea(){r(w,function(a){a()});t=b(ea,a)})()}function g(){y!=h.url()&&(y=h.url(),r(ba,function(a){a(h.url())}))}var h=this,k=a[0],m=b.location,l=b.history,n=b.setTimeout,q=b.clearTimeout,p={};h.isMock=!1;var s=0,J=[];h.$$completeOutstandingRequest=e;h.$$incOutstandingRequestCount=function(){s++};h.notifyWhenNoOutstandingRequests=function(a){r(w,function(a){a()});0===s?a():J.push(a)};
var w=[],t;h.addPollFn=function(a){F(t)&&f(100,n);w.push(a);return a};var y=m.href,K=a.find("base"),B=null;h.url=function(a,c){m!==b.location&&(m=b.location);l!==b.history&&(l=b.history);if(a){if(y!=a){var e=y&&Ga(y)===Ga(a);y=a;!e&&d.history?c?l.replaceState(null,"",a):(l.pushState(null,"",a),K.attr("href",K.attr("href"))):(e||(B=a),c?m.replace(a):m.href=a);return h}}else return B||m.href.replace(/%27/g,"'")};var ba=[],O=!1;h.onUrlChange=function(a){if(!O){if(d.history)A(b).on("popstate",g);if(d.hashchange)A(b).on("hashchange",
g);else h.addPollFn(g);O=!0}ba.push(a);return a};h.$$checkUrlChange=g;h.baseHref=function(){var a=K.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};var M={},ca="",P=h.baseHref();h.cookies=function(a,b){var d,e,f,h;if(a)b===u?k.cookie=escape(a)+"=;path="+P+";expires=Thu, 01 Jan 1970 00:00:00 GMT":G(b)&&(d=(k.cookie=escape(a)+"="+escape(b)+";path="+P).length+1,4096<d&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"));else{if(k.cookie!==
ca)for(ca=k.cookie,d=ca.split("; "),M={},f=0;f<d.length;f++)e=d[f],h=e.indexOf("="),0<h&&(a=unescape(e.substring(0,h)),M[a]===u&&(M[a]=unescape(e.substring(h+1))));return M}};h.defer=function(a,b){var c;s++;c=n(function(){delete p[c];e(a)},b||0);p[c]=!0;return c};h.defer.cancel=function(a){return p[a]?(delete p[a],q(a),e(v),!0):!1}}function Md(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new se(b,d,a,c)}]}function Nd(){this.$get=function(){function b(b,d){function e(a){a!=
n&&(q?q==a&&(q=a.n):q=a,f(a.n,a.p),f(a,n),n=a,n.n=null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(b in a)throw z("$cacheFactory")("iid",b);var g=0,h=E({},d,{id:b}),k={},m=d&&d.capacity||Number.MAX_VALUE,l={},n=null,q=null;return a[b]={put:function(a,b){if(m<Number.MAX_VALUE){var c=l[a]||(l[a]={key:a});e(c)}if(!F(b))return a in k||g++,k[a]=b,g>m&&this.remove(q.key),b},get:function(a){if(m<Number.MAX_VALUE){var b=l[a];if(!b)return;e(b)}return k[a]},remove:function(a){if(m<Number.MAX_VALUE){var b=
l[a];if(!b)return;b==n&&(n=b.p);b==q&&(q=b.n);f(b.n,b.p);delete l[a]}delete k[a];g--},removeAll:function(){k={};g=0;l={};n=q=null},destroy:function(){l=h=k=null;delete a[b]},info:function(){return E({},h,{size:g})}}}var a={};b.info=function(){var b={};r(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function ce(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function gc(b,a){var c={},d="Directive",e=/^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,f=/(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
g=/^(on[a-z]+|formaction)$/;this.directive=function k(a,e){Ea(a,"directive");G(a)?(Db(e,"directiveFactory"),c.hasOwnProperty(a)||(c[a]=[],b.factory(a+d,["$injector","$exceptionHandler",function(b,d){var e=[];r(c[a],function(c,f){try{var g=b.invoke(c);N(g)?g={compile:aa(g)}:!g.compile&&g.link&&(g.compile=aa(g.link));g.priority=g.priority||0;g.index=f;g.name=g.name||a;g.require=g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(k){d(k)}});return e}])),c[a].push(e)):r(a,Yb(k));
return this};this.aHrefSanitizationWhitelist=function(b){return D(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return D(b)?(a.imgSrcSanitizationWhitelist(b),this):a.imgSrcSanitizationWhitelist()};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document","$sce","$animate","$$sanitizeUri",function(a,b,l,n,q,p,s,J,w,t,y,K){function B(a,b,c,d,e){a instanceof
A||(a=A(a));r(a,function(b,c){3==b.nodeType&&b.nodeValue.match(/\S+/)&&(a[c]=A(b).wrap("<span></span>").parent()[0])});var f=O(a,b,a,c,d,e);ba(a,"ng-scope");return function(b,c,d,e){Db(b,"scope");var g=c?Oa.clone.call(a):a;r(d,function(a,b){g.data("$"+b+"Controller",a)});d=0;for(var k=g.length;d<k;d++){var p=g[d].nodeType;1!==p&&9!==p||g.eq(d).data("$scope",b)}c&&c(g,b);f&&f(b,g,g,e);return g}}function ba(a,b){try{a.addClass(b)}catch(c){}}function O(a,b,c,d,e,f){function g(a,c,d,e){var f,p,l,m,q,
n,w;f=c.length;var s=Array(f);for(m=0;m<f;m++)s[m]=c[m];n=m=0;for(q=k.length;m<q;n++)p=s[n],c=k[m++],f=k[m++],c?(c.scope?(l=a.$new(),A.data(p,"$scope",l)):l=a,w=c.transcludeOnThisElement?M(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?M(a,b):null,c(f,l,p,d,w)):f&&f(a,p.childNodes,u,e)}for(var k=[],p,l,m,q,n=0;n<a.length;n++)p=new Ob,l=ca(a[n],[],p,0===n?d:u,e),(f=l.length?I(l,a[n],p,b,c,null,[],[],f):null)&&f.scope&&ba(p.$$element,"ng-scope"),p=f&&f.terminal||!(m=a[n].childNodes)||!m.length?
null:O(m,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b),k.push(f,p),q=q||f||p,f=null;return q?g:null}function M(a,b,c){return function(d,e,f){var g=!1;d||(d=a.$new(),g=d.$$transcluded=!0);e=b(d,e,f,c);if(g)e.on("$destroy",function(){d.$destroy()});return e}}function ca(a,b,c,d,g){var k=c.$attr,p;switch(a.nodeType){case 1:ea(b,qa(Pa(a).toLowerCase()),"E",d,g);for(var l,m,q,n=a.attributes,w=0,s=n&&n.length;w<s;w++){var t=!1,J=!1;l=n[w];if(!R||8<=R||l.specified){p=l.name;m=
$(l.value);l=qa(p);if(q=U.test(l))p=nb(l.substr(6),"-");var y=l.replace(/(Start|End)$/,"");l===y+"Start"&&(t=p,J=p.substr(0,p.length-5)+"end",p=p.substr(0,p.length-6));l=qa(p.toLowerCase());k[l]=p;if(q||!c.hasOwnProperty(l))c[l]=m,oc(a,l)&&(c[l]=!0);S(a,b,m,l);ea(b,l,"A",d,g,t,J)}}a=a.className;if(G(a)&&""!==a)for(;p=f.exec(a);)l=qa(p[2]),ea(b,l,"C",d,g)&&(c[l]=$(p[3])),a=a.substr(p.index+p[0].length);break;case 3:x(b,a.nodeValue);break;case 8:try{if(p=e.exec(a.nodeValue))l=qa(p[1]),ea(b,l,"M",d,
g)&&(c[l]=$(p[2]))}catch(B){}}b.sort(F);return b}function P(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ja("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return A(d)}function C(a,b,c){return function(d,e,f,g,k){e=P(e[0],b,c);return a(d,e,f,g,k)}}function I(a,c,d,e,f,g,k,q,n){function w(a,b,c,d){if(a){c&&(a=C(a,c,d));a.require=H.require;a.directiveName=z;if(K===H||H.$$isolateScope)a=rc(a,
{isolateScope:!0});k.push(a)}if(b){c&&(b=C(b,c,d));b.require=H.require;b.directiveName=z;if(K===H||H.$$isolateScope)b=rc(b,{isolateScope:!0});q.push(b)}}function t(a,b,c,d){var e,f="data",g=!1;if(G(b)){for(;"^"==(e=b.charAt(0))||"?"==e;)b=b.substr(1),"^"==e&&(f="inheritedData"),g=g||"?"==e;e=null;d&&"data"===f&&(e=d[b]);e=e||c[f]("$"+b+"Controller");if(!e&&!g)throw ja("ctreq",b,a);}else L(b)&&(e=[],r(b,function(b){e.push(t(a,b,c,d))}));return e}function J(a,e,f,g,n){function w(a,b){var c;2>arguments.length&&
(b=a,a=u);Ia&&(c=ca);return n(a,b,c)}var y,Q,B,M,C,P,ca={},ra;y=c===f?d:ha(d,new Ob(A(f),d.$attr));Q=y.$$element;if(K){var ue=/^\s*([@=&])(\??)\s*(\w*)\s*$/;P=e.$new(!0);!I||I!==K&&I!==K.$$originalDirective?Q.data("$isolateScopeNoTemplate",P):Q.data("$isolateScope",P);ba(Q,"ng-isolate-scope");r(K.scope,function(a,c){var d=a.match(ue)||[],f=d[3]||c,g="?"==d[2],d=d[1],k,l,n,q;P.$$isolateBindings[c]=d+f;switch(d){case "@":y.$observe(f,function(a){P[c]=a});y.$$observers[f].$$scope=e;y[f]&&(P[c]=b(y[f])(e));
break;case "=":if(g&&!y[f])break;l=p(y[f]);q=l.literal?Ca:function(a,b){return a===b||a!==a&&b!==b};n=l.assign||function(){k=P[c]=l(e);throw ja("nonassign",y[f],K.name);};k=P[c]=l(e);P.$watch(function(){var a=l(e);q(a,P[c])||(q(a,k)?n(e,a=P[c]):P[c]=a);return k=a},null,l.literal);break;case "&":l=p(y[f]);P[c]=function(a){return l(e,a)};break;default:throw ja("iscp",K.name,c,a);}})}ra=n&&w;O&&r(O,function(a){var b={$scope:a===K||a.$$isolateScope?P:e,$element:Q,$attrs:y,$transclude:ra},c;C=a.controller;
"@"==C&&(C=y[a.name]);c=s(C,b);ca[a.name]=c;Ia||Q.data("$"+a.name+"Controller",c);a.controllerAs&&(b.$scope[a.controllerAs]=c)});g=0;for(B=k.length;g<B;g++)try{M=k[g],M(M.isolateScope?P:e,Q,y,M.require&&t(M.directiveName,M.require,Q,ca),ra)}catch(H){l(H,ia(Q))}g=e;K&&(K.template||null===K.templateUrl)&&(g=P);a&&a(g,f.childNodes,u,n);for(g=q.length-1;0<=g;g--)try{M=q[g],M(M.isolateScope?P:e,Q,y,M.require&&t(M.directiveName,M.require,Q,ca),ra)}catch(D){l(D,ia(Q))}}n=n||{};for(var y=-Number.MAX_VALUE,
M,O=n.controllerDirectives,K=n.newIsolateScopeDirective,I=n.templateDirective,ea=n.nonTlbTranscludeDirective,F=!1,E=!1,Ia=n.hasElementTranscludeDirective,x=d.$$element=A(c),H,z,V,S=e,R,Ha=0,sa=a.length;Ha<sa;Ha++){H=a[Ha];var U=H.$$start,Y=H.$$end;U&&(x=P(c,U,Y));V=u;if(y>H.priority)break;if(V=H.scope)M=M||H,H.templateUrl||(fb("new/isolated scope",K,H,x),T(V)&&(K=H));z=H.name;!H.templateUrl&&H.controller&&(V=H.controller,O=O||{},fb("'"+z+"' controller",O[z],H,x),O[z]=H);if(V=H.transclude)F=!0,H.$$tlb||
(fb("transclusion",ea,H,x),ea=H),"element"==V?(Ia=!0,y=H.priority,V=x,x=d.$$element=A(X.createComment(" "+z+": "+d[z]+" ")),c=x[0],ra(f,wa.call(V,0),c),S=B(V,e,y,g&&g.name,{nonTlbTranscludeDirective:ea})):(V=A(Kb(c)).contents(),x.empty(),S=B(V,e));if(H.template)if(E=!0,fb("template",I,H,x),I=H,V=N(H.template)?H.template(x,d):H.template,V=W(V),H.replace){g=H;V=Ib.test(V)?A($(V)):[];c=V[0];if(1!=V.length||1!==c.nodeType)throw ja("tplrt",z,"");ra(f,x,c);sa={$attr:{}};V=ca(c,[],sa);var Z=a.splice(Ha+
1,a.length-(Ha+1));K&&D(V);a=a.concat(V).concat(Z);v(d,sa);sa=a.length}else x.html(V);if(H.templateUrl)E=!0,fb("template",I,H,x),I=H,H.replace&&(g=H),J=te(a.splice(Ha,a.length-Ha),x,d,f,F&&S,k,q,{controllerDirectives:O,newIsolateScopeDirective:K,templateDirective:I,nonTlbTranscludeDirective:ea}),sa=a.length;else if(H.compile)try{R=H.compile(x,d,S),N(R)?w(null,R,U,Y):R&&w(R.pre,R.post,U,Y)}catch(ve){l(ve,ia(x))}H.terminal&&(J.terminal=!0,y=Math.max(y,H.priority))}J.scope=M&&!0===M.scope;J.transcludeOnThisElement=
F;J.templateOnThisElement=E;J.transclude=S;n.hasElementTranscludeDirective=Ia;return J}function D(a){for(var b=0,c=a.length;b<c;b++)a[b]=$b(a[b],{$$isolateScope:!0})}function ea(b,e,f,g,p,m,n){if(e===p)return null;p=null;if(c.hasOwnProperty(e)){var q;e=a.get(e+d);for(var w=0,s=e.length;w<s;w++)try{q=e[w],(g===u||g>q.priority)&&-1!=q.restrict.indexOf(f)&&(m&&(q=$b(q,{$$start:m,$$end:n})),b.push(q),p=q)}catch(y){l(y)}}return p}function v(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;r(a,function(d,e){"$"!=
e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});r(b,function(b,f){"class"==f?(ba(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==f?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==f.charAt(0)||a.hasOwnProperty(f)||(a[f]=b,d[f]=c[f])})}function te(a,b,c,d,e,f,g,k){var p=[],l,m,w=b[0],s=a.shift(),y=E({},s,{templateUrl:null,transclude:null,replace:null,$$originalDirective:s}),J=N(s.templateUrl)?s.templateUrl(b,c):s.templateUrl;
b.empty();n.get(t.getTrustedResourceUrl(J),{cache:q}).success(function(q){var n,t;q=W(q);if(s.replace){q=Ib.test(q)?A($(q)):[];n=q[0];if(1!=q.length||1!==n.nodeType)throw ja("tplrt",s.name,J);q={$attr:{}};ra(d,b,n);var B=ca(n,[],q);T(s.scope)&&D(B);a=B.concat(a);v(c,q)}else n=w,b.html(q);a.unshift(y);l=I(a,n,c,e,b,s,f,g,k);r(d,function(a,c){a==n&&(d[c]=b[0])});for(m=O(b[0].childNodes,e);p.length;){q=p.shift();t=p.shift();var K=p.shift(),C=p.shift(),B=b[0];if(t!==w){var P=t.className;k.hasElementTranscludeDirective&&
s.replace||(B=Kb(n));ra(K,A(t),B);ba(A(B),P)}t=l.transcludeOnThisElement?M(q,l.transclude,C):C;l(m,q,B,d,t)}p=null}).error(function(a,b,c,d){throw ja("tpload",d.url);});return function(a,b,c,d,e){a=e;p?(p.push(b),p.push(c),p.push(d),p.push(a)):(l.transcludeOnThisElement&&(a=M(b,l.transclude,e)),l(m,b,c,d,a))}}function F(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function fb(a,b,c,d){if(b)throw ja("multidir",b.name,c.name,a,ia(d));}function x(a,
c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){var b=a.parent().length;b&&ba(a.parent(),"ng-binding");return function(a,c){var e=c.parent(),f=e.data("$binding")||[];f.push(d);e.data("$binding",f);b||ba(e,"ng-binding");a.$watch(d,function(a){c[0].nodeValue=a})}}})}function z(a,b){if("srcdoc"==b)return t.HTML;var c=Pa(a);if("xlinkHref"==b||"FORM"==c&&"action"==b||"IMG"!=c&&("src"==b||"ngSrc"==b))return t.RESOURCE_URL}function S(a,c,d,e){var f=b(d,!0);if(f){if("multiple"===e&&"SELECT"===
Pa(a))throw ja("selmulti",ia(a));c.push({priority:100,compile:function(){return{pre:function(c,d,k){d=k.$$observers||(k.$$observers={});if(g.test(e))throw ja("nodomevents");if(f=b(k[e],!0,z(a,e)))k[e]=f(c),(d[e]||(d[e]=[])).$$inter=!0,(k.$$observers&&k.$$observers[e].$$scope||c).$watch(f,function(a,b){"class"===e&&a!=b?k.$updateClass(a,b):k.$set(e,a)})}}}})}}function ra(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,k;if(a)for(g=0,k=a.length;g<k;g++)if(a[g]==d){a[g++]=c;k=g+e-1;for(var p=a.length;g<
p;g++,k++)k<p?a[g]=a[k]:delete a[g];a.length-=e-1;break}f&&f.replaceChild(c,d);a=X.createDocumentFragment();a.appendChild(d);c[A.expando]=d[A.expando];d=1;for(e=b.length;d<e;d++)f=b[d],A(f).remove(),a.appendChild(f),delete b[d];b[0]=c;b.length=1}function rc(a,b){return E(function(){return a.apply(null,arguments)},a,b)}var Ob=function(a,b){this.$$element=a;this.$attr=b||{}};Ob.prototype={$normalize:qa,$addClass:function(a){a&&0<a.length&&y.addClass(this.$$element,a)},$removeClass:function(a){a&&0<
a.length&&y.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=sc(a,b),d=sc(b,a);0===c.length?y.removeClass(this.$$element,d):0===d.length?y.addClass(this.$$element,c):y.setClass(this.$$element,c,d)},$set:function(a,b,c,d){var e=oc(this.$$element[0],a);e&&(this.$$element.prop(a,b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=nb(a,"-"));e=Pa(this.$$element);if("A"===e&&"href"===a||"IMG"===e&&"src"===a)this[a]=b=K(b,"src"===a);!1!==c&&(null===b||b===u?this.$$element.removeAttr(d):
this.$$element.attr(d,b));(c=this.$$observers)&&r(c[a],function(a){try{a(b)}catch(c){l(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);J.$evalAsync(function(){e.$$inter||b(c[a])});return b}};var sa=b.startSymbol(),Ia=b.endSymbol(),W="{{"==sa||"}}"==Ia?ga:function(a){return a.replace(/\{\{/g,sa).replace(/}}/g,Ia)},U=/^ngAttr[A-Z]/;return B}]}function qa(b){return ab(b.replace(we,""))}function sc(b,a){var c="",d=b.split(/\s+/),e=a.split(/\s+/),
f=0;a:for(;f<d.length;f++){for(var g=d[f],h=0;h<e.length;h++)if(g==e[h])continue a;c+=(0<c.length?" ":"")+g}return c}function Od(){var b={},a=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,d){Ea(a,"controller");T(a)?E(b,a):b[a]=d};this.$get=["$injector","$window",function(c,d){return function(e,f){var g,h,k;G(e)&&(g=e.match(a),h=g[1],k=g[3],e=b.hasOwnProperty(h)?b[h]:fc(f.$scope,h,!0)||fc(d,h,!0),Ya(e,h,!0));g=c.instantiate(e,f);if(k){if(!f||"object"!==typeof f.$scope)throw z("$controller")("noscp",
h||e.name,k);f.$scope[k]=g}return g}}]}function Pd(){this.$get=["$window",function(b){return A(b.document)}]}function Qd(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function tc(b){var a={},c,d,e;if(!b)return a;r(b.split("\n"),function(b){e=b.indexOf(":");c=x($(b.substr(0,e)));d=$(b.substr(e+1));c&&(a[c]=a[c]?a[c]+", "+d:d)});return a}function uc(b){var a=T(b)?b:u;return function(c){a||(a=tc(b));return c?a[x(c)]||null:a}}function vc(b,a,c){if(N(c))return c(b,
a);r(c,function(c){b=c(b,a)});return b}function Td(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults={transformResponse:[function(d){G(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ac(d)));return d}],transformRequest:[function(a){return T(a)&&"[object File]"!==Ba.call(a)&&"[object Blob]"!==Ba.call(a)?oa(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ha(d),put:ha(d),patch:ha(d)},xsrfCookieName:"XSRF-TOKEN",
xsrfHeaderName:"X-XSRF-TOKEN"},f=this.interceptors=[],g=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,n,q){function p(a){function b(a){var d=E({},a,{data:vc(a.data,a.headers,c.transformResponse)});return 200<=a.status&&300>a.status?d:n.reject(d)}var c={method:"get",transformRequest:e.transformRequest,transformResponse:e.transformResponse},d=function(a){var b=e.headers,c=E({},a.headers),d,f,b=E({},b.common,b[x(a.method)]);
a:for(d in b){a=x(d);for(f in c)if(x(f)===a)continue a;c[d]=b[d]}(function(a){var b;r(a,function(c,d){N(c)&&(b=c(),null!=b?a[d]=b:delete a[d])})})(c);return c}(a);E(c,a);c.headers=d;c.method=La(c.method);var f=[function(a){d=a.headers;var c=vc(a.data,uc(d),a.transformRequest);F(c)&&r(d,function(a,b){"content-type"===x(b)&&delete d[b]});F(a.withCredentials)&&!F(e.withCredentials)&&(a.withCredentials=e.withCredentials);return s(a,c,d).then(b,b)},u],g=n.when(c);for(r(t,function(a){(a.request||a.requestError)&&
f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var h=f.shift(),g=g.then(a,h)}g.success=function(a){g.then(function(b){a(b.data,b.status,b.headers,c)});return g};g.error=function(a){g.then(null,function(b){a(b.data,b.status,b.headers,c)});return g};return g}function s(c,f,g){function m(a,b,c,e){C&&(200<=a&&300>a?C.put(A,[a,b,tc(c),e]):C.remove(A));q(b,a,c,e);d.$$phase||d.$apply()}function q(a,b,d,e){b=Math.max(b,0);(200<=
b&&300>b?t.resolve:t.reject)({data:a,status:b,headers:uc(d),config:c,statusText:e})}function s(){var a=Ta(p.pendingRequests,c);-1!==a&&p.pendingRequests.splice(a,1)}var t=n.defer(),r=t.promise,C,I,A=J(c.url,c.params);p.pendingRequests.push(c);r.then(s,s);!c.cache&&!e.cache||(!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method)||(C=T(c.cache)?c.cache:T(e.cache)?e.cache:w);if(C)if(I=C.get(A),D(I)){if(I&&N(I.then))return I.then(s,s),I;L(I)?q(I[1],I[0],ha(I[2]),I[3]):q(I,200,{},"OK")}else C.put(A,r);F(I)&&
((I=Pb(c.url)?b.cookies()[c.xsrfCookieName||e.xsrfCookieName]:u)&&(g[c.xsrfHeaderName||e.xsrfHeaderName]=I),a(c.method,A,f,m,g,c.timeout,c.withCredentials,c.responseType));return r}function J(a,b){if(!b)return a;var c=[];Sc(b,function(a,b){null===a||F(a)||(L(a)||(a=[a]),r(a,function(a){T(a)&&(a=va(a)?a.toISOString():oa(a));c.push(Da(b)+"="+Da(a))}))});0<c.length&&(a+=(-1==a.indexOf("?")?"?":"&")+c.join("&"));return a}var w=c("$http"),t=[];r(f,function(a){t.unshift(G(a)?q.get(a):q.invoke(a))});r(g,
function(a,b){var c=G(a)?q.get(a):q.invoke(a);t.splice(b,0,{response:function(a){return c(n.when(a))},responseError:function(a){return c(n.reject(a))}})});p.pendingRequests=[];(function(a){r(arguments,function(a){p[a]=function(b,c){return p(E(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){r(arguments,function(a){p[a]=function(b,c,d){return p(E(d||{},{method:a,url:b,data:c}))}})})("post","put","patch");p.defaults=e;return p}]}function xe(b){if(8>=R&&(!b.match(/^(get|post|head|put|delete|options)$/i)||
!W.XMLHttpRequest))return new W.ActiveXObject("Microsoft.XMLHTTP");if(W.XMLHttpRequest)return new W.XMLHttpRequest;throw z("$httpBackend")("noxhr");}function Ud(){this.$get=["$browser","$window","$document",function(b,a,c){return ye(b,xe,b.defer,a.angular.callbacks,c[0])}]}function ye(b,a,c,d,e){function f(a,b,c){var f=e.createElement("script"),g=null;f.type="text/javascript";f.src=a;f.async=!0;g=function(a){bb(f,"load",g);bb(f,"error",g);e.body.removeChild(f);f=null;var h=-1,s="unknown";a&&("load"!==
a.type||d[b].called||(a={type:"error"}),s=a.type,h="error"===a.type?404:200);c&&c(h,s)};sb(f,"load",g);sb(f,"error",g);8>=R&&(f.onreadystatechange=function(){G(f.readyState)&&/loaded|complete/.test(f.readyState)&&(f.onreadystatechange=null,g({type:"load"}))});e.body.appendChild(f);return g}var g=-1;return function(e,k,m,l,n,q,p,s){function J(){t=g;K&&K();B&&B.abort()}function w(a,d,e,f,g){O&&c.cancel(O);K=B=null;0===d&&(d=e?200:"file"==xa(k).protocol?404:0);a(1223===d?204:d,e,f,g||"");b.$$completeOutstandingRequest(v)}
var t;b.$$incOutstandingRequestCount();k=k||b.url();if("jsonp"==x(e)){var y="_"+(d.counter++).toString(36);d[y]=function(a){d[y].data=a;d[y].called=!0};var K=f(k.replace("JSON_CALLBACK","angular.callbacks."+y),y,function(a,b){w(l,a,d[y].data,"",b);d[y]=v})}else{var B=a(e);B.open(e,k,!0);r(n,function(a,b){D(a)&&B.setRequestHeader(b,a)});B.onreadystatechange=function(){if(B&&4==B.readyState){var a=null,b=null,c="";t!==g&&(a=B.getAllResponseHeaders(),b="response"in B?B.response:B.responseText);t===g&&
10>R||(c=B.statusText);w(l,t||B.status,b,a,c)}};p&&(B.withCredentials=!0);if(s)try{B.responseType=s}catch(ba){if("json"!==s)throw ba;}B.send(m||null)}if(0<q)var O=c(J,q);else q&&N(q.then)&&q.then(J)}}function Rd(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler","$sce",function(c,d,e){function f(f,m,l){for(var n,q,p=0,s=[],J=f.length,w=!1,t=[];p<J;)-1!=(n=f.indexOf(b,p))&&-1!=(q=f.indexOf(a,
n+g))?(p!=n&&s.push(f.substring(p,n)),s.push(p=c(w=f.substring(n+g,q))),p.exp=w,p=q+h,w=!0):(p!=J&&s.push(f.substring(p)),p=J);(J=s.length)||(s.push(""),J=1);if(l&&1<s.length)throw wc("noconcat",f);if(!m||w)return t.length=J,p=function(a){try{for(var b=0,c=J,g;b<c;b++){if("function"==typeof(g=s[b]))if(g=g(a),g=l?e.getTrusted(l,g):e.valueOf(g),null==g)g="";else switch(typeof g){case "string":break;case "number":g=""+g;break;default:g=oa(g)}t[b]=g}return t.join("")}catch(h){a=wc("interr",f,h.toString()),
d(a)}},p.exp=f,p.parts=s,p}var g=b.length,h=a.length;f.startSymbol=function(){return b};f.endSymbol=function(){return a};return f}]}function Sd(){this.$get=["$rootScope","$window","$q",function(b,a,c){function d(d,g,h,k){var m=a.setInterval,l=a.clearInterval,n=c.defer(),q=n.promise,p=0,s=D(k)&&!k;h=D(h)?h:0;q.then(null,null,d);q.$$intervalId=m(function(){n.notify(p++);0<h&&p>=h&&(n.resolve(p),l(q.$$intervalId),delete e[q.$$intervalId]);s||b.$apply()},g);e[q.$$intervalId]=n;return q}var e={};d.cancel=
function(b){return b&&b.$$intervalId in e?(e[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),delete e[b.$$intervalId],!0):!1};return d}]}function ad(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),
SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function Qb(b){b=b.split("/");for(var a=b.length;a--;)b[a]=
mb(b[a]);return b.join("/")}function xc(b,a,c){b=xa(b,c);a.$$protocol=b.protocol;a.$$host=b.hostname;a.$$port=U(b.port)||ze[b.protocol]||null}function yc(b,a,c){var d="/"!==b.charAt(0);d&&(b="/"+b);b=xa(b,c);a.$$path=decodeURIComponent(d&&"/"===b.pathname.charAt(0)?b.pathname.substring(1):b.pathname);a.$$search=cc(b.search);a.$$hash=decodeURIComponent(b.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function ta(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ga(b){var a=
b.indexOf("#");return-1==a?b:b.substr(0,a)}function Rb(b){return b.substr(0,Ga(b).lastIndexOf("/")+1)}function zc(b,a){this.$$html5=!0;a=a||"";var c=Rb(b);xc(b,this,b);this.$$parse=function(a){var e=ta(c,a);if(!G(e))throw Sb("ipthprfx",a,c);yc(e,this,b);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Cb(this.$$search),b=this.$$hash?"#"+mb(this.$$hash):"";this.$$url=Qb(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$parseLinkUrl=function(d,
e){var f,g;(f=ta(b,d))!==u?(g=f,g=(f=ta(a,f))!==u?c+(ta("/",f)||f):b+g):(f=ta(c,d))!==u?g=c+f:c==d+"/"&&(g=c);g&&this.$$parse(g);return!!g}}function Tb(b,a){var c=Rb(b);xc(b,this,b);this.$$parse=function(d){var e=ta(b,d)||ta(c,d),e="#"==e.charAt(0)?ta(a,e):this.$$html5?e:"";if(!G(e))throw Sb("ihshprfx",d,a);yc(e,this,b);d=this.$$path;var f=/^\/[A-Z]:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));f.exec(e)||(d=(e=f.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Cb(this.$$search),
e=this.$$hash?"#"+mb(this.$$hash):"";this.$$url=Qb(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ga(b)==Ga(a)?(this.$$parse(a),!0):!1}}function Ac(b,a){this.$$html5=!0;Tb.apply(this,arguments);var c=Rb(b);this.$$parseLinkUrl=function(d,e){var f,g;b==Ga(d)?f=d:(g=ta(c,d))?f=b+a+g:c===d+"/"&&(f=c);f&&this.$$parse(f);return!!f};this.$$compose=function(){var c=Cb(this.$$search),e=this.$$hash?"#"+mb(this.$$hash):"";this.$$url=Qb(this.$$path)+
(c?"?"+c:"")+e;this.$$absUrl=b+a+this.$$url}}function tb(b){return function(){return this[b]}}function Bc(b,a){return function(c){if(F(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Vd(){var b="",a=!1;this.hashPrefix=function(a){return D(a)?(b=a,this):b};this.html5Mode=function(b){return D(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,f){function g(a){c.$broadcast("$locationChangeSuccess",h.absUrl(),a)}var h,k=d.baseHref(),m=d.url();
a?(k=m.substring(0,m.indexOf("/",m.indexOf("//")+2))+(k||"/"),e=e.history?zc:Ac):(k=Ga(m),e=Tb);h=new e(k,"#"+b);h.$$parseLinkUrl(m,m);var l=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(!a.ctrlKey&&!a.metaKey&&2!=a.which){for(var b=A(a.target);"a"!==x(b[0].nodeName);)if(b[0]===f[0]||!(b=b.parent())[0])return;var e=b.prop("href"),g=b.attr("href")||b.attr("xlink:href");T(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=xa(e.animVal).href);l.test(e)||(!e||(b.attr("target")||a.isDefaultPrevented())||
!h.$$parseLinkUrl(e,g))||(a.preventDefault(),h.absUrl()!=d.url()&&(c.$apply(),W.angular["ff-684208-preventDefault"]=!0))}});h.absUrl()!=m&&d.url(h.absUrl(),!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$evalAsync(function(){var b=h.absUrl();h.$$parse(a);c.$broadcast("$locationChangeStart",a,b).defaultPrevented?(h.$$parse(b),d.url(b)):g(b)}),c.$$phase||c.$digest())});var n=0;c.$watch(function(){var a=d.url(),b=h.$$replace;n&&a==h.absUrl()||(n++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",
h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),g(a))}));h.$$replace=!1;return n});return h}]}function Wd(){var b=!0,a=this;this.debugEnabled=function(a){return D(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||v;a=!1;try{a=!!e.apply}catch(k){}return a?
function(){var a=[];r(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function ka(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw la("isecfld",a);return b}function ma(b,a){if(b){if(b.constructor===b)throw la("isecfn",a);if(b.document&&
b.location&&b.alert&&b.setInterval)throw la("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw la("isecdom",a);if(b===Object)throw la("isecobj",a);}return b}function ub(b,a,c,d,e){ma(b,d);e=e||{};a=a.split(".");for(var f,g=0;1<a.length;g++){f=ka(a.shift(),d);var h=ma(b[f],d);h||(h={},b[f]=h);b=h;b.then&&e.unwrapPromises&&(ya(d),"$$v"in b||function(a){a.then(function(b){a.$$v=b})}(b),b.$$v===u&&(b.$$v={}),b=b.$$v)}f=ka(a.shift(),d);ma(b[f],d);return b[f]=c}function Qa(b){return"constructor"==
b}function Cc(b,a,c,d,e,f,g){ka(b,f);ka(a,f);ka(c,f);ka(d,f);ka(e,f);var h=function(a){return ma(a,f)},k=g.expensiveChecks,m=k||Qa(b)?h:ga,l=k||Qa(a)?h:ga,n=k||Qa(c)?h:ga,q=k||Qa(d)?h:ga,p=k||Qa(e)?h:ga;return g.unwrapPromises?function(g,h){var k=h&&h.hasOwnProperty(b)?h:g,t;if(null==k)return k;(k=m(k[b]))&&k.then&&(ya(f),"$$v"in k||(t=k,t.$$v=u,t.then(function(a){t.$$v=m(a)})),k=m(k.$$v));if(!a)return k;if(null==k)return u;(k=l(k[a]))&&k.then&&(ya(f),"$$v"in k||(t=k,t.$$v=u,t.then(function(a){t.$$v=
l(a)})),k=l(k.$$v));if(!c)return k;if(null==k)return u;(k=n(k[c]))&&k.then&&(ya(f),"$$v"in k||(t=k,t.$$v=u,t.then(function(a){t.$$v=n(a)})),k=n(k.$$v));if(!d)return k;if(null==k)return u;(k=q(k[d]))&&k.then&&(ya(f),"$$v"in k||(t=k,t.$$v=u,t.then(function(a){t.$$v=q(a)})),k=q(k.$$v));if(!e)return k;if(null==k)return u;(k=p(k[e]))&&k.then&&(ya(f),"$$v"in k||(t=k,t.$$v=u,t.then(function(a){t.$$v=p(a)})),k=p(k.$$v));return k}:function(f,g){var h=g&&g.hasOwnProperty(b)?g:f;if(null==h)return h;h=m(h[b]);
if(!a)return h;if(null==h)return u;h=l(h[a]);if(!c)return h;if(null==h)return u;h=n(h[c]);if(!d)return h;if(null==h)return u;h=q(h[d]);return e?null==h?u:h=p(h[e]):h}}function Ae(b,a){return function(c,d){return b(c,d,ya,ma,a)}}function Dc(b,a,c){var d=a.expensiveChecks,e=d?Be:Ce;if(e.hasOwnProperty(b))return e[b];var f=b.split("."),g=f.length,h;if(a.csp)h=6>g?Cc(f[0],f[1],f[2],f[3],f[4],c,a):function(b,d){var e=0,h;do h=Cc(f[e++],f[e++],f[e++],f[e++],f[e++],c,a)(b,d),d=u,b=h;while(e<g);return h};
else{var k="var p;\n";d&&(k+="s = eso(s, fe);\nl = eso(l, fe);\n");var m=d;r(f,function(b,e){ka(b,c);var f=(e?"s":'((l&&l.hasOwnProperty("'+b+'"))?l:s)')+'["'+b+'"]',g=d||Qa(b);g&&(f="eso("+f+", fe)",m=!0);k+="if(s == null) return undefined;\ns="+f+";\n";a.unwrapPromises&&(k+='if (s && s.then) {\n pw("'+c.replace(/(["\r\n])/g,"\\$1")+'");\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v='+(g?"eso(v)":"v")+";});\n}\n s="+(g?"eso(s.$$v)":"s.$$v")+"\n}\n")});k+="return s;";
h=new Function("s","l","pw","eso","fe",k);h.toString=aa(k);if(m||a.unwrapPromises)h=Ae(h,c)}"hasOwnProperty"!==b&&(e[b]=h);return h}function Xd(){var b={},a={},c={csp:!1,unwrapPromises:!1,logPromiseWarnings:!0,expensiveChecks:!1};this.unwrapPromises=function(a){return D(a)?(c.unwrapPromises=!!a,this):c.unwrapPromises};this.logPromiseWarnings=function(a){return D(a)?(c.logPromiseWarnings=a,this):c.logPromiseWarnings};this.$get=["$filter","$sniffer","$log",function(d,e,f){c.csp=e.csp;var g={csp:c.csp,
unwrapPromises:c.unwrapPromises,logPromiseWarnings:c.logPromiseWarnings,expensiveChecks:!0};ya=function(a){c.logPromiseWarnings&&!Ec.hasOwnProperty(a)&&(Ec[a]=!0,f.warn("[$parse] Promise found in the expression `"+a+"`. Automatic unwrapping of promises in Angular expressions is deprecated."))};return function(e,f){var m;switch(typeof e){case "string":var l=f?a:b;if(l.hasOwnProperty(e))return l[e];m=f?g:c;var n=new Ub(m);m=(new gb(n,d,m)).parse(e);"hasOwnProperty"!==e&&(l[e]=m);return m;case "function":return e;
default:return v}}}]}function Zd(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return De(function(a){b.$evalAsync(a)},a)}]}function De(b,a){function c(a){return a}function d(a){return g(a)}var e=function(){var g=[],m,l;return l={resolve:function(a){if(g){var c=g;g=u;m=f(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],m.then(a[0],a[1],a[2])})}},reject:function(a){l.resolve(h(a))},notify:function(a){if(g){var c=g;g.length&&b(function(){for(var b,d=0,e=c.length;d<e;d++)b=
c[d],b[2](a)})}},promise:{then:function(b,f,h){var l=e(),J=function(d){try{l.resolve((N(b)?b:c)(d))}catch(e){l.reject(e),a(e)}},w=function(b){try{l.resolve((N(f)?f:d)(b))}catch(c){l.reject(c),a(c)}},t=function(b){try{l.notify((N(h)?h:c)(b))}catch(d){a(d)}};g?g.push([J,w,t]):m.then(J,w,t);return l.promise},"catch":function(a){return this.then(null,a)},"finally":function(a){function b(a,c){var d=e();c?d.resolve(a):d.reject(a);return d.promise}function d(e,f){var g=null;try{g=(a||c)()}catch(h){return b(h,
!1)}return g&&N(g.then)?g.then(function(){return b(e,f)},function(a){return b(a,!1)}):b(e,f)}return this.then(function(a){return d(a,!0)},function(a){return d(a,!1)})}}}},f=function(a){return a&&N(a.then)?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},g=function(a){var b=e();b.reject(a);return b.promise},h=function(c){return{then:function(f,g){var h=e();b(function(){try{h.resolve((N(g)?g:d)(c))}catch(b){h.reject(b),a(b)}});return h.promise}}};return{defer:e,reject:g,
when:function(h,m,l,n){var q=e(),p,s=function(b){try{return(N(m)?m:c)(b)}catch(d){return a(d),g(d)}},J=function(b){try{return(N(l)?l:d)(b)}catch(c){return a(c),g(c)}},w=function(b){try{return(N(n)?n:c)(b)}catch(d){a(d)}};b(function(){f(h).then(function(a){p||(p=!0,q.resolve(f(a).then(s,J,w)))},function(a){p||(p=!0,q.resolve(J(a)))},function(a){p||q.notify(w(a))})});return q.promise},all:function(a){var b=e(),c=0,d=L(a)?[]:{};r(a,function(a,e){c++;f(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,
--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise}}}function fe(){this.$get=["$window","$timeout",function(b,a){var c=b.requestAnimationFrame||b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame,d=b.cancelAnimationFrame||b.webkitCancelAnimationFrame||b.mozCancelAnimationFrame||b.webkitCancelRequestAnimationFrame,e=!!c,f=e?function(a){var b=c(a);return function(){d(b)}}:function(b){var c=a(b,16.66,!1);return function(){a.cancel(c)}};f.supported=
e;return f}]}function Yd(){var b=10,a=z("$rootScope"),c=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler","$parse","$browser",function(d,e,f,g){function h(){this.$id=ib();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$postDigestQueue=[];this.$$listeners={};this.$$listenerCount={};this.$$isolateBindings=
{}}function k(b){if(q.$$phase)throw a("inprog",q.$$phase);q.$$phase=b}function m(a,b){var c=f(a);Ya(c,b);return c}function l(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function n(){}h.prototype={constructor:h,$new:function(a){a?(a=new h,a.$root=this.$root,a.$$asyncQueue=this.$$asyncQueue,a.$$postDigestQueue=this.$$postDigestQueue):(this.$$childScopeClass||(this.$$childScopeClass=function(){this.$$watchers=this.$$nextSibling=this.$$childHead=
this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$id=ib();this.$$childScopeClass=null},this.$$childScopeClass.prototype=this),a=new this.$$childScopeClass);a["this"]=a;a.$parent=this;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,d){var e=m(a,"watch"),f=this.$$watchers,g={fn:b,last:n,get:e,exp:a,eq:!!d};c=null;if(!N(b)){var h=m(b||v,"listener");g.fn=function(a,
b,c){h(c)}}if("string"==typeof a&&e.constant){var k=g.fn;g.fn=function(a,b,c){k.call(this,a,b,c);Ua(f,g)}}f||(f=this.$$watchers=[]);f.unshift(g);return function(){Ua(f,g);c=null}},$watchCollection:function(a,b){var c=this,d,e,g,h=1<b.length,k=0,l=f(a),m=[],n={},q=!0,r=0;return this.$watch(function(){d=l(c);var a,b,f;if(T(d))if(Sa(d))for(e!==m&&(e=m,r=e.length=0,k++),a=d.length,r!==a&&(k++,e.length=r=a),b=0;b<a;b++)f=e[b]!==e[b]&&d[b]!==d[b],f||e[b]===d[b]||(k++,e[b]=d[b]);else{e!==n&&(e=n={},r=0,
k++);a=0;for(b in d)d.hasOwnProperty(b)&&(a++,e.hasOwnProperty(b)?(f=e[b]!==e[b]&&d[b]!==d[b],f||e[b]===d[b]||(k++,e[b]=d[b])):(r++,e[b]=d[b],k++));if(r>a)for(b in k++,e)e.hasOwnProperty(b)&&!d.hasOwnProperty(b)&&(r--,delete e[b])}else e!==d&&(e=d,k++);return k},function(){q?(q=!1,b(d,d,c)):b(d,g,c);if(h)if(T(d))if(Sa(d)){g=Array(d.length);for(var a=0;a<d.length;a++)g[a]=d[a]}else for(a in g={},d)lb.call(d,a)&&(g[a]=d[a]);else g=d})},$digest:function(){var d,f,h,l,m=this.$$asyncQueue,r=this.$$postDigestQueue,
K,B,u=b,O,M=[],A,P,C;k("$digest");g.$$checkUrlChange();c=null;do{B=!1;for(O=this;m.length;){try{C=m.shift(),C.scope.$eval(C.expression)}catch(I){q.$$phase=null,e(I)}c=null}a:do{if(l=O.$$watchers)for(K=l.length;K--;)try{if(d=l[K])if((f=d.get(O))!==(h=d.last)&&!(d.eq?Ca(f,h):"number"===typeof f&&"number"===typeof h&&isNaN(f)&&isNaN(h)))B=!0,c=d,d.last=d.eq?Ka(f,null):f,d.fn(f,h===n?f:h,O),5>u&&(A=4-u,M[A]||(M[A]=[]),P=N(d.exp)?"fn: "+(d.exp.name||d.exp.toString()):d.exp,P+="; newVal: "+oa(f)+"; oldVal: "+
oa(h),M[A].push(P));else if(d===c){B=!1;break a}}catch(D){q.$$phase=null,e(D)}if(!(l=O.$$childHead||O!==this&&O.$$nextSibling))for(;O!==this&&!(l=O.$$nextSibling);)O=O.$parent}while(O=l);if((B||m.length)&&!u--)throw q.$$phase=null,a("infdig",b,oa(M));}while(B||m.length);for(q.$$phase=null;r.length;)try{r.shift()()}catch(x){e(x)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this!==q&&(r(this.$$listenerCount,Bb(null,l,this)),a.$$childHead==
this&&(a.$$childHead=this.$$nextSibling),a.$$childTail==this&&(a.$$childTail=this.$$prevSibling),this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling),this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling),this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=null,this.$$listeners={},this.$$watchers=this.$$asyncQueue=this.$$postDigestQueue=[],this.$destroy=this.$digest=this.$apply=v,this.$on=this.$watch=function(){return v})}},
$eval:function(a,b){return f(a)(this,b)},$evalAsync:function(a){q.$$phase||q.$$asyncQueue.length||g.defer(function(){q.$$asyncQueue.length&&q.$digest()});this.$$asyncQueue.push({scope:this,expression:a})},$$postDigest:function(a){this.$$postDigestQueue.push(a)},$apply:function(a){try{return k("$apply"),this.$eval(a)}catch(b){e(b)}finally{q.$$phase=null;try{q.$digest()}catch(c){throw e(c),c;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||
(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=Ta(c,b);-1!==d&&(c[d]=null,l(e,1,a))}},$emit:function(a,b){var c=[],d,f=this,g=!1,h={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=[h].concat(wa.call(arguments,1)),l,m;do{d=f.$$listeners[a]||c;h.currentScope=f;l=0;for(m=d.length;l<m;l++)if(d[l])try{d[l].apply(null,k)}catch(n){e(n)}else d.splice(l,1),l--,m--;if(g)break;
f=f.$parent}while(f);return h},$broadcast:function(a,b){for(var c=this,d=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},g=[f].concat(wa.call(arguments,1)),h,k;c=d;){f.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,g)}catch(l){e(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}return f}};var q=new h;
return q}]}function bd(){var b=/^\s*(https?|ftp|mailto|tel|file):/,a=/^\s*((https?|ftp|file):|data:image\/)/;this.aHrefSanitizationWhitelist=function(a){return D(a)?(b=a,this):b};this.imgSrcSanitizationWhitelist=function(b){return D(b)?(a=b,this):a};this.$get=function(){return function(c,d){var e=d?a:b,f;if(!R||8<=R)if(f=xa(c).href,""!==f&&!f.match(e))return"unsafe:"+f;return c}}}function Ee(b){if("self"===b)return b;if(G(b)){if(-1<b.indexOf("***"))throw za("iwcard",b);b=b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,
"\\$1").replace(/\x08/g,"\\x08").replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return RegExp("^"+b+"$")}if(kb(b))return RegExp("^"+b.source+"$");throw za("imatcher");}function Fc(b){var a=[];D(b)&&r(b,function(b){a.push(Ee(b))});return a}function ae(){this.SCE_CONTEXTS=fa;var b=["self"],a=[];this.resourceUrlWhitelist=function(a){arguments.length&&(b=Fc(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=Fc(b));return a};this.$get=["$injector",function(c){function d(a){var b=
function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var e=function(a){throw za("unsafe");};c.has("$sanitize")&&(e=c.get("$sanitize"));var f=d(),g={};g[fa.HTML]=d(f);g[fa.CSS]=d(f);g[fa.URL]=d(f);g[fa.JS]=d(f);g[fa.RESOURCE_URL]=d(g[fa.URL]);return{trustAs:function(a,b){var c=g.hasOwnProperty(a)?g[a]:null;if(!c)throw za("icontext",
a,b);if(null===b||b===u||""===b)return b;if("string"!==typeof b)throw za("itype",a);return new c(b)},getTrusted:function(c,d){if(null===d||d===u||""===d)return d;var f=g.hasOwnProperty(c)?g[c]:null;if(f&&d instanceof f)return d.$$unwrapTrustedValue();if(c===fa.RESOURCE_URL){var f=xa(d.toString()),l,n,q=!1;l=0;for(n=b.length;l<n;l++)if("self"===b[l]?Pb(f):b[l].exec(f.href)){q=!0;break}if(q)for(l=0,n=a.length;l<n;l++)if("self"===a[l]?Pb(f):a[l].exec(f.href)){q=!1;break}if(q)return d;throw za("insecurl",
d.toString());}if(c===fa.HTML)return e(d);throw za("unsafe");},valueOf:function(a){return a instanceof f?a.$$unwrapTrustedValue():a}}}]}function $d(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$sniffer","$sceDelegate",function(a,c,d){if(b&&c.msie&&8>c.msieDocumentMode)throw za("iequirks");var e=ha(fa);e.isEnabled=function(){return b};e.trustAs=d.trustAs;e.getTrusted=d.getTrusted;e.valueOf=d.valueOf;b||(e.trustAs=e.getTrusted=function(a,b){return b},
e.valueOf=ga);e.parseAs=function(b,c){var d=a(c);return d.literal&&d.constant?d:function(a,c){return e.getTrusted(b,d(a,c))}};var f=e.parseAs,g=e.getTrusted,h=e.trustAs;r(fa,function(a,b){var c=x(b);e[ab("parse_as_"+c)]=function(b){return f(a,b)};e[ab("get_trusted_"+c)]=function(b){return g(a,b)};e[ab("trust_as_"+c)]=function(b){return h(a,b)}});return e}]}function be(){this.$get=["$window","$document",function(b,a){var c={},d=U((/android (\d+)/.exec(x((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||
{}).userAgent),f=a[0]||{},g=f.documentMode,h,k=/^(Moz|webkit|O|ms)(?=[A-Z])/,m=f.body&&f.body.style,l=!1,n=!1;if(m){for(var q in m)if(l=k.exec(q)){h=l[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in m&&"webkit");l=!!("transition"in m||h+"Transition"in m);n=!!("animation"in m||h+"Animation"in m);!d||l&&n||(l=G(f.body.style.webkitTransition),n=G(f.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hashchange:"onhashchange"in b&&(!g||7<
g),hasEvent:function(a){if("input"==a&&9==R)return!1;if(F(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:Za(),vendorPrefix:h,transitions:l,animations:n,android:d,msie:R,msieDocumentMode:g}}]}function de(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,h,k){var m=c.defer(),l=m.promise,n=D(k)&&!k;h=a.defer(function(){try{m.resolve(e())}catch(a){m.reject(a),d(a)}finally{delete f[l.$$timeoutId]}n||b.$apply()},h);l.$$timeoutId=h;f[h]=m;
return l}var f={};e.cancel=function(b){return b&&b.$$timeoutId in f?(f[b.$$timeoutId].reject("canceled"),delete f[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return e}]}function xa(b,a){var c=b;R&&(Y.setAttribute("href",c),c=Y.href);Y.setAttribute("href",c);return{href:Y.href,protocol:Y.protocol?Y.protocol.replace(/:$/,""):"",host:Y.host,search:Y.search?Y.search.replace(/^\?/,""):"",hash:Y.hash?Y.hash.replace(/^#/,""):"",hostname:Y.hostname,port:Y.port,pathname:"/"===Y.pathname.charAt(0)?Y.pathname:
"/"+Y.pathname}}function Pb(b){b=G(b)?xa(b):b;return b.protocol===Gc.protocol&&b.host===Gc.host}function ee(){this.$get=aa(W)}function kc(b){function a(d,e){if(T(d)){var f={};r(d,function(b,c){f[c]=a(c,b)});return f}return b.factory(d+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Hc);a("date",Ic);a("filter",Fe);a("json",Ge);a("limitTo",He);a("lowercase",Ie);a("number",Jc);a("orderBy",Kc);a("uppercase",Je)}function Fe(){return function(b,
a,c){if(!L(b))return b;var d=typeof c,e=[];e.check=function(a){for(var b=0;b<e.length;b++)if(!e[b](a))return!1;return!0};"function"!==d&&(c="boolean"===d&&c?function(a,b){return Xa.equals(a,b)}:function(a,b){if(a&&b&&"object"===typeof a&&"object"===typeof b){for(var d in a)if("$"!==d.charAt(0)&&lb.call(a,d)&&c(a[d],b[d]))return!0;return!1}b=(""+b).toLowerCase();return-1<(""+a).toLowerCase().indexOf(b)});var f=function(a,b){if("string"===typeof b&&"!"===b.charAt(0))return!f(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,
b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if("$"!==d.charAt(0)&&f(a[d],b))return!0}return!1;case "array":for(d=0;d<a.length;d++)if(f(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a={$:a};case "object":for(var g in a)(function(b){"undefined"!==typeof a[b]&&e.push(function(c){return f("$"==b?c:c&&c[b],a[b])})})(g);break;case "function":e.push(a);break;default:return b}d=[];for(g=0;g<b.length;g++){var h=
b[g];e.check(h)&&d.push(h)}return d}}function Hc(b){var a=b.NUMBER_FORMATS;return function(b,d){F(d)&&(d=a.CURRENCY_SYM);return Lc(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function Jc(b){var a=b.NUMBER_FORMATS;return function(b,d){return Lc(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Lc(b,a,c,d,e){if(null==b||!isFinite(b)||T(b))return"";var f=0>b;b=Math.abs(b);var g=b+"",h="",k=[],m=!1;if(-1!==g.indexOf("e")){var l=g.match(/([\d\.]+)e(-?)(\d+)/);l&&"-"==l[2]&&
l[3]>e+1?(g="0",b=0):(h=g,m=!0)}if(m)0<e&&(-1<b&&1>b)&&(h=b.toFixed(e));else{g=(g.split(Mc)[1]||"").length;F(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);0===b&&(f=!1);b=(""+b).split(Mc);g=b[0];b=b[1]||"";var l=0,n=a.lgSize,q=a.gSize;if(g.length>=n+q)for(l=g.length-n,m=0;m<l;m++)0===(l-m)%q&&0!==m&&(h+=c),h+=g.charAt(m);for(m=l;m<g.length;m++)0===(g.length-m)%n&&0!==m&&(h+=c),h+=g.charAt(m);for(;b.length<e;)b+="0";e&&"0"!==e&&(h+=d+b.substr(0,
e))}k.push(f?a.negPre:a.posPre);k.push(h);k.push(f?a.negSuf:a.posSuf);return k.join("")}function Vb(b,a,c){var d="";0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function Z(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(0<c||e>-c)e+=c;0===e&&-12==c&&(e=12);return Vb(e,a,d)}}function vb(b,a){return function(c,d){var e=c["get"+b](),f=La(a?"SHORT"+b:b);return d[f][e]}}function Ic(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?
a.setUTCFullYear:a.setFullYear,k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=U(b[9]+b[10]),g=U(b[9]+b[11]));h.call(a,U(b[1]),U(b[2])-1,U(b[3]));f=U(b[4]||0)-f;g=U(b[5]||0)-g;h=U(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var f="",g=[],h,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;G(c)&&(c=Ke.test(c)?U(c):a(c));jb(c)&&(c=new Date(c));
if(!va(c))return c;for(;e;)(k=Le.exec(e))?(g=g.concat(wa.call(k,1)),e=g.pop()):(g.push(e),e=null);r(g,function(a){h=Me[a];f+=h?h(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return f}}function Ge(){return function(b){return oa(b,!0)}}function He(){return function(b,a){if(!L(b)&&!G(b))return b;a=Infinity===Math.abs(Number(a))?Number(a):U(a);if(G(b))return a?0<=a?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);0<a?(d=0,e=a):(d=
b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Kc(b){return function(a,c,d){function e(a,b){return Wa(b)?function(b,c){return a(c,b)}:a}function f(a,b){var c=typeof a,d=typeof b;return c==d?(va(a)&&va(b)&&(a=a.valueOf(),b=b.valueOf()),"string"==c&&(a=a.toLowerCase(),b=b.toLowerCase()),a===b?0:a<b?-1:1):c<d?-1:1}if(!Sa(a))return a;c=L(c)?c:[c];0===c.length&&(c=["+"]);c=Uc(c,function(a){var c=!1,d=a||ga;if(G(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))c="-"==a.charAt(0),a=a.substring(1);
if(""===a)return e(function(a,b){return f(a,b)},c);d=b(a);if(d.constant){var m=d();return e(function(a,b){return f(a[m],b[m])},c)}}return e(function(a,b){return f(d(a),d(b))},c)});return wa.call(a).sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(0!==e)return e}return 0},d))}}function Aa(b){N(b)&&(b={link:b});b.restrict=b.restrict||"AC";return aa(b)}function Nc(b,a,c,d){function e(a,c){c=c?"-"+nb(c,"-"):"";d.setClass(b,(a?wb:xb)+c,(a?xb:wb)+c)}var f=this,g=b.parent().controller("form")||
yb,h=0,k=f.$error={},m=[];f.$name=a.name||a.ngForm;f.$dirty=!1;f.$pristine=!0;f.$valid=!0;f.$invalid=!1;g.$addControl(f);b.addClass(Ra);e(!0);f.$addControl=function(a){Ea(a.$name,"input");m.push(a);a.$name&&(f[a.$name]=a)};f.$removeControl=function(a){a.$name&&f[a.$name]===a&&delete f[a.$name];r(k,function(b,c){f.$setValidity(c,!0,a)});Ua(m,a)};f.$setValidity=function(a,b,c){var d=k[a];if(b)d&&(Ua(d,c),d.length||(h--,h||(e(b),f.$valid=!0,f.$invalid=!1),k[a]=!1,e(!0,a),g.$setValidity(a,!0,f)));else{h||
e(b);if(d){if(-1!=Ta(d,c))return}else k[a]=d=[],h++,e(!1,a),g.$setValidity(a,!1,f);d.push(c);f.$valid=!1;f.$invalid=!0}};f.$setDirty=function(){d.removeClass(b,Ra);d.addClass(b,zb);f.$dirty=!0;f.$pristine=!1;g.$setDirty()};f.$setPristine=function(){d.removeClass(b,zb);d.addClass(b,Ra);f.$dirty=!1;f.$pristine=!0;r(m,function(a){a.$setPristine()})}}function ua(b,a,c,d){b.$setValidity(a,c);return c?d:u}function Oc(b,a){var c,d;if(a)for(c=0;c<a.length;++c)if(d=a[c],b[d])return!0;return!1}function Ne(b,
a,c,d,e){T(e)&&(b.$$hasNativeValidators=!0,b.$parsers.push(function(f){if(b.$error[a]||Oc(e,d)||!Oc(e,c))return f;b.$setValidity(a,!1)}))}function Ab(b,a,c,d,e,f){var g=a.prop(Oe),h=a[0].placeholder,k={},m=x(a[0].type);d.$$validityState=g;if(!e.android){var l=!1;a.on("compositionstart",function(a){l=!0});a.on("compositionend",function(){l=!1;n()})}var n=function(e){if(!l){var f=a.val();if(R&&"input"===(e||k).type&&a[0].placeholder!==h)h=a[0].placeholder;else if("password"!==m&&Wa(c.ngTrim||"T")&&
(f=$(f)),e=g&&d.$$hasNativeValidators,d.$viewValue!==f||""===f&&e)b.$root.$$phase?d.$setViewValue(f):b.$apply(function(){d.$setViewValue(f)})}};if(e.hasEvent("input"))a.on("input",n);else{var q,p=function(){q||(q=f.defer(function(){n();q=null}))};a.on("keydown",function(a){a=a.keyCode;91===a||(15<a&&19>a||37<=a&&40>=a)||p()});if(e.hasEvent("paste"))a.on("paste cut",p)}a.on("change",n);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)};var s=c.ngPattern;s&&((e=s.match(/^\/(.*)\/([gim]*)$/))?
(s=RegExp(e[1],e[2]),e=function(a){return ua(d,"pattern",d.$isEmpty(a)||s.test(a),a)}):e=function(c){var e=b.$eval(s);if(!e||!e.test)throw z("ngPattern")("noregexp",s,e,ia(a));return ua(d,"pattern",d.$isEmpty(c)||e.test(c),c)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var r=U(c.ngMinlength);e=function(a){return ua(d,"minlength",d.$isEmpty(a)||a.length>=r,a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var w=U(c.ngMaxlength);e=function(a){return ua(d,"maxlength",d.$isEmpty(a)||
a.length<=w,a)};d.$parsers.push(e);d.$formatters.push(e)}}function Wb(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],l=0;l<b.length;l++)if(e==b[l])continue a;c.push(e)}return c}function e(a){if(!L(a)){if(G(a))return a.split(" ");if(T(a)){var b=[];r(a,function(a,c){a&&(b=b.concat(c.split(" ")))});return b}}return a}return{restrict:"AC",link:function(f,g,h){function k(a,b){var c=g.data("$classCounts")||{},d=[];r(a,function(a){if(0<
b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});g.data("$classCounts",c);return d.join(" ")}function m(b){if(!0===a||f.$index%2===a){var m=e(b||[]);if(!l){var p=k(m,1);h.$addClass(p)}else if(!Ca(b,l)){var s=e(l),p=d(m,s),m=d(s,m),m=k(m,-1),p=k(p,1);0===p.length?c.removeClass(g,m):0===m.length?c.addClass(g,p):c.setClass(g,p,m)}}l=ha(b)}var l;f.$watch(h[b],m,!0);h.$observe("class",function(a){m(f.$eval(h[b]))});"ngClass"!==b&&f.$watch("$index",function(c,d){var g=c&1;if(g!==(d&1)){var l=e(f.$eval(h[b]));
g===a?(g=k(l,1),h.$addClass(g)):(g=k(l,-1),h.$removeClass(g))}})}}}]}var Oe="validity",x=function(b){return G(b)?b.toLowerCase():b},lb=Object.prototype.hasOwnProperty,La=function(b){return G(b)?b.toUpperCase():b},R,A,Fa,wa=[].slice,Pe=[].push,Ba=Object.prototype.toString,Va=z("ng"),Xa=W.angular||(W.angular={}),$a,Pa,na=["0","0","0"];R=U((/msie (\d+)/.exec(x(navigator.userAgent))||[])[1]);isNaN(R)&&(R=U((/trident\/.*; rv:(\d+)/.exec(x(navigator.userAgent))||[])[1]));v.$inject=[];ga.$inject=[];var L=
function(){return N(Array.isArray)?Array.isArray:function(b){return"[object Array]"===Ba.call(b)}}(),$=function(){return String.prototype.trim?function(b){return G(b)?b.trim():b}:function(b){return G(b)?b.replace(/^\s\s*/,"").replace(/\s\s*$/,""):b}}();Pa=9>R?function(b){b=b.nodeName?b:b[0];return b.scopeName&&"HTML"!=b.scopeName?La(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var Za=function(){if(D(Za.isActive_))return Za.isActive_;var b=!(!X.querySelector("[ng-csp]")&&
!X.querySelector("[data-ng-csp]"));if(!b)try{new Function("")}catch(a){b=!0}return Za.isActive_=b},Xc=/[A-Z]/g,$c={full:"1.2.28",major:1,minor:2,dot:28,codeName:"finnish-disembarkation"};S.expando="ng339";var cb=S.cache={},me=1,sb=W.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},bb=W.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)};S._data=function(b){return this.cache[b[this.expando]]||
{}};var he=/([\:\-\_]+(.))/g,ie=/^moz([A-Z])/,Hb=z("jqLite"),je=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,Ib=/<|&#?\w+;/,ke=/<([\w:]+)/,le=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,da={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};da.optgroup=da.option;da.tbody=da.tfoot=da.colgroup=
da.caption=da.thead;da.th=da.td;var Oa=S.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===X.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),S(W).on("load",a))},toString:function(){var b=[];r(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?A(this[b]):A(this[this.length+b])},length:0,push:Pe,sort:[].sort,splice:[].splice},rb={};r("multiple selected checked disabled readOnly required open".split(" "),function(b){rb[x(b)]=b});
var pc={};r("input select option textarea button form details".split(" "),function(b){pc[La(b)]=!0});r({data:Mb,removeData:Lb},function(b,a){S[a]=b});r({data:Mb,inheritedData:qb,scope:function(b){return A.data(b,"$scope")||qb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return A.data(b,"$isolateScope")||A.data(b,"$isolateScopeNoTemplate")},controller:mc,injector:function(b){return qb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Nb,css:function(b,
a,c){a=ab(a);if(D(c))b.style[a]=c;else{var d;8>=R&&(d=b.currentStyle&&b.currentStyle[a],""===d&&(d="auto"));d=d||b.style[a];8>=R&&(d=""===d?u:d);return d}},attr:function(b,a,c){var d=x(a);if(rb[d])if(D(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||v).specified?d:u;else if(D(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?u:b},prop:function(b,a,c){if(D(c))b[a]=c;else return b[a]},text:function(){function b(b,
d){var e=a[b.nodeType];if(F(d))return e?b[e]:"";b[e]=d}var a=[];9>R?(a[1]="innerText",a[3]="nodeValue"):a[1]=a[3]="textContent";b.$dv="";return b}(),val:function(b,a){if(F(a)){if("SELECT"===Pa(b)&&b.multiple){var c=[];r(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(F(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)Ma(d[c]);b.innerHTML=a},empty:nc},function(b,a){S.prototype[a]=function(a,d){var e,
f,g=this.length;if(b!==nc&&(2==b.length&&b!==Nb&&b!==mc?a:d)===u){if(T(a)){for(e=0;e<g;e++)if(b===Mb)b(this[e],a);else for(f in a)b(this[e],f,a[f]);return this}e=b.$dv;g=e===u?Math.min(g,1):g;for(f=0;f<g;f++){var h=b(this[f],a,d);e=e?e+h:h}return e}for(e=0;e<g;e++)b(this[e],a,d);return this}});r({removeData:Lb,dealoc:Ma,on:function a(c,d,e,f){if(D(f))throw Hb("onargs");var g=pa(c,"events"),h=pa(c,"handle");g||pa(c,"events",g={});h||pa(c,"handle",h=ne(c,g));r(d.split(" "),function(d){var f=g[d];if(!f){if("mouseenter"==
d||"mouseleave"==d){var l=X.body.contains||X.body.compareDocumentPosition?function(a,c){var d=9===a.nodeType?a.documentElement:a,e=c&&c.parentNode;return a===e||!!(e&&1===e.nodeType&&(d.contains?d.contains(e):a.compareDocumentPosition&&a.compareDocumentPosition(e)&16))}:function(a,c){if(c)for(;c=c.parentNode;)if(c===a)return!0;return!1};g[d]=[];a(c,{mouseleave:"mouseout",mouseenter:"mouseover"}[d],function(a){var c=a.relatedTarget;c&&(c===this||l(this,c))||h(a,d)})}else sb(c,d,h),g[d]=[];f=g[d]}f.push(e)})},
off:lc,one:function(a,c,d){a=A(a);a.on(c,function f(){a.off(c,d);a.off(c,f)});a.on(c,d)},replaceWith:function(a,c){var d,e=a.parentNode;Ma(a);r(new S(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];r(a.childNodes,function(a){1===a.nodeType&&c.push(a)});return c},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,c){r(new S(c),function(c){1!==a.nodeType&&11!==a.nodeType||a.appendChild(c)})},prepend:function(a,
c){if(1===a.nodeType){var d=a.firstChild;r(new S(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=A(c)[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){Ma(a);var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;r(new S(c),function(a){e.insertBefore(a,d.nextSibling);d=a})},addClass:pb,removeClass:ob,toggleClass:function(a,c,d){c&&r(c.split(" "),function(c){var f=d;F(f)&&(f=!Nb(a,c));(f?pb:ob)(a,c)})},parent:function(a){return(a=
a.parentNode)&&11!==a.nodeType?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;for(a=a.nextSibling;null!=a&&1!==a.nodeType;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName?a.getElementsByTagName(c):[]},clone:Kb,triggerHandler:function(a,c,d){var e,f;e=c.type||c;var g=(pa(a,"events")||{})[e];g&&(e={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopPropagation:v,type:e,target:a},
c.type&&(e=E(e,c)),c=ha(g),f=d?[e].concat(d):[e],r(c,function(c){c.apply(a,f)}))}},function(a,c){S.prototype[c]=function(c,e,f){for(var g,h=0;h<this.length;h++)F(g)?(g=a(this[h],c,e,f),D(g)&&(g=A(g))):Jb(g,a(this[h],c,e,f));return D(g)?g:this};S.prototype.bind=S.prototype.on;S.prototype.unbind=S.prototype.off});db.prototype={put:function(a,c){this[Na(a,this.nextUid)]=c},get:function(a){return this[Na(a,this.nextUid)]},remove:function(a){var c=this[a=Na(a,this.nextUid)];delete this[a];return c}};var pe=
/^function\s*[^\(]*\(\s*([^\)]*)\)/m,qe=/,/,re=/^\s*(_?)(\S+?)\1\s*$/,oe=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,eb=z("$injector"),Qe=z("$animate"),Ld=["$provide",function(a){this.$$selectors={};this.register=function(c,d){var e=c+"-animation";if(c&&"."!=c.charAt(0))throw Qe("notcsel",c);this.$$selectors[c.substr(1)]=e;a.factory(e,d)};this.classNameFilter=function(a){1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null);return this.$$classNameFilter};this.$get=["$timeout","$$asyncCallback",
function(a,d){return{enter:function(a,c,g,h){g?g.after(a):(c&&c[0]||(c=g.parent()),c.append(a));h&&d(h)},leave:function(a,c){a.remove();c&&d(c)},move:function(a,c,d,h){this.enter(a,c,d,h)},addClass:function(a,c,g){c=G(c)?c:L(c)?c.join(" "):"";r(a,function(a){pb(a,c)});g&&d(g)},removeClass:function(a,c,g){c=G(c)?c:L(c)?c.join(" "):"";r(a,function(a){ob(a,c)});g&&d(g)},setClass:function(a,c,g,h){r(a,function(a){pb(a,c);ob(a,g)});h&&d(h)},enabled:v}}]}],ja=z("$compile");gc.$inject=["$provide","$$sanitizeUriProvider"];
var we=/^(x[\:\-_]|data[\:\-_])/i,wc=z("$interpolate"),Re=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,ze={http:80,https:443,ftp:21},Sb=z("$location");Ac.prototype=Tb.prototype=zc.prototype={$$html5:!1,$$replace:!1,absUrl:tb("$$absUrl"),url:function(a){if(F(a))return this.$$url;a=Re.exec(a);a[1]&&this.path(decodeURIComponent(a[1]));(a[2]||a[1])&&this.search(a[3]||"");this.hash(a[5]||"");return this},protocol:tb("$$protocol"),host:tb("$$host"),port:tb("$$port"),path:Bc("$$path",function(a){a=null!==a?a.toString():
"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,c){switch(arguments.length){case 0:return this.$$search;case 1:if(G(a)||jb(a))a=a.toString(),this.$$search=cc(a);else if(T(a))r(a,function(c,e){null==c&&delete a[e]}),this.$$search=a;else throw Sb("isrcharg");break;default:F(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:Bc("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};var la=z("$parse"),Ec=
{},ya,Se=Function.prototype.call,Te=Function.prototype.apply,Pc=Function.prototype.bind,hb={"null":function(){return null},"true":function(){return!0},"false":function(){return!1},undefined:v,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return D(d)?D(e)?d+e:d:D(e)?e:u},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(D(d)?d:0)-(D(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^
e(a,c)},"=":v,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,
c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Ue={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Ub=function(a){this.options=a};Ub.prototype={constructor:Ub,lex:function(a){this.text=a;this.index=0;this.ch=u;this.lastCh=":";for(this.tokens=[];this.index<this.text.length;){this.ch=this.text.charAt(this.index);if(this.is("\"'"))this.readString(this.ch);else if(this.isNumber(this.ch)||this.is(".")&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(this.ch))this.readIdent();
else if(this.is("(){}[].,;:?"))this.tokens.push({index:this.index,text:this.ch}),this.index++;else if(this.isWhitespace(this.ch)){this.index++;continue}else{a=this.ch+this.peek();var c=a+this.peek(2),d=hb[this.ch],e=hb[a],f=hb[c];f?(this.tokens.push({index:this.index,text:c,fn:f}),this.index+=3):e?(this.tokens.push({index:this.index,text:a,fn:e}),this.index+=2):d?(this.tokens.push({index:this.index,text:this.ch,fn:d}),this.index+=1):this.throwError("Unexpected next character ",this.index,this.index+
1)}this.lastCh=this.ch}return this.tokens},is:function(a){return-1!==a.indexOf(this.ch)},was:function(a){return-1!==a.indexOf(this.lastCh)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},
throwError:function(a,c,d){d=d||this.index;c=D(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw la("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=x(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-
1))break;else this.throwError("Invalid exponent")}this.index++}a*=1;this.tokens.push({index:c,text:a,literal:!0,constant:!0,fn:function(){return a}})},readIdent:function(){for(var a=this,c="",d=this.index,e,f,g,h;this.index<this.text.length;){h=this.text.charAt(this.index);if("."===h||this.isIdent(h)||this.isNumber(h))"."===h&&(e=this.index),c+=h;else break;this.index++}if(e)for(f=this.index;f<this.text.length;){h=this.text.charAt(f);if("("===h){g=c.substr(e-d+1);c=c.substr(0,e-d);this.index=f;break}if(this.isWhitespace(h))f++;
else break}d={index:d,text:c};if(hb.hasOwnProperty(c))d.fn=hb[c],d.literal=!0,d.constant=!0;else{var k=Dc(c,this.options,this.text);d.fn=E(function(a,c){return k(a,c)},{assign:function(d,e){return ub(d,c,e,a.text,a.options)}})}this.tokens.push(d);g&&(this.tokens.push({index:e,text:"."}),this.tokens.push({index:e+1,text:g}))},readString:function(a){var c=this.index;this.index++;for(var d="",e=a,f=!1;this.index<this.text.length;){var g=this.text.charAt(this.index),e=e+g;if(f)"u"===g?(f=this.text.substring(this.index+
1,this.index+5),f.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+f+"]"),this.index+=4,d+=String.fromCharCode(parseInt(f,16))):d+=Ue[g]||g,f=!1;else if("\\"===g)f=!0;else{if(g===a){this.index++;this.tokens.push({index:c,text:e,string:d,literal:!0,constant:!0,fn:function(){return d}});return}d+=g}this.index++}this.throwError("Unterminated quote",c)}};var gb=function(a,c,d){this.lexer=a;this.$filter=c;this.options=d};gb.ZERO=E(function(){return 0},{constant:!0});gb.prototype={constructor:gb,
parse:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.statements();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);a.literal=!!a.literal;a.constant=!!a.constant;return a},primary:function(){var a;if(this.expect("("))a=this.filterChain(),this.consume(")");else if(this.expect("["))a=this.arrayDeclaration();else if(this.expect("{"))a=this.object();else{var c=this.expect();(a=c.fn)||this.throwError("not a primary expression",c);a.literal=!!c.literal;a.constant=
!!c.constant}for(var d;c=this.expect("(","[",".");)"("===c.text?(a=this.functionCall(a,d),d=null):"["===c.text?(d=a,a=this.objectIndex(a)):"."===c.text?(d=a,a=this.fieldAccess(a)):this.throwError("IMPOSSIBLE");return a},throwError:function(a,c){throw la("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},peekToken:function(){if(0===this.tokens.length)throw la("ueoe",this.text);return this.tokens[0]},peek:function(a,c,d,e){if(0<this.tokens.length){var f=this.tokens[0],g=f.text;if(g===
a||g===c||g===d||g===e||!(a||c||d||e))return f}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},consume:function(a){this.expect(a)||this.throwError("is unexpected, expecting ["+a+"]",this.peek())},unaryFn:function(a,c){return E(function(d,e){return a(d,e,c)},{constant:c.constant})},ternaryFn:function(a,c,d){return E(function(e,f){return a(e,f)?c(e,f):d(e,f)},{constant:a.constant&&c.constant&&d.constant})},binaryFn:function(a,c,d){return E(function(e,f){return c(e,
f,a,d)},{constant:a.constant&&d.constant})},statements:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.filterChain()),!this.expect(";"))return 1===a.length?a[0]:function(c,d){for(var e,f=0;f<a.length;f++){var g=a[f];g&&(e=g(c,d))}return e}},filterChain:function(){for(var a=this.expression(),c;;)if(c=this.expect("|"))a=this.binaryFn(a,c.fn,this.filter());else return a},filter:function(){for(var a=this.expect(),c=this.$filter(a.text),d=[];;)if(a=this.expect(":"))d.push(this.expression());
else{var e=function(a,e,h){h=[h];for(var k=0;k<d.length;k++)h.push(d[k](a,e));return c.apply(a,h)};return function(){return e}}},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary(),c,d;return(d=this.expect("="))?(a.assign||this.throwError("implies assignment but ["+this.text.substring(0,d.index)+"] can not be assigned to",d),c=this.ternary(),function(d,f){return a.assign(d,c(d,f),f)}):a},ternary:function(){var a=this.logicalOR(),c,d;if(this.expect("?")){c=this.assignment();
if(d=this.expect(":"))return this.ternaryFn(a,c,this.assignment());this.throwError("expected :",d)}else return a},logicalOR:function(){for(var a=this.logicalAND(),c;;)if(c=this.expect("||"))a=this.binaryFn(a,c.fn,this.logicalAND());else return a},logicalAND:function(){var a=this.equality(),c;if(c=this.expect("&&"))a=this.binaryFn(a,c.fn,this.logicalAND());return a},equality:function(){var a=this.relational(),c;if(c=this.expect("==","!=","===","!=="))a=this.binaryFn(a,c.fn,this.equality());return a},
relational:function(){var a=this.additive(),c;if(c=this.expect("<",">","<=",">="))a=this.binaryFn(a,c.fn,this.relational());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.fn,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.fn,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(gb.ZERO,a.fn,
this.unary()):(a=this.expect("!"))?this.unaryFn(a.fn,this.unary()):this.primary()},fieldAccess:function(a){var c=this,d=this.expect().text,e=Dc(d,this.options,this.text);return E(function(c,d,h){return e(h||a(c,d))},{assign:function(e,g,h){(h=a(e,h))||a.assign(e,h={});return ub(h,d,g,c.text,c.options)}})},objectIndex:function(a){var c=this,d=this.expression();this.consume("]");return E(function(e,f){var g=a(e,f),h=d(e,f),k;ka(h,c.text);if(!g)return u;(g=ma(g[h],c.text))&&(g.then&&c.options.unwrapPromises)&&
(k=g,"$$v"in g||(k.$$v=u,k.then(function(a){k.$$v=a})),g=g.$$v);return g},{assign:function(e,f,g){var h=ka(d(e,g),c.text);(g=ma(a(e,g),c.text))||a.assign(e,g={});return g[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());while(this.expect(","))}this.consume(")");var e=this;return function(f,g){for(var h=[],k=c?c(f,g):f,m=0;m<d.length;m++)h.push(ma(d[m](f,g),e.text));m=a(f,g,k)||v;ma(k,e.text);var l=e.text;if(m){if(m.constructor===m)throw la("isecfn",
l);if(m===Se||m===Te||Pc&&m===Pc)throw la("isecff",l);}h=m.apply?m.apply(k,h):m(h[0],h[1],h[2],h[3],h[4]);return ma(h,e.text)}},arrayDeclaration:function(){var a=[],c=!0;if("]"!==this.peekToken().text){do{if(this.peek("]"))break;var d=this.expression();a.push(d);d.constant||(c=!1)}while(this.expect(","))}this.consume("]");return E(function(c,d){for(var g=[],h=0;h<a.length;h++)g.push(a[h](c,d));return g},{literal:!0,constant:c})},object:function(){var a=[],c=!0;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;
var d=this.expect(),d=d.string||d.text;this.consume(":");var e=this.expression();a.push({key:d,value:e});e.constant||(c=!1)}while(this.expect(","))}this.consume("}");return E(function(c,d){for(var e={},k=0;k<a.length;k++){var m=a[k];e[m.key]=m.value(c,d)}return e},{literal:!0,constant:c})}};var Ce={},Be={},za=z("$sce"),fa={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},Y=X.createElement("a"),Gc=xa(W.location.href,!0);kc.$inject=["$provide"];Hc.$inject=["$locale"];Jc.$inject=["$locale"];
var Mc=".",Me={yyyy:Z("FullYear",4),yy:Z("FullYear",2,0,!0),y:Z("FullYear",1),MMMM:vb("Month"),MMM:vb("Month",!0),MM:Z("Month",2,1),M:Z("Month",1,1),dd:Z("Date",2),d:Z("Date",1),HH:Z("Hours",2),H:Z("Hours",1),hh:Z("Hours",2,-12),h:Z("Hours",1,-12),mm:Z("Minutes",2),m:Z("Minutes",1),ss:Z("Seconds",2),s:Z("Seconds",1),sss:Z("Milliseconds",3),EEEE:vb("Day"),EEE:vb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Vb(Math[0<
a?"floor":"ceil"](a/60),2)+Vb(Math.abs(a%60),2))}},Le=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,Ke=/^\-?\d+$/;Ic.$inject=["$locale"];var Ie=aa(x),Je=aa(La);Kc.$inject=["$parse"];var cd=aa({restrict:"E",compile:function(a,c){8>=R&&(c.href||c.name||c.$set("href",""),a.append(X.createComment("IE fix")));if(!c.href&&!c.xlinkHref&&!c.name)return function(a,c){var f="[object SVGAnimatedString]"===Ba.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(f)||
a.preventDefault()})}}}),Fb={};r(rb,function(a,c){if("multiple"!=a){var d=qa("ng-"+c);Fb[d]=function(){return{priority:100,link:function(a,f,g){a.$watch(g[d],function(a){g.$set(c,!!a)})}}}}});r(["src","srcset","href"],function(a){var c=qa("ng-"+a);Fb[c]=function(){return{priority:99,link:function(d,e,f){var g=a,h=a;"href"===a&&"[object SVGAnimatedString]"===Ba.call(e.prop("href"))&&(h="xlinkHref",f.$attr[h]="xlink:href",g=null);f.$observe(c,function(c){c?(f.$set(h,c),R&&g&&e.prop(g,f[h])):"href"===
a&&f.$set(h,null)})}}}});var yb={$addControl:v,$removeControl:v,$setValidity:v,$setDirty:v,$setPristine:v};Nc.$inject=["$element","$attrs","$scope","$animate"];var Qc=function(a){return["$timeout",function(c){return{name:"form",restrict:a?"EAC":"E",controller:Nc,compile:function(){return{pre:function(a,e,f,g){if(!f.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};sb(e[0],"submit",h);e.on("$destroy",function(){c(function(){bb(e[0],"submit",h)},0,!1)})}var k=e.parent().controller("form"),
m=f.name||f.ngForm;m&&ub(a,m,g,m);if(k)e.on("$destroy",function(){k.$removeControl(g);m&&ub(a,m,u,m);E(g,yb)})}}}}}]},dd=Qc(),qd=Qc(!0),Ve=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,We=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,Xe=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,Rc={text:Ab,number:function(a,c,d,e,f,g){Ab(a,c,d,e,f,g);e.$parsers.push(function(a){var c=e.$isEmpty(a);if(c||Xe.test(a))return e.$setValidity("number",
!0),""===a?null:c?a:parseFloat(a);e.$setValidity("number",!1);return u});Ne(e,"number",Ye,null,e.$$validityState);e.$formatters.push(function(a){return e.$isEmpty(a)?"":""+a});d.min&&(a=function(a){var c=parseFloat(d.min);return ua(e,"min",e.$isEmpty(a)||a>=c,a)},e.$parsers.push(a),e.$formatters.push(a));d.max&&(a=function(a){var c=parseFloat(d.max);return ua(e,"max",e.$isEmpty(a)||a<=c,a)},e.$parsers.push(a),e.$formatters.push(a));e.$formatters.push(function(a){return ua(e,"number",e.$isEmpty(a)||
jb(a),a)})},url:function(a,c,d,e,f,g){Ab(a,c,d,e,f,g);a=function(a){return ua(e,"url",e.$isEmpty(a)||Ve.test(a),a)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,f,g){Ab(a,c,d,e,f,g);a=function(a){return ua(e,"email",e.$isEmpty(a)||We.test(a),a)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){F(d.name)&&c.attr("name",ib());c.on("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};
d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var f=d.ngTrueValue,g=d.ngFalseValue;G(f)||(f=!0);G(g)||(g=!1);c.on("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return a!==f};e.$formatters.push(function(a){return a===f});e.$parsers.push(function(a){return a?f:g})},hidden:v,button:v,submit:v,reset:v,file:v},Ye=["badInput"],hc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",
link:function(d,e,f,g){g&&(Rc[x(f.type)]||Rc.text)(d,e,f,g,c,a)}}}],wb="ng-valid",xb="ng-invalid",Ra="ng-pristine",zb="ng-dirty",Ze=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate",function(a,c,d,e,f,g){function h(a,c){c=c?"-"+nb(c,"-"):"";g.removeClass(e,(a?xb:wb)+c);g.addClass(e,(a?wb:xb)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=
d.name;var k=f(d.ngModel),m=k.assign;if(!m)throw z("ngModel")("nonassign",d.ngModel,ia(e));this.$render=v;this.$isEmpty=function(a){return F(a)||""===a||null===a||a!==a};var l=e.inheritedData("$formController")||yb,n=0,q=this.$error={};e.addClass(Ra);h(!0);this.$setValidity=function(a,c){q[a]!==!c&&(c?(q[a]&&n--,n||(h(!0),this.$valid=!0,this.$invalid=!1)):(h(!1),this.$invalid=!0,this.$valid=!1,n++),q[a]=!c,h(c,a),l.$setValidity(a,c,this))};this.$setPristine=function(){this.$dirty=!1;this.$pristine=
!0;g.removeClass(e,zb);g.addClass(e,Ra)};this.$setViewValue=function(d){this.$viewValue=d;this.$pristine&&(this.$dirty=!0,this.$pristine=!1,g.removeClass(e,Ra),g.addClass(e,zb),l.$setDirty());r(this.$parsers,function(a){d=a(d)});this.$modelValue!==d&&(this.$modelValue=d,m(a,d),r(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}}))};var p=this;a.$watch(function(){var c=k(a);if(p.$modelValue!==c){var d=p.$formatters,e=d.length;for(p.$modelValue=c;e--;)c=d[e](c);p.$viewValue!==c&&(p.$viewValue=
c,p.$render())}return c})}],Fd=function(){return{require:["ngModel","^?form"],controller:Ze,link:function(a,c,d,e){var f=e[0],g=e[1]||yb;g.$addControl(f);a.$on("$destroy",function(){g.$removeControl(f)})}}},Hd=aa({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),ic=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var f=function(a){if(d.required&&e.$isEmpty(a))e.$setValidity("required",!1);else return e.$setValidity("required",
!0),a};e.$formatters.push(f);e.$parsers.unshift(f);d.$observe("required",function(){f(e.$viewValue)})}}}},Gd=function(){return{require:"ngModel",link:function(a,c,d,e){var f=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){if(!F(a)){var c=[];a&&r(a.split(f),function(a){a&&c.push($(a))});return c}});e.$formatters.push(function(a){return L(a)?a.join(", "):u});e.$isEmpty=function(a){return!a||!a.length}}}},$e=/^(true|false|\d+)$/,Id=function(){return{priority:100,
compile:function(a,c){return $e.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},id=Aa({compile:function(a){a.addClass("ng-binding");return function(a,d,e){d.data("$binding",e.ngBind);a.$watch(e.ngBind,function(a){d.text(a==u?"":a)})}}}),kd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],
jd=["$sce","$parse",function(a,c){return{compile:function(d){d.addClass("ng-binding");return function(d,f,g){f.data("$binding",g.ngBindHtml);var h=c(g.ngBindHtml);d.$watch(function(){return(h(d)||"").toString()},function(c){f.html(a.getTrustedHtml(h(d))||"")})}}}}],ld=Wb("",!0),nd=Wb("Odd",0),md=Wb("Even",1),od=Aa({compile:function(a,c){c.$set("ngCloak",u);a.removeClass("ng-cloak")}}),pd=[function(){return{scope:!0,controller:"@",priority:500}}],jc={},af={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),
function(a){var c=qa("ng-"+a);jc[c]=["$parse","$rootScope",function(d,e){return{compile:function(f,g){var h=d(g[c],!0);return function(c,d){d.on(a,function(d){var f=function(){h(c,{$event:d})};af[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var sd=["$animate",function(a){return{transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,k,m;c.$watch(e.ngIf,function(f){Wa(f)?k||(k=c.$new(),g(k,function(c){c[c.length++]=X.createComment(" end ngIf: "+e.ngIf+
" ");h={clone:c};a.enter(c,d.parent(),d)})):(m&&(m.remove(),m=null),k&&(k.$destroy(),k=null),h&&(m=Eb(h.clone),a.leave(m,function(){m=null}),h=null))})}}}],td=["$http","$templateCache","$anchorScroll","$animate","$sce",function(a,c,d,e,f){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:Xa.noop,compile:function(g,h){var k=h.ngInclude||h.src,m=h.onload||"",l=h.autoscroll;return function(g,h,p,r,J){var w=0,t,y,u,B=function(){y&&(y.remove(),y=null);t&&(t.$destroy(),t=null);
u&&(e.leave(u,function(){y=null}),y=u,u=null)};g.$watch(f.parseAsResourceUrl(k),function(f){var k=function(){!D(l)||l&&!g.$eval(l)||d()},p=++w;f?(a.get(f,{cache:c}).success(function(a){if(p===w){var c=g.$new();r.template=a;a=J(c,function(a){B();e.enter(a,null,h,k)});t=c;u=a;t.$emit("$includeContentLoaded");g.$eval(m)}}).error(function(){p===w&&B()}),g.$emit("$includeContentRequested")):(B(),r.template=null)})}}}}],Jd=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",
link:function(c,d,e,f){d.html(f.template);a(d.contents())(c)}}}],ud=Aa({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),vd=Aa({terminal:!0,priority:1E3}),wd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,f,g){var h=g.count,k=g.$attr.when&&f.attr(g.$attr.when),m=g.offset||0,l=e.$eval(k)||{},n={},q=c.startSymbol(),p=c.endSymbol(),s=/^when(Minus)?(.+)$/;r(g,function(a,c){s.test(c)&&(l[x(c.replace("when","").replace("Minus","-"))]=
f.attr(g.$attr[c]))});r(l,function(a,e){n[e]=c(a.replace(d,q+h+"-"+m+p))});e.$watch(function(){var c=parseFloat(e.$eval(h));if(isNaN(c))return"";c in l||(c=a.pluralCat(c-m));return n[c](e,f,!0)},function(a){f.text(a)})}}}],xd=["$parse","$animate",function(a,c){var d=z("ngRepeat");return{transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,link:function(e,f,g,h,k){var m=g.ngRepeat,l=m.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),n,q,p,s,u,w,t={$id:Na};if(!l)throw d("iexp",
m);g=l[1];h=l[2];(l=l[3])?(n=a(l),q=function(a,c,d){w&&(t[w]=a);t[u]=c;t.$index=d;return n(e,t)}):(p=function(a,c){return Na(c)},s=function(a){return a});l=g.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!l)throw d("iidexp",g);u=l[3]||l[1];w=l[2];var y={};e.$watchCollection(h,function(a){var g,h,l=f[0],n,t={},D,C,I,x,G,v,z,F=[];if(Sa(a))v=a,G=q||p;else{G=q||s;v=[];for(I in a)a.hasOwnProperty(I)&&"$"!=I.charAt(0)&&v.push(I);v.sort()}D=v.length;h=F.length=v.length;for(g=0;g<h;g++)if(I=a===
v?g:v[g],x=a[I],n=G(I,x,g),Ea(n,"`track by` id"),y.hasOwnProperty(n))z=y[n],delete y[n],t[n]=z,F[g]=z;else{if(t.hasOwnProperty(n))throw r(F,function(a){a&&a.scope&&(y[a.id]=a)}),d("dupes",m,n,oa(x));F[g]={id:n};t[n]=!1}for(I in y)y.hasOwnProperty(I)&&(z=y[I],g=Eb(z.clone),c.leave(g),r(g,function(a){a.$$NG_REMOVED=!0}),z.scope.$destroy());g=0;for(h=v.length;g<h;g++){I=a===v?g:v[g];x=a[I];z=F[g];F[g-1]&&(l=F[g-1].clone[F[g-1].clone.length-1]);if(z.scope){C=z.scope;n=l;do n=n.nextSibling;while(n&&n.$$NG_REMOVED);
z.clone[0]!=n&&c.move(Eb(z.clone),null,A(l));l=z.clone[z.clone.length-1]}else C=e.$new();C[u]=x;w&&(C[w]=I);C.$index=g;C.$first=0===g;C.$last=g===D-1;C.$middle=!(C.$first||C.$last);C.$odd=!(C.$even=0===(g&1));z.scope||k(C,function(a){a[a.length++]=X.createComment(" end ngRepeat: "+m+" ");c.enter(a,null,A(l));l=a;z.scope=C;z.clone=a;t[z.id]=z})}y=t})}}}],yd=["$animate",function(a){return function(c,d,e){c.$watch(e.ngShow,function(c){a[Wa(c)?"removeClass":"addClass"](d,"ng-hide")})}}],rd=["$animate",
function(a){return function(c,d,e){c.$watch(e.ngHide,function(c){a[Wa(c)?"addClass":"removeClass"](d,"ng-hide")})}}],zd=Aa(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&r(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Ad=["$animate",function(a){return{restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,d,e,f){var g=[],h=[],k=[],m=[];c.$watch(e.ngSwitch||e.on,function(d){var n,q;n=0;for(q=k.length;n<q;++n)k[n].remove();n=k.length=0;for(q=
m.length;n<q;++n){var p=h[n];m[n].$destroy();k[n]=p;a.leave(p,function(){k.splice(n,1)})}h.length=0;m.length=0;if(g=f.cases["!"+d]||f.cases["?"])c.$eval(e.change),r(g,function(d){var e=c.$new();m.push(e);d.transclude(e,function(c){var e=d.element;h.push(c);a.enter(c,e.parent(),e)})})})}}}],Bd=Aa({transclude:"element",priority:800,require:"^ngSwitch",link:function(a,c,d,e,f){e.cases["!"+d.ngSwitchWhen]=e.cases["!"+d.ngSwitchWhen]||[];e.cases["!"+d.ngSwitchWhen].push({transclude:f,element:c})}}),Cd=
Aa({transclude:"element",priority:800,require:"^ngSwitch",link:function(a,c,d,e,f){e.cases["?"]=e.cases["?"]||[];e.cases["?"].push({transclude:f,element:c})}}),Ed=Aa({link:function(a,c,d,e,f){if(!f)throw z("ngTransclude")("orphan",ia(c));f(function(a){c.empty();c.append(a)})}}),ed=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],bf=z("ngOptions"),Dd=aa({terminal:!0}),fd=["$compile","$parse",function(a,c){var d=
/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,e={$setViewValue:v};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var k=this,m={},l=e,n;k.databound=d.ngModel;k.init=function(a,c,d){l=a;n=d};k.addOption=function(c){Ea(c,'"option value"');m[c]=!0;l.$viewValue==c&&(a.val(c),n.parent()&&n.remove())};
k.removeOption=function(a){this.hasOption(a)&&(delete m[a],l.$viewValue==a&&this.renderUnknownOption(a))};k.renderUnknownOption=function(c){c="? "+Na(c)+" ?";n.val(c);a.prepend(n);a.val(c);n.prop("selected",!0)};k.hasOption=function(a){return m.hasOwnProperty(a)};c.$on("$destroy",function(){k.renderUnknownOption=v})}],link:function(e,g,h,k){function m(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(x.parent()&&x.remove(),c.val(a),""===a&&w.prop("selected",!0)):F(a)&&w?c.val(""):e.renderUnknownOption(a)};
c.on("change",function(){a.$apply(function(){x.parent()&&x.remove();d.$setViewValue(c.val())})})}function l(a,c,d){var e;d.$render=function(){var a=new db(d.$viewValue);r(c.find("option"),function(c){c.selected=D(a.get(c.value))})};a.$watch(function(){Ca(e,d.$viewValue)||(e=ha(d.$viewValue),d.$render())});c.on("change",function(){a.$apply(function(){var a=[];r(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function n(e,f,g){function h(){var a={"":[]},c=[""],d,k,
s,u,v;s=g.$modelValue;u=A(e)||[];var F=n?Xb(u):u,G,Q,C;Q={};C=!1;if(p)if(k=g.$modelValue,w&&L(k))for(C=new db([]),d={},v=0;v<k.length;v++)d[m]=k[v],C.put(w(e,d),k[v]);else C=new db(k);v=C;var E,K;for(C=0;G=F.length,C<G;C++){k=C;if(n){k=F[C];if("$"===k.charAt(0))continue;Q[n]=k}Q[m]=u[k];d=r(e,Q)||"";(k=a[d])||(k=a[d]=[],c.push(d));p?d=D(v.remove(w?w(e,Q):x(e,Q))):(w?(d={},d[m]=s,d=w(e,d)===w(e,Q)):d=s===x(e,Q),v=v||d);E=l(e,Q);E=D(E)?E:"";k.push({id:w?w(e,Q):n?F[C]:C,label:E,selected:d})}p||(z||null===
s?a[""].unshift({id:"",label:"",selected:!v}):v||a[""].unshift({id:"?",label:"",selected:!0}));Q=0;for(F=c.length;Q<F;Q++){d=c[Q];k=a[d];B.length<=Q?(s={element:y.clone().attr("label",d),label:k.label},u=[s],B.push(u),f.append(s.element)):(u=B[Q],s=u[0],s.label!=d&&s.element.attr("label",s.label=d));E=null;C=0;for(G=k.length;C<G;C++)d=k[C],(v=u[C+1])?(E=v.element,v.label!==d.label&&(E.text(v.label=d.label),E.prop("label",v.label)),v.id!==d.id&&E.val(v.id=d.id),E[0].selected!==d.selected&&(E.prop("selected",
v.selected=d.selected),R&&E.prop("selected",v.selected))):(""===d.id&&z?K=z:(K=t.clone()).val(d.id).prop("selected",d.selected).attr("selected",d.selected).prop("label",d.label).text(d.label),u.push({element:K,label:d.label,id:d.id,selected:d.selected}),q.addOption(d.label,K),E?E.after(K):s.element.append(K),E=K);for(C++;u.length>C;)d=u.pop(),q.removeOption(d.label),d.element.remove()}for(;B.length>Q;)B.pop()[0].element.remove()}var k;if(!(k=s.match(d)))throw bf("iexp",s,ia(f));var l=c(k[2]||k[1]),
m=k[4]||k[6],n=k[5],r=c(k[3]||""),x=c(k[2]?k[1]:m),A=c(k[7]),w=k[8]?c(k[8]):null,B=[[{element:f,label:""}]];z&&(a(z)(e),z.removeClass("ng-scope"),z.remove());f.empty();f.on("change",function(){e.$apply(function(){var a,c=A(e)||[],d={},k,l,q,r,s,t,v;if(p)for(l=[],r=0,t=B.length;r<t;r++)for(a=B[r],q=1,s=a.length;q<s;q++){if((k=a[q].element)[0].selected){k=k.val();n&&(d[n]=k);if(w)for(v=0;v<c.length&&(d[m]=c[v],w(e,d)!=k);v++);else d[m]=c[k];l.push(x(e,d))}}else if(k=f.val(),"?"==k)l=u;else if(""===
k)l=null;else if(w)for(v=0;v<c.length;v++){if(d[m]=c[v],w(e,d)==k){l=x(e,d);break}}else d[m]=c[k],n&&(d[n]=k),l=x(e,d);g.$setViewValue(l);h()})});g.$render=h;e.$watchCollection(A,h);e.$watchCollection(function(){var a={},c=A(e);if(c){for(var d=Array(c.length),f=0,g=c.length;f<g;f++)a[m]=c[f],d[f]=l(e,a);return d}},h);p&&e.$watchCollection(function(){return g.$modelValue},h)}if(k[1]){var q=k[0];k=k[1];var p=h.multiple,s=h.ngOptions,z=!1,w,t=A(X.createElement("option")),y=A(X.createElement("optgroup")),
x=t.clone();h=0;for(var B=g.children(),v=B.length;h<v;h++)if(""===B[h].value){w=z=B.eq(h);break}q.init(k,z,x);p&&(k.$isEmpty=function(a){return!a||0===a.length});s?n(e,g,k):p?l(e,g,k):m(e,g,k,q)}}}}],hd=["$interpolate",function(a){var c={addOption:v,removeOption:v};return{restrict:"E",priority:100,compile:function(d,e){if(F(e.value)){var f=a(d.text(),!0);f||e.$set("value",d.text())}return function(a,d,e){var m=d.parent(),l=m.data("$selectController")||m.parent().data("$selectController");l&&l.databound?
d.prop("selected",!1):l=c;f?a.$watch(f,function(a,c){e.$set("value",a);a!==c&&l.removeOption(c);l.addOption(a)}):l.addOption(e.value);d.on("$destroy",function(){l.removeOption(e.value)})}}}}],gd=aa({restrict:"E",terminal:!0});W.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):((Fa=W.jQuery)&&Fa.fn.on?(A=Fa,E(Fa.fn,{scope:Oa.scope,isolateScope:Oa.isolateScope,controller:Oa.controller,injector:Oa.injector,inheritedData:Oa.inheritedData}),Gb("remove",!0,!0,!1),Gb("empty",
!1,!1,!1),Gb("html",!1,!1,!0)):A=S,Xa.element=A,Zc(Xa),A(X).ready(function(){Wc(X,dc)}))})(window,document);!window.angular.$$csp()&&window.angular.element(document).find("head").prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}.ng-animate-block-transitions{transition:0s all!important;-webkit-transition:0s all!important;}.ng-hide-add-active,.ng-hide-remove{display:block!important;}</style>');
//# sourceMappingURL=angular.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,137 @@
/**
* @license
* lodash 3.8.0 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
* Build: `lodash modern -o ./lodash.js`
*/
;(function(){function n(n,t){if(n!==t){var r=n===n,e=t===t;if(n>t||!r||n===w&&e)return 1;if(n<t||!e||t===w&&r)return-1}return 0}function t(n,t,r){for(var e=n.length,u=r?e:-1;r?u--:++u<e;)if(t(n[u],u,n))return u;return-1}function r(n,t,r){if(t!==t)return p(n,r);r-=1;for(var e=n.length;++r<e;)if(n[r]===t)return r;return-1}function e(n){return typeof n=="function"||false}function u(n){return typeof n=="string"?n:null==n?"":n+""}function o(n){return n.charCodeAt(0)}function i(n,t){for(var r=-1,e=n.length;++r<e&&-1<t.indexOf(n.charAt(r)););
return r}function f(n,t){for(var r=n.length;r--&&-1<t.indexOf(n.charAt(r)););return r}function a(t,r){return n(t.a,r.a)||t.b-r.b}function c(n){return $n[n]}function l(n){return Ln[n]}function s(n){return"\\"+Mn[n]}function p(n,t,r){var e=n.length;for(t+=r?0:-1;r?t--:++t<e;){var u=n[t];if(u!==u)return t}return-1}function h(n){return!!n&&typeof n=="object"}function _(n){return 160>=n&&9<=n&&13>=n||32==n||160==n||5760==n||6158==n||8192<=n&&(8202>=n||8232==n||8233==n||8239==n||8287==n||12288==n||65279==n);
}function v(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;)n[r]===t&&(n[r]=z,o[++u]=r);return o}function g(n){for(var t=-1,r=n.length;++t<r&&_(n.charCodeAt(t)););return t}function y(n){for(var t=n.length;t--&&_(n.charCodeAt(t)););return t}function d(n){return zn[n]}function m(_){function $n(n){if(h(n)&&!(To(n)||n instanceof Bn)){if(n instanceof zn)return n;if(Ge.call(n,"__chain__")&&Ge.call(n,"__wrapped__"))return Lr(n)}return new zn(n)}function Ln(){}function zn(n,t,r){this.__wrapped__=n,this.__actions__=r||[],
this.__chain__=!!t}function Bn(n){this.__wrapped__=n,this.__actions__=null,this.__dir__=1,this.__filtered__=false,this.__iteratees__=null,this.__takeCount__=Iu,this.__views__=null}function Mn(){this.__data__={}}function Dn(n){var t=n?n.length:0;for(this.data={hash:du(null),set:new lu};t--;)this.push(n[t])}function Pn(n,t){var r=n.data;return(typeof t=="string"||se(t)?r.set.has(t):r.hash[t])?0:-1}function qn(n,t){var r=-1,e=n.length;for(t||(t=Ue(e));++r<e;)t[r]=n[r];return t}function Kn(n,t){for(var r=-1,e=n.length;++r<e&&false!==t(n[r],r,n););
return n}function Vn(n,t){for(var r=-1,e=n.length;++r<e;)if(!t(n[r],r,n))return false;return true}function Gn(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;){var i=n[r];t(i,r,n)&&(o[++u]=i)}return o}function Jn(n,t){for(var r=-1,e=n.length,u=Ue(e);++r<e;)u[r]=t(n[r],r,n);return u}function Xn(n,t,r,e){var u=-1,o=n.length;for(e&&o&&(r=n[++u]);++u<o;)r=t(r,n[u],u,n);return r}function Hn(n,t){for(var r=-1,e=n.length;++r<e;)if(t(n[r],r,n))return true;return false}function Qn(n,t){return n===w?t:n}function nt(n,t,r,e){
return n!==w&&Ge.call(e,r)?n:t}function tt(n,t,r){var e=Ko(t);fu.apply(e,Zu(t));for(var u=-1,o=e.length;++u<o;){var i=e[u],f=n[i],a=r(f,t[i],i,n,t);(a===a?a===f:f!==f)&&(f!==w||i in n)||(n[i]=a)}return n}function rt(n,t){for(var r=-1,e=null==n,u=!e&&jr(n),o=u&&n.length,i=t.length,f=Ue(i);++r<i;){var a=t[r];f[r]=u?kr(a,o)?n[a]:w:e?w:n[a]}return f}function et(n,t,r){r||(r={});for(var e=-1,u=t.length;++e<u;){var o=t[e];r[o]=n[o]}return r}function ut(n,t,r){var e=typeof n;return"function"==e?t===w?n:zt(n,t,r):null==n?Re:"object"==e?wt(n):t===w?Te(n):bt(n,t);
}function ot(n,t,r,e,u,o,i){var f;if(r&&(f=u?r(n,e,u):r(n)),f!==w)return f;if(!se(n))return n;if(e=To(n)){if(f=wr(n),!t)return qn(n,f)}else{var a=Xe.call(n),c=a==K;if(a!=Y&&a!=B&&(!c||u))return Nn[a]?xr(n,a,t):u?n:{};if(f=br(c?{}:n),!t)return $u(f,n)}for(o||(o=[]),i||(i=[]),u=o.length;u--;)if(o[u]==n)return i[u];return o.push(n),i.push(f),(e?Kn:ht)(n,function(e,u){f[u]=ot(e,t,r,u,n,o,i)}),f}function it(n,t,r){if(typeof n!="function")throw new Pe(L);return su(function(){n.apply(w,r)},t)}function ft(n,t){
var e=n?n.length:0,u=[];if(!e)return u;var o=-1,i=mr(),f=i==r,a=f&&200<=t.length?qu(t):null,c=t.length;a&&(i=Pn,f=false,t=a);n:for(;++o<e;)if(a=n[o],f&&a===a){for(var l=c;l--;)if(t[l]===a)continue n;u.push(a)}else 0>i(t,a,0)&&u.push(a);return u}function at(n,t){var r=true;return zu(n,function(n,e,u){return r=!!t(n,e,u)}),r}function ct(n,t){var r=[];return zu(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function lt(n,t,r,e){var u;return r(n,function(n,r,o){return t(n,r,o)?(u=e?r:n,false):void 0}),u}function st(n,t,r){
for(var e=-1,u=n.length,o=-1,i=[];++e<u;){var f=n[e];if(h(f)&&jr(f)&&(r||To(f)||ae(f))){t&&(f=st(f,t,r));for(var a=-1,c=f.length;++a<c;)i[++o]=f[a]}else r||(i[++o]=f)}return i}function pt(n,t){Mu(n,t,me)}function ht(n,t){return Mu(n,t,Ko)}function _t(n,t){return Du(n,t,Ko)}function vt(n,t){for(var r=-1,e=t.length,u=-1,o=[];++r<e;){var i=t[r];No(n[i])&&(o[++u]=i)}return o}function gt(n,t,r){if(null!=n){r!==w&&r in Fr(n)&&(t=[r]),r=-1;for(var e=t.length;null!=n&&++r<e;)n=n[t[r]];return r&&r==e?n:w}
}function yt(n,t,r,e,u,o){if(n===t)return true;var i=typeof n,f=typeof t;if("function"!=i&&"object"!=i&&"function"!=f&&"object"!=f||null==n||null==t)n=n!==n&&t!==t;else n:{var i=yt,f=To(n),a=To(t),c=M,l=M;f||(c=Xe.call(n),c==B?c=Y:c!=Y&&(f=ge(n))),a||(l=Xe.call(t),l==B?l=Y:l!=Y&&ge(t));var s=c==Y,a=l==Y,l=c==l;if(!l||f||s){if(!e&&(c=s&&Ge.call(n,"__wrapped__"),a=a&&Ge.call(t,"__wrapped__"),c||a)){n=i(c?n.value():n,a?t.value():t,r,e,u,o);break n}if(l){for(u||(u=[]),o||(o=[]),c=u.length;c--;)if(u[c]==n){
n=o[c]==t;break n}u.push(n),o.push(t),n=(f?_r:gr)(n,t,i,r,e,u,o),u.pop(),o.pop()}else n=false}else n=vr(n,t,c)}return n}function dt(n,t,r,e,u){for(var o=-1,i=t.length,f=!u;++o<i;)if(f&&e[o]?r[o]!==n[t[o]]:!(t[o]in n))return false;for(o=-1;++o<i;){var a=t[o],c=n[a],l=r[o];if(f&&e[o]?a=c!==w||a in n:(a=u?u(c,l,a):w,a===w&&(a=yt(l,c,u,true))),!a)return false}return true}function mt(n,t){var r=-1,e=jr(n)?Ue(n.length):[];return zu(n,function(n,u,o){e[++r]=t(n,u,o)}),e}function wt(n){var t=Ko(n),r=t.length;if(!r)return Ie(true);
if(1==r){var e=t[0],u=n[e];if(Cr(u))return function(n){return null==n?false:n[e]===u&&(u!==w||e in Fr(n))}}for(var o=Ue(r),i=Ue(r);r--;)u=n[t[r]],o[r]=u,i[r]=Cr(u);return function(n){return null!=n&&dt(Fr(n),t,o,i)}}function bt(n,t){var r=To(n),e=Er(n)&&Cr(t),u=n+"";return n=$r(n),function(o){if(null==o)return false;var i=u;if(o=Fr(o),!(!r&&e||i in o)){if(o=1==n.length?o:gt(o,It(n,0,-1)),null==o)return false;i=Pr(n),o=Fr(o)}return o[i]===t?t!==w||i in o:yt(t,o[i],null,true)}}function xt(n,t,r,e,u){if(!se(n))return n;
var o=jr(t)&&(To(t)||ge(t));if(!o){var i=Ko(t);fu.apply(i,Zu(t))}return Kn(i||t,function(f,a){if(i&&(a=f,f=t[a]),h(f)){e||(e=[]),u||(u=[]);n:{for(var c=a,l=e,s=u,p=l.length,_=t[c];p--;)if(l[p]==_){n[c]=s[p];break n}var p=n[c],v=r?r(p,_,c,n,t):w,g=v===w;g&&(v=_,jr(_)&&(To(_)||ge(_))?v=To(p)?p:jr(p)?qn(p):[]:Fo(_)||ae(_)?v=ae(p)?ye(p):Fo(p)?p:{}:g=false),l.push(_),s.push(v),g?n[c]=xt(v,_,r,l,s):(v===v?v!==p:p===p)&&(n[c]=v)}}else c=n[a],l=r?r(c,f,a,n,t):w,(s=l===w)&&(l=f),!o&&l===w||!s&&(l===l?l===c:c!==c)||(n[a]=l);
}),n}function At(n){return function(t){return null==t?w:t[n]}}function jt(n){var t=n+"";return n=$r(n),function(r){return gt(r,n,t)}}function kt(n,t){for(var r=n?t.length:0;r--;){var e=parseFloat(t[r]);if(e!=u&&kr(e)){var u=e;pu.call(n,e,1)}}}function Ot(n,t){return n+uu(Ou()*(t-n+1))}function Et(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function It(n,t,r){var e=-1,u=n.length;for(t=null==t?0:+t||0,0>t&&(t=-t>u?0:u+t),r=r===w||r>u?u:+r||0,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,
r=Ue(u);++e<u;)r[e]=n[e+t];return r}function Rt(n,t){var r;return zu(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function Ct(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].c;return n}function Wt(t,r,e){var u=dr(),o=-1;return r=Jn(r,function(n){return u(n)}),t=mt(t,function(n){return{a:Jn(r,function(t){return t(n)}),b:++o,c:n}}),Ct(t,function(t,r){var u;n:{u=-1;for(var o=t.a,i=r.a,f=o.length,a=e.length;++u<f;){var c=n(o[u],i[u]);if(c){u=u<a?c*(e[u]?1:-1):c;break n}}u=t.b-r.b}return u})}function St(n,t){
var r=0;return zu(n,function(n,e,u){r+=+t(n,e,u)||0}),r}function Tt(n,t){var e=-1,u=mr(),o=n.length,i=u==r,f=i&&200<=o,a=f?qu():null,c=[];a?(u=Pn,i=false):(f=false,a=t?[]:c);n:for(;++e<o;){var l=n[e],s=t?t(l,e,n):l;if(i&&l===l){for(var p=a.length;p--;)if(a[p]===s)continue n;t&&a.push(s),c.push(l)}else 0>u(a,s,0)&&((t||f)&&a.push(s),c.push(l))}return c}function Ut(n,t){for(var r=-1,e=t.length,u=Ue(e);++r<e;)u[r]=n[t[r]];return u}function Nt(n,t,r,e){for(var u=n.length,o=e?u:-1;(e?o--:++o<u)&&t(n[o],o,n););
return r?It(n,e?0:o,e?o+1:u):It(n,e?o+1:0,e?u:o)}function Ft(n,t){var r=n;r instanceof Bn&&(r=r.value());for(var e=-1,u=t.length;++e<u;){var r=[r],o=t[e];fu.apply(r,o.args),r=o.func.apply(o.thisArg,r)}return r}function $t(n,t,r){var e=0,u=n?n.length:e;if(typeof t=="number"&&t===t&&u<=Wu){for(;e<u;){var o=e+u>>>1,i=n[o];(r?i<=t:i<t)?e=o+1:u=o}return u}return Lt(n,t,Re,r)}function Lt(n,t,r,e){t=r(t);for(var u=0,o=n?n.length:0,i=t!==t,f=t===w;u<o;){var a=uu((u+o)/2),c=r(n[a]),l=c===c;(i?l||e:f?l&&(e||c!==w):e?c<=t:c<t)?u=a+1:o=a;
}return xu(o,Cu)}function zt(n,t,r){if(typeof n!="function")return Re;if(t===w)return n;switch(r){case 1:return function(r){return n.call(t,r)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,o){return n.call(t,r,e,u,o)};case 5:return function(r,e,u,o,i){return n.call(t,r,e,u,o,i)}}return function(){return n.apply(t,arguments)}}function Bt(n){return tu.call(n,0)}function Mt(n,t,r){for(var e=r.length,u=-1,o=bu(n.length-e,0),i=-1,f=t.length,a=Ue(o+f);++i<f;)a[i]=t[i];
for(;++u<e;)a[r[u]]=n[u];for(;o--;)a[i++]=n[u++];return a}function Dt(n,t,r){for(var e=-1,u=r.length,o=-1,i=bu(n.length-u,0),f=-1,a=t.length,c=Ue(i+a);++o<i;)c[o]=n[o];for(i=o;++f<a;)c[i+f]=t[f];for(;++e<u;)c[i+r[e]]=n[o++];return c}function Pt(n,t){return function(r,e,u){var o=t?t():{};if(e=dr(e,u,3),To(r)){u=-1;for(var i=r.length;++u<i;){var f=r[u];n(o,f,e(f,u,r),r)}}else zu(r,function(t,r,u){n(o,t,e(t,r,u),u)});return o}}function qt(n){return fe(function(t,r){var e=-1,u=null==t?0:r.length,o=2<u&&r[u-2],i=2<u&&r[2],f=1<u&&r[u-1];
for(typeof o=="function"?(o=zt(o,f,5),u-=2):(o=typeof f=="function"?f:null,u-=o?1:0),i&&Or(r[0],r[1],i)&&(o=3>u?null:o,u=1);++e<u;)(i=r[e])&&n(t,i,o);return t})}function Kt(n,t){return function(r,e){var u=r?Yu(r):0;if(!Rr(u))return n(r,e);for(var o=t?u:-1,i=Fr(r);(t?o--:++o<u)&&false!==e(i[o],o,i););return r}}function Vt(n){return function(t,r,e){var u=Fr(t);e=e(t);for(var o=e.length,i=n?o:-1;n?i--:++i<o;){var f=e[i];if(false===r(u[f],f,u))break}return t}}function Yt(n,t){function r(){return(this&&this!==Yn&&this instanceof r?e:n).apply(t,arguments);
}var e=Gt(n);return r}function Zt(n){return function(t){var r=-1;t=Oe(be(t));for(var e=t.length,u="";++r<e;)u=n(u,t[r],r);return u}}function Gt(n){return function(){var t=Lu(n.prototype),r=n.apply(t,arguments);return se(r)?r:t}}function Jt(n){function t(r,e,u){return u&&Or(r,e,u)&&(e=null),r=hr(r,n,null,null,null,null,null,e),r.placeholder=t.placeholder,r}return t}function Xt(n,t){return function(r,e,u){u&&Or(r,e,u)&&(e=null);var i=dr(),f=null==e;if(i===ut&&f||(f=false,e=i(e,u,3)),f){if(e=To(r),e||!ve(r))return n(e?r:Nr(r));
e=o}return yr(r,e,t)}}function Ht(n,r){return function(e,u,o){return u=dr(u,o,3),To(e)?(u=t(e,u,r),-1<u?e[u]:w):lt(e,u,n)}}function Qt(n){return function(r,e,u){return r&&r.length?(e=dr(e,u,3),t(r,e,n)):-1}}function nr(n){return function(t,r,e){return r=dr(r,e,3),lt(t,r,n,true)}}function tr(n){return function(){var t=arguments.length;if(!t)return function(){return arguments[0]};for(var r,e=n?t:-1,u=0,o=Ue(t);n?e--:++e<t;){var i=o[u++]=arguments[e];if(typeof i!="function")throw new Pe(L);var f=r?"":Vu(i);
r="wrapper"==f?new zn([]):r}for(e=r?-1:t;++e<t;)i=o[e],f=Vu(i),r=(u="wrapper"==f?Ku(i):null)&&Ir(u[0])&&u[1]==(R|k|E|C)&&!u[4].length&&1==u[9]?r[Vu(u[0])].apply(r,u[3]):1==i.length&&Ir(i)?r[f]():r.thru(i);return function(){var n=arguments;if(r&&1==n.length&&To(n[0]))return r.plant(n[0]).value();for(var e=0,n=o[e].apply(this,n);++e<t;)n=o[e].call(this,n);return n}}}function rr(n,t){return function(r,e,u){return typeof e=="function"&&u===w&&To(r)?n(r,e):t(r,zt(e,u,3))}}function er(n){return function(t,r,e){
return(typeof r!="function"||e!==w)&&(r=zt(r,e,3)),n(t,r,me)}}function ur(n){return function(t,r,e){return(typeof r!="function"||e!==w)&&(r=zt(r,e,3)),n(t,r)}}function or(n){return function(t,r,e){var u={};return r=dr(r,e,3),ht(t,function(t,e,o){o=r(t,e,o),e=n?o:e,t=n?t:o,u[e]=t}),u}}function ir(n){return function(t,r,e){return t=u(t),(n?t:"")+lr(t,r,e)+(n?"":t)}}function fr(n){var t=fe(function(r,e){var u=v(e,t.placeholder);return hr(r,n,null,e,u)});return t}function ar(n,t){return function(r,e,u,o){
var i=3>arguments.length;return typeof e=="function"&&o===w&&To(r)?n(r,e,u,i):Et(r,dr(e,o,4),u,i,t)}}function cr(n,t,r,e,u,o,i,f,a,c){function l(){for(var b=arguments.length,j=b,k=Ue(b);j--;)k[j]=arguments[j];if(e&&(k=Mt(k,e,u)),o&&(k=Dt(k,o,i)),_||y){var j=l.placeholder,O=v(k,j),b=b-O.length;if(b<c){var R=f?qn(f):null,b=bu(c-b,0),C=_?O:null,O=_?null:O,W=_?k:null,k=_?null:k;return t|=_?E:I,t&=~(_?I:E),g||(t&=~(x|A)),k=[n,t,r,W,C,k,O,R,a,b],R=cr.apply(w,k),Ir(n)&&Gu(R,k),R.placeholder=j,R}}if(j=p?r:this,
h&&(n=j[m]),f)for(R=k.length,b=xu(f.length,R),C=qn(k);b--;)O=f[b],k[b]=kr(O,R)?C[O]:w;return s&&a<k.length&&(k.length=a),(this&&this!==Yn&&this instanceof l?d||Gt(n):n).apply(j,k)}var s=t&R,p=t&x,h=t&A,_=t&k,g=t&j,y=t&O,d=!h&&Gt(n),m=n;return l}function lr(n,t,r){return n=n.length,t=+t,n<t&&mu(t)?(t-=n,r=null==r?" ":r+"",je(r,ru(t/r.length)).slice(0,t)):""}function sr(n,t,r,e){function u(){for(var t=-1,f=arguments.length,a=-1,c=e.length,l=Ue(f+c);++a<c;)l[a]=e[a];for(;f--;)l[a++]=arguments[++t];return(this&&this!==Yn&&this instanceof u?i:n).apply(o?r:this,l);
}var o=t&x,i=Gt(n);return u}function pr(n){return function(t,r,e,u){var o=dr(e);return o===ut&&null==e?$t(t,r,n):Lt(t,r,o(e,u,1),n)}}function hr(n,t,r,e,u,o,i,f){var a=t&A;if(!a&&typeof n!="function")throw new Pe(L);var c=e?e.length:0;if(c||(t&=~(E|I),e=u=null),c-=u?u.length:0,t&I){var l=e,s=u;e=u=null}var p=a?null:Ku(n);return r=[n,t,r,e,u,l,s,o,i,f],p&&(e=r[1],t=p[1],f=e|t,u=t==R&&e==k||t==R&&e==C&&r[7].length<=p[8]||t==(R|C)&&e==k,(f<R||u)&&(t&x&&(r[2]=p[2],f|=e&x?0:j),(e=p[3])&&(u=r[3],r[3]=u?Mt(u,e,p[4]):qn(e),
r[4]=u?v(r[3],z):qn(p[4])),(e=p[5])&&(u=r[5],r[5]=u?Dt(u,e,p[6]):qn(e),r[6]=u?v(r[5],z):qn(p[6])),(e=p[7])&&(r[7]=qn(e)),t&R&&(r[8]=null==r[8]?p[8]:xu(r[8],p[8])),null==r[9]&&(r[9]=p[9]),r[0]=p[0],r[1]=f),t=r[1],f=r[9]),r[9]=null==f?a?0:n.length:bu(f-c,0)||0,(p?Pu:Gu)(t==x?Yt(r[0],r[2]):t!=E&&t!=(x|E)||r[4].length?cr.apply(w,r):sr.apply(w,r),r)}function _r(n,t,r,e,u,o,i){var f=-1,a=n.length,c=t.length,l=true;if(a!=c&&(!u||c<=a))return false;for(;l&&++f<a;){var s=n[f],p=t[f],l=w;if(e&&(l=u?e(p,s,f):e(s,p,f)),
l===w)if(u)for(var h=c;h--&&(p=t[h],!(l=s&&s===p||r(s,p,e,u,o,i))););else l=s&&s===p||r(s,p,e,u,o,i)}return!!l}function vr(n,t,r){switch(r){case D:case P:return+n==+t;case q:return n.name==t.name&&n.message==t.message;case V:return n!=+n?t!=+t:n==+t;case Z:case G:return n==t+""}return false}function gr(n,t,r,e,u,o,i){var f=Ko(n),a=f.length,c=Ko(t).length;if(a!=c&&!u)return false;for(var c=u,l=-1;++l<a;){var s=f[l],p=u?s in t:Ge.call(t,s);if(p){var h=n[s],_=t[s],p=w;e&&(p=u?e(_,h,s):e(h,_,s)),p===w&&(p=h&&h===_||r(h,_,e,u,o,i));
}if(!p)return false;c||(c="constructor"==s)}return c||(r=n.constructor,e=t.constructor,!(r!=e&&"constructor"in n&&"constructor"in t)||typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)?true:false}function yr(n,t,r){var e=r?Iu:Eu,u=e,o=u;return zu(n,function(n,i,f){i=t(n,i,f),((r?i<u:i>u)||i===e&&i===o)&&(u=i,o=n)}),o}function dr(n,t,r){var e=$n.callback||Ee,e=e===Ee?ut:e;return r?e(n,t,r):e}function mr(n,t,e){var u=$n.indexOf||Dr,u=u===Dr?r:u;return n?u(n,t,e):u}function wr(n){var t=n.length,r=new n.constructor(t);
return t&&"string"==typeof n[0]&&Ge.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function br(n){return n=n.constructor,typeof n=="function"&&n instanceof n||(n=Be),new n}function xr(n,t,r){var e=n.constructor;switch(t){case J:return Bt(n);case D:case P:return new e(+n);case X:case H:case Q:case nn:case tn:case rn:case en:case un:case on:return t=n.buffer,new e(r?Bt(t):t,n.byteOffset,n.length);case V:case G:return new e(n);case Z:var u=new e(n.source,kn.exec(n));u.lastIndex=n.lastIndex}return u;
}function Ar(n,t,r){return null==n||Er(t,n)||(t=$r(t),n=1==t.length?n:gt(n,It(t,0,-1)),t=Pr(t)),t=null==n?n:n[t],null==t?w:t.apply(n,r)}function jr(n){return null!=n&&Rr(Yu(n))}function kr(n,t){return n=+n,t=null==t?Tu:t,-1<n&&0==n%1&&n<t}function Or(n,t,r){if(!se(r))return false;var e=typeof t;return("number"==e?jr(r)&&kr(t,r.length):"string"==e&&t in r)?(t=r[t],n===n?n===t:t!==t):false}function Er(n,t){var r=typeof n;return"string"==r&&dn.test(n)||"number"==r?true:To(n)?false:!yn.test(n)||null!=t&&n in Fr(t);
}function Ir(n){var t=Vu(n);return!!t&&n===$n[t]&&t in Bn.prototype}function Rr(n){return typeof n=="number"&&-1<n&&0==n%1&&n<=Tu}function Cr(n){return n===n&&!se(n)}function Wr(n,t){n=Fr(n);for(var r=-1,e=t.length,u={};++r<e;){var o=t[r];o in n&&(u[o]=n[o])}return u}function Sr(n,t){var r={};return pt(n,function(n,e,u){t(n,e,u)&&(r[e]=n)}),r}function Tr(n){var t;if(!h(n)||Xe.call(n)!=Y||!(Ge.call(n,"constructor")||(t=n.constructor,typeof t!="function"||t instanceof t)))return false;var r;return pt(n,function(n,t){
r=t}),r===w||Ge.call(n,r)}function Ur(n){for(var t=me(n),r=t.length,e=r&&n.length,u=$n.support,u=e&&Rr(e)&&(To(n)||u.nonEnumArgs&&ae(n)),o=-1,i=[];++o<r;){var f=t[o];(u&&kr(f,e)||Ge.call(n,f))&&i.push(f)}return i}function Nr(n){return null==n?[]:jr(n)?se(n)?n:Be(n):we(n)}function Fr(n){return se(n)?n:Be(n)}function $r(n){if(To(n))return n;var t=[];return u(n).replace(mn,function(n,r,e,u){t.push(e?u.replace(An,"$1"):r||n)}),t}function Lr(n){return n instanceof Bn?n.clone():new zn(n.__wrapped__,n.__chain__,qn(n.__actions__));
}function zr(n,t,r){return n&&n.length?((r?Or(n,t,r):null==t)&&(t=1),It(n,0>t?0:t)):[]}function Br(n,t,r){var e=n?n.length:0;return e?((r?Or(n,t,r):null==t)&&(t=1),t=e-(+t||0),It(n,0,0>t?0:t)):[]}function Mr(n){return n?n[0]:w}function Dr(n,t,e){var u=n?n.length:0;if(!u)return-1;if(typeof e=="number")e=0>e?bu(u+e,0):e;else if(e)return e=$t(n,t),n=n[e],(t===t?t===n:n!==n)?e:-1;return r(n,t,e||0)}function Pr(n){var t=n?n.length:0;return t?n[t-1]:w}function qr(n){return zr(n,1)}function Kr(n,t,e,u){
if(!n||!n.length)return[];null!=t&&typeof t!="boolean"&&(u=e,e=Or(n,t,u)?null:t,t=false);var o=dr();if((o!==ut||null!=e)&&(e=o(e,u,3)),t&&mr()==r){t=e;var i;e=-1,u=n.length;for(var o=-1,f=[];++e<u;){var a=n[e],c=t?t(a,e,n):a;e&&i===c||(i=c,f[++o]=a)}n=f}else n=Tt(n,e);return n}function Vr(n){if(!n||!n.length)return[];var t=-1,r=0;n=Gn(n,function(n){return jr(n)?(r=bu(n.length,r),true):void 0});for(var e=Ue(r);++t<r;)e[t]=Jn(n,At(t));return e}function Yr(n,t,r){return n&&n.length?(n=Vr(n),null==t?n:(t=zt(t,r,4),
Jn(n,function(n){return Xn(n,t,w,true)}))):[]}function Zr(n,t){var r=-1,e=n?n.length:0,u={};for(!e||t||To(n[0])||(t=[]);++r<e;){var o=n[r];t?u[o]=t[r]:o&&(u[o[0]]=o[1])}return u}function Gr(n){return n=$n(n),n.__chain__=true,n}function Jr(n,t,r){return t.call(r,n)}function Xr(n,t,r){var e=To(n)?Vn:at;return r&&Or(n,t,r)&&(t=null),(typeof t!="function"||r!==w)&&(t=dr(t,r,3)),e(n,t)}function Hr(n,t,r){var e=To(n)?Gn:ct;return t=dr(t,r,3),e(n,t)}function Qr(n,t,r,e){var u=n?Yu(n):0;return Rr(u)||(n=we(n),
u=n.length),u?(r=typeof r!="number"||e&&Or(t,r,e)?0:0>r?bu(u+r,0):r||0,typeof n=="string"||!To(n)&&ve(n)?r<u&&-1<n.indexOf(t,r):-1<mr(n,t,r)):false}function ne(n,t,r){var e=To(n)?Jn:mt;return t=dr(t,r,3),e(n,t)}function te(n,t,r){return(r?Or(n,t,r):null==t)?(n=Nr(n),t=n.length,0<t?n[Ot(0,t-1)]:w):(n=re(n),n.length=xu(0>t?0:+t||0,n.length),n)}function re(n){n=Nr(n);for(var t=-1,r=n.length,e=Ue(r);++t<r;){var u=Ot(0,t);t!=u&&(e[t]=e[u]),e[u]=n[t]}return e}function ee(n,t,r){var e=To(n)?Hn:Rt;return r&&Or(n,t,r)&&(t=null),
(typeof t!="function"||r!==w)&&(t=dr(t,r,3)),e(n,t)}function ue(n,t){var r;if(typeof t!="function"){if(typeof n!="function")throw new Pe(L);var e=n;n=t,t=e}return function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}}function oe(n,t,r){function e(){var r=t-(wo()-c);0>=r||r>t?(f&&eu(f),r=p,f=s=p=w,r&&(h=wo(),a=n.apply(l,i),s||f||(i=l=null))):s=su(e,r)}function u(){s&&eu(s),f=s=p=w,(v||_!==t)&&(h=wo(),a=n.apply(l,i),s||f||(i=l=null))}function o(){if(i=arguments,c=wo(),l=this,p=v&&(s||!g),
!1===_)var r=g&&!s;else{f||g||(h=c);var o=_-(c-h),y=0>=o||o>_;y?(f&&(f=eu(f)),h=c,a=n.apply(l,i)):f||(f=su(u,o))}return y&&s?s=eu(s):s||t===_||(s=su(e,t)),r&&(y=true,a=n.apply(l,i)),!y||s||f||(i=l=null),a}var i,f,a,c,l,s,p,h=0,_=false,v=true;if(typeof n!="function")throw new Pe(L);if(t=0>t?0:+t||0,true===r)var g=true,v=false;else se(r)&&(g=r.leading,_="maxWait"in r&&bu(+r.maxWait||0,t),v="trailing"in r?r.trailing:v);return o.cancel=function(){s&&eu(s),f&&eu(f),f=s=p=w},o}function ie(n,t){function r(){var e=arguments,u=r.cache,o=t?t.apply(this,e):e[0];
return u.has(o)?u.get(o):(e=n.apply(this,e),u.set(o,e),e)}if(typeof n!="function"||t&&typeof t!="function")throw new Pe(L);return r.cache=new ie.Cache,r}function fe(n,t){if(typeof n!="function")throw new Pe(L);return t=bu(t===w?n.length-1:+t||0,0),function(){for(var r=arguments,e=-1,u=bu(r.length-t,0),o=Ue(u);++e<u;)o[e]=r[t+e];switch(t){case 0:return n.call(this,o);case 1:return n.call(this,r[0],o);case 2:return n.call(this,r[0],r[1],o)}for(u=Ue(t+1),e=-1;++e<t;)u[e]=r[e];return u[t]=o,n.apply(this,u);
}}function ae(n){return h(n)&&jr(n)&&Xe.call(n)==B}function ce(n){return!!n&&1===n.nodeType&&h(n)&&-1<Xe.call(n).indexOf("Element")}function le(n){return h(n)&&typeof n.message=="string"&&Xe.call(n)==q}function se(n){var t=typeof n;return"function"==t||!!n&&"object"==t}function pe(n){return null==n?false:Xe.call(n)==K?Qe.test(Ze.call(n)):h(n)&&En.test(n)}function he(n){return typeof n=="number"||h(n)&&Xe.call(n)==V}function _e(n){return h(n)&&Xe.call(n)==Z}function ve(n){return typeof n=="string"||h(n)&&Xe.call(n)==G;
}function ge(n){return h(n)&&Rr(n.length)&&!!Un[Xe.call(n)]}function ye(n){return et(n,me(n))}function de(n){return vt(n,me(n))}function me(n){if(null==n)return[];se(n)||(n=Be(n));for(var t=n.length,t=t&&Rr(t)&&(To(n)||Fu.nonEnumArgs&&ae(n))&&t||0,r=n.constructor,e=-1,r=typeof r=="function"&&r.prototype===n,u=Ue(t),o=0<t;++e<t;)u[e]=e+"";for(var i in n)o&&kr(i,t)||"constructor"==i&&(r||!Ge.call(n,i))||u.push(i);return u}function we(n){return Ut(n,Ko(n))}function be(n){return(n=u(n))&&n.replace(In,c).replace(xn,"");
}function xe(n){return(n=u(n))&&bn.test(n)?n.replace(wn,"\\$&"):n}function Ae(n,t,r){return r&&Or(n,t,r)&&(t=0),ku(n,t)}function je(n,t){var r="";if(n=u(n),t=+t,1>t||!n||!mu(t))return r;do t%2&&(r+=n),t=uu(t/2),n+=n;while(t);return r}function ke(n,t,r){var e=n;return(n=u(n))?(r?Or(e,t,r):null==t)?n.slice(g(n),y(n)+1):(t+="",n.slice(i(n,t),f(n,t)+1)):n}function Oe(n,t,r){return r&&Or(n,t,r)&&(t=null),n=u(n),n.match(t||Wn)||[]}function Ee(n,t,r){return r&&Or(n,t,r)&&(t=null),h(n)?Ce(n):ut(n,t)}function Ie(n){
return function(){return n}}function Re(n){return n}function Ce(n){return wt(ot(n,true))}function We(n,t,r){if(null==r){var e=se(t),u=e&&Ko(t);((u=u&&u.length&&vt(t,u))?u.length:e)||(u=false,r=t,t=n,n=this)}u||(u=vt(t,Ko(t)));var o=true,e=-1,i=No(n),f=u.length;false===r?o=false:se(r)&&"chain"in r&&(o=r.chain);for(;++e<f;){r=u[e];var a=t[r];n[r]=a,i&&(n.prototype[r]=function(t){return function(){var r=this.__chain__;if(o||r){var e=n(this.__wrapped__);return(e.__actions__=qn(this.__actions__)).push({func:t,args:arguments,
thisArg:n}),e.__chain__=r,e}return r=[this.value()],fu.apply(r,arguments),t.apply(n,r)}}(a))}return n}function Se(){}function Te(n){return Er(n)?At(n):jt(n)}_=_?Zn.defaults(Yn.Object(),_,Zn.pick(Yn,Tn)):Yn;var Ue=_.Array,Ne=_.Date,Fe=_.Error,$e=_.Function,Le=_.Math,ze=_.Number,Be=_.Object,Me=_.RegExp,De=_.String,Pe=_.TypeError,qe=Ue.prototype,Ke=Be.prototype,Ve=De.prototype,Ye=(Ye=_.window)&&Ye.document,Ze=$e.prototype.toString,Ge=Ke.hasOwnProperty,Je=0,Xe=Ke.toString,He=_._,Qe=Me("^"+xe(Xe).replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),nu=pe(nu=_.ArrayBuffer)&&nu,tu=pe(tu=nu&&new nu(0).slice)&&tu,ru=Le.ceil,eu=_.clearTimeout,uu=Le.floor,ou=pe(ou=Be.getOwnPropertySymbols)&&ou,iu=pe(iu=Be.getPrototypeOf)&&iu,fu=qe.push,au=pe(au=Be.preventExtensions)&&au,cu=Ke.propertyIsEnumerable,lu=pe(lu=_.Set)&&lu,su=_.setTimeout,pu=qe.splice,hu=pe(hu=_.Uint8Array)&&hu,_u=pe(_u=_.WeakMap)&&_u,vu=function(){
try{var n=pe(n=_.Float64Array)&&n,t=new n(new nu(10),0,1)&&n}catch(r){}return t}(),gu=function(){var n=au&&pe(n=Be.assign)&&n;try{if(n){var t=au({1:0});t[0]=1}}catch(r){try{n(t,"xo")}catch(e){}return!t[1]&&n}return false}(),yu=pe(yu=Ue.isArray)&&yu,du=pe(du=Be.create)&&du,mu=_.isFinite,wu=pe(wu=Be.keys)&&wu,bu=Le.max,xu=Le.min,Au=pe(Au=Ne.now)&&Au,ju=pe(ju=ze.isFinite)&&ju,ku=_.parseInt,Ou=Le.random,Eu=ze.NEGATIVE_INFINITY,Iu=ze.POSITIVE_INFINITY,Ru=Le.pow(2,32)-1,Cu=Ru-1,Wu=Ru>>>1,Su=vu?vu.BYTES_PER_ELEMENT:0,Tu=Le.pow(2,53)-1,Uu=_u&&new _u,Nu={},Fu=$n.support={};
!function(n){function t(){this.x=n}var r=arguments,e=[];t.prototype={valueOf:n,y:n};for(var u in new t)e.push(u);Fu.funcDecomp=/\bthis\b/.test(function(){return this}),Fu.funcNames=typeof $e.name=="string";try{Fu.dom=11===Ye.createDocumentFragment().nodeType}catch(o){Fu.dom=false}try{Fu.nonEnumArgs=!cu.call(r,1)}catch(i){Fu.nonEnumArgs=true}}(1,0),$n.templateSettings={escape:_n,evaluate:vn,interpolate:gn,variable:"",imports:{_:$n}};var $u=gu||function(n,t){return null==t?n:et(t,Zu(t),et(t,Ko(t),n))},Lu=function(){
function n(){}return function(t){if(se(t)){n.prototype=t;var r=new n;n.prototype=null}return r||_.Object()}}(),zu=Kt(ht),Bu=Kt(_t,true),Mu=Vt(),Du=Vt(true),Pu=Uu?function(n,t){return Uu.set(n,t),n}:Re;tu||(Bt=nu&&hu?function(n){var t=n.byteLength,r=vu?uu(t/Su):0,e=r*Su,u=new nu(t);if(r){var o=new vu(u,0,r);o.set(new vu(n,0,r))}return t!=e&&(o=new hu(u,e),o.set(new hu(n,e))),u}:Ie(null));var qu=du&&lu?function(n){return new Dn(n)}:Ie(null),Ku=Uu?function(n){return Uu.get(n)}:Se,Vu=function(){return Fu.funcNames?"constant"==Ie.name?At("name"):function(n){
for(var t=n.name,r=Nu[t],e=r?r.length:0;e--;){var u=r[e],o=u.func;if(null==o||o==n)return u.name}return t}:Ie("")}(),Yu=At("length"),Zu=ou?function(n){return ou(Fr(n))}:Ie([]),Gu=function(){var n=0,t=0;return function(r,e){var u=wo(),o=U-(u-t);if(t=u,0<o){if(++n>=T)return r}else n=0;return Pu(r,e)}}(),Ju=fe(function(n,t){return jr(n)?ft(n,st(t,false,true)):[]}),Xu=Qt(),Hu=Qt(true),Qu=fe(function(t,r){r=st(r);var e=rt(t,r);return kt(t,r.sort(n)),e}),no=pr(),to=pr(true),ro=fe(function(n){return Tt(st(n,false,true));
}),eo=fe(function(n,t){return jr(n)?ft(n,t):[]}),uo=fe(Vr),oo=fe(function(n){var t=n.length,r=n[t-2],e=n[t-1];return 2<t&&typeof r=="function"?t-=2:(r=1<t&&typeof e=="function"?(--t,e):w,e=w),n.length=t,Yr(n,r,e)}),io=fe(function(n,t){return rt(n,st(t))}),fo=Pt(function(n,t,r){Ge.call(n,r)?++n[r]:n[r]=1}),ao=Ht(zu),co=Ht(Bu,true),lo=rr(Kn,zu),so=rr(function(n,t){for(var r=n.length;r--&&false!==t(n[r],r,n););return n},Bu),po=Pt(function(n,t,r){Ge.call(n,r)?n[r].push(t):n[r]=[t]}),ho=Pt(function(n,t,r){
n[r]=t}),_o=fe(function(n,t,r){var e=-1,u=typeof t=="function",o=Er(t),i=jr(n)?Ue(n.length):[];return zu(n,function(n){var f=u?t:o&&null!=n&&n[t];i[++e]=f?f.apply(n,r):Ar(n,t,r)}),i}),vo=Pt(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),go=ar(Xn,zu),yo=ar(function(n,t,r,e){var u=n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r},Bu),mo=fe(function(n,t){if(null==n)return[];var r=t[2];return r&&Or(t[0],t[1],r)&&(t.length=1),Wt(n,st(t),[])}),wo=Au||function(){return(new Ne).getTime();
},bo=fe(function(n,t,r){var e=x;if(r.length)var u=v(r,bo.placeholder),e=e|E;return hr(n,e,t,r,u)}),xo=fe(function(n,t){t=t.length?st(t):de(n);for(var r=-1,e=t.length;++r<e;){var u=t[r];n[u]=hr(n[u],x,n)}return n}),Ao=fe(function(n,t,r){var e=x|A;if(r.length)var u=v(r,Ao.placeholder),e=e|E;return hr(t,e,n,r,u)}),jo=Jt(k),ko=Jt(O),Oo=fe(function(n,t){return it(n,1,t)}),Eo=fe(function(n,t,r){return it(n,t,r)}),Io=tr(),Ro=tr(true),Co=fr(E),Wo=fr(I),So=fe(function(n,t){return hr(n,C,null,null,null,st(t));
}),To=yu||function(n){return h(n)&&Rr(n.length)&&Xe.call(n)==M};Fu.dom||(ce=function(n){return!!n&&1===n.nodeType&&h(n)&&!Fo(n)});var Uo=ju||function(n){return typeof n=="number"&&mu(n)},No=e(/x/)||hu&&!e(hu)?function(n){return Xe.call(n)==K}:e,Fo=iu?function(n){if(!n||Xe.call(n)!=Y)return false;var t=n.valueOf,r=pe(t)&&(r=iu(t))&&iu(r);return r?n==r||iu(n)==r:Tr(n)}:Tr,$o=qt(function(n,t,r){return r?tt(n,t,r):$u(n,t)}),Lo=fe(function(n){var t=n[0];return null==t?t:(n.push(Qn),$o.apply(w,n))}),zo=nr(ht),Bo=nr(_t),Mo=er(Mu),Do=er(Du),Po=ur(ht),qo=ur(_t),Ko=wu?function(n){
var t=null!=n&&n.constructor;return typeof t=="function"&&t.prototype===n||typeof n!="function"&&jr(n)?Ur(n):se(n)?wu(n):[]}:Ur,Vo=or(true),Yo=or(),Zo=qt(xt),Go=fe(function(n,t){if(null==n)return{};if("function"!=typeof t[0])return t=Jn(st(t),De),Wr(n,ft(me(n),t));var r=zt(t[0],t[1],3);return Sr(n,function(n,t,e){return!r(n,t,e)})}),Jo=fe(function(n,t){return null==n?{}:"function"==typeof t[0]?Sr(n,zt(t[0],t[1],3)):Wr(n,st(t))}),Xo=Zt(function(n,t,r){return t=t.toLowerCase(),n+(r?t.charAt(0).toUpperCase()+t.slice(1):t);
}),Ho=Zt(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Qo=ir(),ni=ir(true);8!=ku(Sn+"08")&&(Ae=function(n,t,r){return(r?Or(n,t,r):null==t)?t=0:t&&(t=+t),n=ke(n),ku(n,t||(On.test(n)?16:10))});var ti=Zt(function(n,t,r){return n+(r?"_":"")+t.toLowerCase()}),ri=Zt(function(n,t,r){return n+(r?" ":"")+(t.charAt(0).toUpperCase()+t.slice(1))}),ei=fe(function(n,t){try{return n.apply(w,t)}catch(r){return le(r)?r:new Fe(r)}}),ui=fe(function(n,t){return function(r){return Ar(r,n,t)}}),oi=fe(function(n,t){
return function(r){return Ar(n,r,t)}}),ii=Xt(function(n){for(var t=-1,r=n.length,e=Eu;++t<r;){var u=n[t];u>e&&(e=u)}return e}),fi=Xt(function(n){for(var t=-1,r=n.length,e=Iu;++t<r;){var u=n[t];u<e&&(e=u)}return e},true);return $n.prototype=Ln.prototype,zn.prototype=Lu(Ln.prototype),zn.prototype.constructor=zn,Bn.prototype=Lu(Ln.prototype),Bn.prototype.constructor=Bn,Mn.prototype["delete"]=function(n){return this.has(n)&&delete this.__data__[n]},Mn.prototype.get=function(n){return"__proto__"==n?w:this.__data__[n];
},Mn.prototype.has=function(n){return"__proto__"!=n&&Ge.call(this.__data__,n)},Mn.prototype.set=function(n,t){return"__proto__"!=n&&(this.__data__[n]=t),this},Dn.prototype.push=function(n){var t=this.data;typeof n=="string"||se(n)?t.set.add(n):t.hash[n]=true},ie.Cache=Mn,$n.after=function(n,t){if(typeof t!="function"){if(typeof n!="function")throw new Pe(L);var r=n;n=t,t=r}return n=mu(n=+n)?n:0,function(){return 1>--n?t.apply(this,arguments):void 0}},$n.ary=function(n,t,r){return r&&Or(n,t,r)&&(t=null),
t=n&&null==t?n.length:bu(+t||0,0),hr(n,R,null,null,null,null,t)},$n.assign=$o,$n.at=io,$n.before=ue,$n.bind=bo,$n.bindAll=xo,$n.bindKey=Ao,$n.callback=Ee,$n.chain=Gr,$n.chunk=function(n,t,r){t=(r?Or(n,t,r):null==t)?1:bu(+t||1,1),r=0;for(var e=n?n.length:0,u=-1,o=Ue(ru(e/t));r<e;)o[++u]=It(n,r,r+=t);return o},$n.compact=function(n){for(var t=-1,r=n?n.length:0,e=-1,u=[];++t<r;){var o=n[t];o&&(u[++e]=o)}return u},$n.constant=Ie,$n.countBy=fo,$n.create=function(n,t,r){var e=Lu(n);return r&&Or(n,t,r)&&(t=null),
t?$u(e,t):e},$n.curry=jo,$n.curryRight=ko,$n.debounce=oe,$n.defaults=Lo,$n.defer=Oo,$n.delay=Eo,$n.difference=Ju,$n.drop=zr,$n.dropRight=Br,$n.dropRightWhile=function(n,t,r){return n&&n.length?Nt(n,dr(t,r,3),true,true):[]},$n.dropWhile=function(n,t,r){return n&&n.length?Nt(n,dr(t,r,3),true):[]},$n.fill=function(n,t,r,e){var u=n?n.length:0;if(!u)return[];for(r&&typeof r!="number"&&Or(n,t,r)&&(r=0,e=u),u=n.length,r=null==r?0:+r||0,0>r&&(r=-r>u?0:u+r),e=e===w||e>u?u:+e||0,0>e&&(e+=u),u=r>e?0:e>>>0,r>>>=0;r<u;)n[r++]=t;
return n},$n.filter=Hr,$n.flatten=function(n,t,r){var e=n?n.length:0;return r&&Or(n,t,r)&&(t=false),e?st(n,t):[]},$n.flattenDeep=function(n){return n&&n.length?st(n,true):[]},$n.flow=Io,$n.flowRight=Ro,$n.forEach=lo,$n.forEachRight=so,$n.forIn=Mo,$n.forInRight=Do,$n.forOwn=Po,$n.forOwnRight=qo,$n.functions=de,$n.groupBy=po,$n.indexBy=ho,$n.initial=function(n){return Br(n,1)},$n.intersection=function(){for(var n=[],t=-1,e=arguments.length,u=[],o=mr(),i=o==r,f=[];++t<e;){var a=arguments[t];jr(a)&&(n.push(a),
u.push(i&&120<=a.length?qu(t&&a):null))}if(e=n.length,2>e)return f;var i=n[0],c=-1,l=i?i.length:0,s=u[0];n:for(;++c<l;)if(a=i[c],0>(s?Pn(s,a):o(f,a,0))){for(t=e;--t;){var p=u[t];if(0>(p?Pn(p,a):o(n[t],a,0)))continue n}s&&s.push(a),f.push(a)}return f},$n.invert=function(n,t,r){r&&Or(n,t,r)&&(t=null),r=-1;for(var e=Ko(n),u=e.length,o={};++r<u;){var i=e[r],f=n[i];t?Ge.call(o,f)?o[f].push(i):o[f]=[i]:o[f]=i}return o},$n.invoke=_o,$n.keys=Ko,$n.keysIn=me,$n.map=ne,$n.mapKeys=Vo,$n.mapValues=Yo,$n.matches=Ce,
$n.matchesProperty=function(n,t){return bt(n,ot(t,true))},$n.memoize=ie,$n.merge=Zo,$n.method=ui,$n.methodOf=oi,$n.mixin=We,$n.negate=function(n){if(typeof n!="function")throw new Pe(L);return function(){return!n.apply(this,arguments)}},$n.omit=Go,$n.once=function(n){return ue(2,n)},$n.pairs=function(n){for(var t=-1,r=Ko(n),e=r.length,u=Ue(e);++t<e;){var o=r[t];u[t]=[o,n[o]]}return u},$n.partial=Co,$n.partialRight=Wo,$n.partition=vo,$n.pick=Jo,$n.pluck=function(n,t){return ne(n,Te(t))},$n.property=Te,
$n.propertyOf=function(n){return function(t){return gt(n,$r(t),t+"")}},$n.pull=function(){var n=arguments,t=n[0];if(!t||!t.length)return t;for(var r=0,e=mr(),u=n.length;++r<u;)for(var o=0,i=n[r];-1<(o=e(t,i,o));)pu.call(t,o,1);return t},$n.pullAt=Qu,$n.range=function(n,t,r){r&&Or(n,t,r)&&(t=r=null),n=+n||0,r=null==r?1:+r||0,null==t?(t=n,n=0):t=+t||0;var e=-1;t=bu(ru((t-n)/(r||1)),0);for(var u=Ue(t);++e<t;)u[e]=n,n+=r;return u},$n.rearg=So,$n.reject=function(n,t,r){var e=To(n)?Gn:ct;return t=dr(t,r,3),
e(n,function(n,r,e){return!t(n,r,e)})},$n.remove=function(n,t,r){var e=[];if(!n||!n.length)return e;var u=-1,o=[],i=n.length;for(t=dr(t,r,3);++u<i;)r=n[u],t(r,u,n)&&(e.push(r),o.push(u));return kt(n,o),e},$n.rest=qr,$n.restParam=fe,$n.set=function(n,t,r){if(null==n)return n;var e=t+"";t=null!=n[e]||Er(t,n)?[e]:$r(t);for(var e=-1,u=t.length,o=u-1,i=n;null!=i&&++e<u;){var f=t[e];se(i)&&(e==o?i[f]=r:null==i[f]&&(i[f]=kr(t[e+1])?[]:{})),i=i[f]}return n},$n.shuffle=re,$n.slice=function(n,t,r){var e=n?n.length:0;
return e?(r&&typeof r!="number"&&Or(n,t,r)&&(t=0,r=e),It(n,t,r)):[]},$n.sortBy=function(n,t,r){if(null==n)return[];r&&Or(n,t,r)&&(t=null);var e=-1;return t=dr(t,r,3),n=mt(n,function(n,r,u){return{a:t(n,r,u),b:++e,c:n}}),Ct(n,a)},$n.sortByAll=mo,$n.sortByOrder=function(n,t,r,e){return null==n?[]:(e&&Or(t,r,e)&&(r=null),To(t)||(t=null==t?[]:[t]),To(r)||(r=null==r?[]:[r]),Wt(n,t,r))},$n.spread=function(n){if(typeof n!="function")throw new Pe(L);return function(t){return n.apply(this,t)}},$n.take=function(n,t,r){
return n&&n.length?((r?Or(n,t,r):null==t)&&(t=1),It(n,0,0>t?0:t)):[]},$n.takeRight=function(n,t,r){var e=n?n.length:0;return e?((r?Or(n,t,r):null==t)&&(t=1),t=e-(+t||0),It(n,0>t?0:t)):[]},$n.takeRightWhile=function(n,t,r){return n&&n.length?Nt(n,dr(t,r,3),false,true):[]},$n.takeWhile=function(n,t,r){return n&&n.length?Nt(n,dr(t,r,3)):[]},$n.tap=function(n,t,r){return t.call(r,n),n},$n.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new Pe(L);return false===r?e=false:se(r)&&(e="leading"in r?!!r.leading:e,
u="trailing"in r?!!r.trailing:u),Fn.leading=e,Fn.maxWait=+t,Fn.trailing=u,oe(n,t,Fn)},$n.thru=Jr,$n.times=function(n,t,r){if(n=uu(n),1>n||!mu(n))return[];var e=-1,u=Ue(xu(n,Ru));for(t=zt(t,r,1);++e<n;)e<Ru?u[e]=t(e):t(e);return u},$n.toArray=function(n){var t=n?Yu(n):0;return Rr(t)?t?qn(n):[]:we(n)},$n.toPlainObject=ye,$n.transform=function(n,t,r,e){var u=To(n)||ge(n);return t=dr(t,e,4),null==r&&(u||se(n)?(e=n.constructor,r=u?To(n)?new e:[]:Lu(No(e)&&e.prototype)):r={}),(u?Kn:ht)(n,function(n,e,u){
return t(r,n,e,u)}),r},$n.union=ro,$n.uniq=Kr,$n.unzip=Vr,$n.unzipWith=Yr,$n.values=we,$n.valuesIn=function(n){return Ut(n,me(n))},$n.where=function(n,t){return Hr(n,wt(t))},$n.without=eo,$n.wrap=function(n,t){return t=null==t?Re:t,hr(t,E,null,[n],[])},$n.xor=function(){for(var n=-1,t=arguments.length;++n<t;){var r=arguments[n];if(jr(r))var e=e?ft(e,r).concat(ft(r,e)):r}return e?Tt(e):[]},$n.zip=uo,$n.zipObject=Zr,$n.zipWith=oo,$n.backflow=Ro,$n.collect=ne,$n.compose=Ro,$n.each=lo,$n.eachRight=so,
$n.extend=$o,$n.iteratee=Ee,$n.methods=de,$n.object=Zr,$n.select=Hr,$n.tail=qr,$n.unique=Kr,We($n,$n),$n.add=function(n,t){return(+n||0)+(+t||0)},$n.attempt=ei,$n.camelCase=Xo,$n.capitalize=function(n){return(n=u(n))&&n.charAt(0).toUpperCase()+n.slice(1)},$n.clone=function(n,t,r,e){return t&&typeof t!="boolean"&&Or(n,t,r)?t=false:typeof t=="function"&&(e=r,r=t,t=false),r=typeof r=="function"&&zt(r,e,1),ot(n,t,r)},$n.cloneDeep=function(n,t,r){return t=typeof t=="function"&&zt(t,r,1),ot(n,true,t)},$n.deburr=be,
$n.endsWith=function(n,t,r){n=u(n),t+="";var e=n.length;return r=r===w?e:xu(0>r?0:+r||0,e),r-=t.length,0<=r&&n.indexOf(t,r)==r},$n.escape=function(n){return(n=u(n))&&hn.test(n)?n.replace(sn,l):n},$n.escapeRegExp=xe,$n.every=Xr,$n.find=ao,$n.findIndex=Xu,$n.findKey=zo,$n.findLast=co,$n.findLastIndex=Hu,$n.findLastKey=Bo,$n.findWhere=function(n,t){return ao(n,wt(t))},$n.first=Mr,$n.get=function(n,t,r){return n=null==n?w:gt(n,$r(t),t+""),n===w?r:n},$n.has=function(n,t){if(null==n)return false;var r=Ge.call(n,t);
return r||Er(t)||(t=$r(t),n=1==t.length?n:gt(n,It(t,0,-1)),t=Pr(t),r=null!=n&&Ge.call(n,t)),r},$n.identity=Re,$n.includes=Qr,$n.indexOf=Dr,$n.inRange=function(n,t,r){return t=+t||0,"undefined"===typeof r?(r=t,t=0):r=+r||0,n>=xu(t,r)&&n<bu(t,r)},$n.isArguments=ae,$n.isArray=To,$n.isBoolean=function(n){return true===n||false===n||h(n)&&Xe.call(n)==D},$n.isDate=function(n){return h(n)&&Xe.call(n)==P},$n.isElement=ce,$n.isEmpty=function(n){return null==n?true:jr(n)&&(To(n)||ve(n)||ae(n)||h(n)&&No(n.splice))?!n.length:!Ko(n).length;
},$n.isEqual=function(n,t,r,e){return r=typeof r=="function"&&zt(r,e,3),!r&&Cr(n)&&Cr(t)?n===t:(e=r?r(n,t):w,e===w?yt(n,t,r):!!e)},$n.isError=le,$n.isFinite=Uo,$n.isFunction=No,$n.isMatch=function(n,t,r,e){var u=Ko(t),o=u.length;if(!o)return true;if(null==n)return false;if(r=typeof r=="function"&&zt(r,e,3),n=Fr(n),!r&&1==o){var i=u[0];if(e=t[i],Cr(e))return e===n[i]&&(e!==w||i in n)}for(var i=Ue(o),f=Ue(o);o--;)e=i[o]=t[u[o]],f[o]=Cr(e);return dt(n,u,i,f,r)},$n.isNaN=function(n){return he(n)&&n!=+n},$n.isNative=pe,
$n.isNull=function(n){return null===n},$n.isNumber=he,$n.isObject=se,$n.isPlainObject=Fo,$n.isRegExp=_e,$n.isString=ve,$n.isTypedArray=ge,$n.isUndefined=function(n){return n===w},$n.kebabCase=Ho,$n.last=Pr,$n.lastIndexOf=function(n,t,r){var e=n?n.length:0;if(!e)return-1;var u=e;if(typeof r=="number")u=(0>r?bu(e+r,0):xu(r||0,e-1))+1;else if(r)return u=$t(n,t,true)-1,n=n[u],(t===t?t===n:n!==n)?u:-1;if(t!==t)return p(n,u,true);for(;u--;)if(n[u]===t)return u;return-1},$n.max=ii,$n.min=fi,$n.noConflict=function(){
return _._=He,this},$n.noop=Se,$n.now=wo,$n.pad=function(n,t,r){n=u(n),t=+t;var e=n.length;return e<t&&mu(t)?(e=(t-e)/2,t=uu(e),e=ru(e),r=lr("",e,r),r.slice(0,t)+n+r):n},$n.padLeft=Qo,$n.padRight=ni,$n.parseInt=Ae,$n.random=function(n,t,r){r&&Or(n,t,r)&&(t=r=null);var e=null==n,u=null==t;return null==r&&(u&&typeof n=="boolean"?(r=n,n=1):typeof t=="boolean"&&(r=t,u=true)),e&&u&&(t=1,u=false),n=+n||0,u?(t=n,n=0):t=+t||0,r||n%1||t%1?(r=Ou(),xu(n+r*(t-n+parseFloat("1e-"+((r+"").length-1))),t)):Ot(n,t)},$n.reduce=go,
$n.reduceRight=yo,$n.repeat=je,$n.result=function(n,t,r){var e=null==n?w:n[t];return e===w&&(null==n||Er(t,n)||(t=$r(t),n=1==t.length?n:gt(n,It(t,0,-1)),e=null==n?w:n[Pr(t)]),e=e===w?r:e),No(e)?e.call(n):e},$n.runInContext=m,$n.size=function(n){var t=n?Yu(n):0;return Rr(t)?t:Ko(n).length},$n.snakeCase=ti,$n.some=ee,$n.sortedIndex=no,$n.sortedLastIndex=to,$n.startCase=ri,$n.startsWith=function(n,t,r){return n=u(n),r=null==r?0:xu(0>r?0:+r||0,n.length),n.lastIndexOf(t,r)==r},$n.sum=function(n,t,r){r&&Or(n,t,r)&&(t=null);
var e=dr(),u=null==t;if(e===ut&&u||(u=false,t=e(t,r,3)),u){for(n=To(n)?n:Nr(n),t=n.length,r=0;t--;)r+=+n[t]||0;n=r}else n=St(n,t);return n},$n.template=function(n,t,r){var e=$n.templateSettings;r&&Or(n,t,r)&&(t=r=null),n=u(n),t=tt($u({},r||t),e,nt),r=tt($u({},t.imports),e.imports,nt);var o,i,f=Ko(r),a=Ut(r,f),c=0;r=t.interpolate||Rn;var l="__p+='";r=Me((t.escape||Rn).source+"|"+r.source+"|"+(r===gn?jn:Rn).source+"|"+(t.evaluate||Rn).source+"|$","g");var p="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";
if(n.replace(r,function(t,r,e,u,f,a){return e||(e=u),l+=n.slice(c,a).replace(Cn,s),r&&(o=true,l+="'+__e("+r+")+'"),f&&(i=true,l+="';"+f+";\n__p+='"),e&&(l+="'+((__t=("+e+"))==null?'':__t)+'"),c=a+t.length,t}),l+="';",(t=t.variable)||(l="with(obj){"+l+"}"),l=(i?l.replace(fn,""):l).replace(an,"$1").replace(cn,"$1;"),l="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(o?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}",
t=ei(function(){return $e(f,p+"return "+l).apply(w,a)}),t.source=l,le(t))throw t;return t},$n.trim=ke,$n.trimLeft=function(n,t,r){var e=n;return(n=u(n))?n.slice((r?Or(e,t,r):null==t)?g(n):i(n,t+"")):n},$n.trimRight=function(n,t,r){var e=n;return(n=u(n))?(r?Or(e,t,r):null==t)?n.slice(0,y(n)+1):n.slice(0,f(n,t+"")+1):n},$n.trunc=function(n,t,r){r&&Or(n,t,r)&&(t=null);var e=W;if(r=S,null!=t)if(se(t)){var o="separator"in t?t.separator:o,e="length"in t?+t.length||0:e;r="omission"in t?u(t.omission):r}else e=+t||0;
if(n=u(n),e>=n.length)return n;if(e-=r.length,1>e)return r;if(t=n.slice(0,e),null==o)return t+r;if(_e(o)){if(n.slice(e).search(o)){var i,f=n.slice(0,e);for(o.global||(o=Me(o.source,(kn.exec(o)||"")+"g")),o.lastIndex=0;n=o.exec(f);)i=n.index;t=t.slice(0,null==i?e:i)}}else n.indexOf(o,e)!=e&&(o=t.lastIndexOf(o),-1<o&&(t=t.slice(0,o)));return t+r},$n.unescape=function(n){return(n=u(n))&&pn.test(n)?n.replace(ln,d):n},$n.uniqueId=function(n){var t=++Je;return u(n)+t},$n.words=Oe,$n.all=Xr,$n.any=ee,$n.contains=Qr,
$n.detect=ao,$n.foldl=go,$n.foldr=yo,$n.head=Mr,$n.include=Qr,$n.inject=go,We($n,function(){var n={};return ht($n,function(t,r){$n.prototype[r]||(n[r]=t)}),n}(),false),$n.sample=te,$n.prototype.sample=function(n){return this.__chain__||null!=n?this.thru(function(t){return te(t,n)}):te(this.value())},$n.VERSION=b,Kn("bind bindKey curry curryRight partial partialRight".split(" "),function(n){$n[n].placeholder=$n}),Kn(["dropWhile","filter","map","takeWhile"],function(n,t){var r=t!=$,e=t==N;Bn.prototype[n]=function(n,u){
var o=this.__filtered__,i=o&&e?new Bn(this):this.clone();return(i.__iteratees__||(i.__iteratees__=[])).push({done:false,count:0,index:0,iteratee:dr(n,u,1),limit:-1,type:t}),i.__filtered__=o||r,i}}),Kn(["drop","take"],function(n,t){var r=n+"While";Bn.prototype[n]=function(r){var e=this.__filtered__,u=e&&!t?this.dropWhile():this.clone();return r=null==r?1:bu(uu(r)||0,0),e?t?u.__takeCount__=xu(u.__takeCount__,r):Pr(u.__iteratees__).limit=r:(u.__views__||(u.__views__=[])).push({size:r,type:n+(0>u.__dir__?"Right":"")
}),u},Bn.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()},Bn.prototype[n+"RightWhile"]=function(n,t){return this.reverse()[r](n,t).reverse()}}),Kn(["first","last"],function(n,t){var r="take"+(t?"Right":"");Bn.prototype[n]=function(){return this[r](1).value()[0]}}),Kn(["initial","rest"],function(n,t){var r="drop"+(t?"":"Right");Bn.prototype[n]=function(){return this[r](1)}}),Kn(["pluck","where"],function(n,t){var r=t?"filter":"map",e=t?wt:Te;Bn.prototype[n]=function(n){return this[r](e(n));
}}),Bn.prototype.compact=function(){return this.filter(Re)},Bn.prototype.reject=function(n,t){return n=dr(n,t,1),this.filter(function(t){return!n(t)})},Bn.prototype.slice=function(n,t){n=null==n?0:+n||0;var r=this;return 0>n?r=this.takeRight(-n):n&&(r=this.drop(n)),t!==w&&(t=+t||0,r=0>t?r.dropRight(-t):r.take(t-n)),r},Bn.prototype.toArray=function(){return this.drop(0)},ht(Bn.prototype,function(n,t){var r=$n[t];if(r){var e=/^(?:filter|map|reject)|While$/.test(t),u=/^(?:first|last)$/.test(t);$n.prototype[t]=function(){
function t(n){return n=[n],fu.apply(n,o),r.apply($n,n)}var o=arguments,i=this.__chain__,f=this.__wrapped__,a=!!this.__actions__.length,c=f instanceof Bn,l=o[0],s=c||To(f);return s&&e&&typeof l=="function"&&1!=l.length&&(c=s=false),c=c&&!a,u&&!i?c?n.call(f):r.call($n,this.value()):s?(f=n.apply(c?f:new Bn(this),o),u||!a&&!f.__actions__||(f.__actions__||(f.__actions__=[])).push({func:Jr,args:[t],thisArg:$n}),new zn(f,i)):this.thru(t)}}}),Kn("concat join pop push replace shift sort splice split unshift".split(" "),function(n){
var t=(/^(?:replace|split)$/.test(n)?Ve:qe)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:join|pop|replace|shift)$/.test(n);$n.prototype[n]=function(){var n=arguments;return e&&!this.__chain__?t.apply(this.value(),n):this[r](function(r){return t.apply(r,n)})}}),ht(Bn.prototype,function(n,t){var r=$n[t];if(r){var e=r.name;(Nu[e]||(Nu[e]=[])).push({name:t,func:r})}}),Nu[cr(null,A).name]=[{name:"wrapper",func:null}],Bn.prototype.clone=function(){var n=this.__actions__,t=this.__iteratees__,r=this.__views__,e=new Bn(this.__wrapped__);
return e.__actions__=n?qn(n):null,e.__dir__=this.__dir__,e.__filtered__=this.__filtered__,e.__iteratees__=t?qn(t):null,e.__takeCount__=this.__takeCount__,e.__views__=r?qn(r):null,e},Bn.prototype.reverse=function(){if(this.__filtered__){var n=new Bn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},Bn.prototype.value=function(){var n=this.__wrapped__.value();if(!To(n))return Ft(n,this.__actions__);var t,r=this.__dir__,e=0>r;t=n.length;for(var u=this.__views__,o=0,i=-1,f=u?u.length:0;++i<f;){
var a=u[i],c=a.size;switch(a.type){case"drop":o+=c;break;case"dropRight":t-=c;break;case"take":t=xu(t,o+c);break;case"takeRight":o=bu(o,t-c)}}t={start:o,end:t},u=t.start,o=t.end,t=o-u,u=e?o:u-1,o=xu(t,this.__takeCount__),f=(i=this.__iteratees__)?i.length:0,a=0,c=[];n:for(;t--&&a<o;){for(var u=u+r,l=-1,s=n[u];++l<f;){var p=i[l],h=p.iteratee,_=p.type;if(_==N){if(p.done&&(e?u>p.index:u<p.index)&&(p.count=0,p.done=false),p.index=u,!(p.done||(_=p.limit,p.done=-1<_?p.count++>=_:!h(s))))continue n}else if(p=h(s),
_==$)s=p;else if(!p){if(_==F)continue n;break n}}c[a++]=s}return c},$n.prototype.chain=function(){return Gr(this)},$n.prototype.commit=function(){return new zn(this.value(),this.__chain__)},$n.prototype.plant=function(n){for(var t,r=this;r instanceof Ln;){var e=Lr(r);t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},$n.prototype.reverse=function(){var n=this.__wrapped__;return n instanceof Bn?(this.__actions__.length&&(n=new Bn(this)),new zn(n.reverse(),this.__chain__)):this.thru(function(n){
return n.reverse()})},$n.prototype.toString=function(){return this.value()+""},$n.prototype.run=$n.prototype.toJSON=$n.prototype.valueOf=$n.prototype.value=function(){return Ft(this.__wrapped__,this.__actions__)},$n.prototype.collect=$n.prototype.map,$n.prototype.head=$n.prototype.first,$n.prototype.select=$n.prototype.filter,$n.prototype.tail=$n.prototype.rest,$n}var w,b="3.8.0",x=1,A=2,j=4,k=8,O=16,E=32,I=64,R=128,C=256,W=30,S="...",T=150,U=16,N=0,F=1,$=2,L="Expected a function",z="__lodash_placeholder__",B="[object Arguments]",M="[object Array]",D="[object Boolean]",P="[object Date]",q="[object Error]",K="[object Function]",V="[object Number]",Y="[object Object]",Z="[object RegExp]",G="[object String]",J="[object ArrayBuffer]",X="[object Float32Array]",H="[object Float64Array]",Q="[object Int8Array]",nn="[object Int16Array]",tn="[object Int32Array]",rn="[object Uint8Array]",en="[object Uint8ClampedArray]",un="[object Uint16Array]",on="[object Uint32Array]",fn=/\b__p\+='';/g,an=/\b(__p\+=)''\+/g,cn=/(__e\(.*?\)|\b__t\))\+'';/g,ln=/&(?:amp|lt|gt|quot|#39|#96);/g,sn=/[&<>"'`]/g,pn=RegExp(ln.source),hn=RegExp(sn.source),_n=/<%-([\s\S]+?)%>/g,vn=/<%([\s\S]+?)%>/g,gn=/<%=([\s\S]+?)%>/g,yn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,dn=/^\w*$/,mn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,wn=/[.*+?^${}()|[\]\/\\]/g,bn=RegExp(wn.source),xn=/[\u0300-\u036f\ufe20-\ufe23]/g,An=/\\(\\)?/g,jn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,kn=/\w*$/,On=/^0[xX]/,En=/^\[object .+?Constructor\]$/,In=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,Rn=/($^)/,Cn=/['\n\r\u2028\u2029\\]/g,Wn=RegExp("[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?=[A-Z\\xc0-\\xd6\\xd8-\\xde][a-z\\xdf-\\xf6\\xf8-\\xff]+)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+|[A-Z\\xc0-\\xd6\\xd8-\\xde]+|[0-9]+","g"),Sn=" \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",Tn="Array ArrayBuffer Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Math Number Object RegExp Set String _ clearTimeout document isFinite parseInt setTimeout TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap window".split(" "),Un={};
Un[X]=Un[H]=Un[Q]=Un[nn]=Un[tn]=Un[rn]=Un[en]=Un[un]=Un[on]=true,Un[B]=Un[M]=Un[J]=Un[D]=Un[P]=Un[q]=Un[K]=Un["[object Map]"]=Un[V]=Un[Y]=Un[Z]=Un["[object Set]"]=Un[G]=Un["[object WeakMap]"]=false;var Nn={};Nn[B]=Nn[M]=Nn[J]=Nn[D]=Nn[P]=Nn[X]=Nn[H]=Nn[Q]=Nn[nn]=Nn[tn]=Nn[V]=Nn[Y]=Nn[Z]=Nn[G]=Nn[rn]=Nn[en]=Nn[un]=Nn[on]=true,Nn[q]=Nn[K]=Nn["[object Map]"]=Nn["[object Set]"]=Nn["[object WeakMap]"]=false;var Fn={leading:false,maxWait:0,trailing:false},$n={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A",
"\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u",
"\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},Ln={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","`":"&#96;"},zn={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#96;":"`"},Bn={"function":true,object:true},Mn={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Dn=Bn[typeof exports]&&exports&&!exports.nodeType&&exports,Pn=Bn[typeof module]&&module&&!module.nodeType&&module,qn=Bn[typeof self]&&self&&self.Object&&self,Kn=Bn[typeof window]&&window&&window.Object&&window,Vn=Pn&&Pn.exports===Dn&&Dn,Yn=Dn&&Pn&&typeof global=="object"&&global&&global.Object&&global||Kn!==(this&&this.window)&&Kn||qn||this,Zn=m();
typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Yn._=Zn, define(function(){return Zn})):Dn&&Pn?Vn?(Pn.exports=Zn)._=Zn:Dn._=Zn:Yn._=Zn}).call(this);

View File

@ -29,7 +29,7 @@ from glances.plugins.glances_plugin import GlancesPlugin
class Plugin(GlancesPlugin):
"""Glances' alert plugin.
"""Glances alert plugin.
Only for display.
"""
@ -42,7 +42,7 @@ class Plugin(GlancesPlugin):
self.display_curse = True
# Set the message position
self.set_align('bottom')
self.align = 'bottom'
# Init the stats
self.reset()
@ -68,17 +68,17 @@ class Plugin(GlancesPlugin):
# Build the string message
# Header
if not self.stats:
msg = _("No warning or critical alert detected")
msg = 'No warning or critical alert detected'
ret.append(self.curse_add_line(msg, "TITLE"))
else:
# Header
msg = _("Warning or critical alerts")
msg = 'Warning or critical alerts'
ret.append(self.curse_add_line(msg, "TITLE"))
logs_len = glances_logs.len()
if logs_len > 1:
msg = _(" (lasts {0} entries)").format(logs_len)
msg = ' (lasts {0} entries)'.format(logs_len)
else:
msg = _(" (one entry)")
msg = ' (one entry)'
ret.append(self.curse_add_line(msg, "TITLE"))
# Loop over alerts
for alert in self.stats:
@ -90,15 +90,16 @@ class Plugin(GlancesPlugin):
# Duration
if alert[1] > 0:
# If finished display duration
msg = ' ({0})'.format(datetime.fromtimestamp(alert[1]) - datetime.fromtimestamp(alert[0]))
msg = ' ({0})'.format(datetime.fromtimestamp(alert[1]) -
datetime.fromtimestamp(alert[0]))
else:
msg = _(" (ongoing)")
msg = ' (ongoing)'
ret.append(self.curse_add_line(msg))
ret.append(self.curse_add_line(" - "))
# Infos
if alert[1] > 0:
# If finished do not display status
msg = _("{0} on {1}").format(alert[2], alert[3])
msg = '{0} on {1}'.format(alert[2], alert[3])
ret.append(self.curse_add_line(msg))
else:
msg = str(alert[3])
@ -107,11 +108,12 @@ class Plugin(GlancesPlugin):
if self.approx_equal(alert[6], alert[4], tolerance=0.1):
msg = ' ({0:.1f})'.format(alert[5])
else:
msg = _(" (Min:{0:.1f} Mean:{1:.1f} Max:{2:.1f})").format(alert[6], alert[5], alert[4])
msg = ' (Min:{0:.1f} Mean:{1:.1f} Max:{2:.1f})'.format(
alert[6], alert[5], alert[4])
ret.append(self.curse_add_line(msg))
# else:
# msg = _(" Running...")
# msg = ' Running...'
# ret.append(self.curse_add_line(msg))
# !!! Debug only
@ -121,9 +123,7 @@ class Plugin(GlancesPlugin):
return ret
def approx_equal(self, a, b, tolerance=0.0):
"""
Compare a with b using the tolerance (if numerical)
"""
"""Compare a with b using the tolerance (if numerical)."""
if str(int(a)).isdigit() and str(int(b)).isdigit():
return abs(a - b) <= max(abs(a), abs(b)) * tolerance
else:

View File

@ -28,12 +28,11 @@ try:
import batinfo
except ImportError:
logger.debug("Batinfo library not found. Glances cannot grab battery info.")
pass
class Plugin(GlancesPlugin):
"""Glances' battery capacity plugin.
"""Glances battery capacity plugin.
stats is a list
"""
@ -62,12 +61,12 @@ class Plugin(GlancesPlugin):
# Reset stats
self.reset()
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats
self.glancesgrabbat.update()
self.stats = self.glancesgrabbat.get()
elif self.get_input() == 'snmp':
elif self.input_method == 'snmp':
# Update stats using SNMP
# Not avalaible
pass
@ -94,7 +93,10 @@ class GlancesGrabBat(object):
"""Update the stats."""
if self.initok:
self.bat.update()
self.bat_list = [{'label': _("Battery (%)"), 'value': self.getcapacitypercent()}]
self.bat_list = [{
'label': 'Battery',
'value': self.battery_percent,
'unit': '%'}]
else:
self.bat_list = []
@ -102,7 +104,8 @@ class GlancesGrabBat(object):
"""Get the stats."""
return self.bat_list
def getcapacitypercent(self):
@property
def battery_percent(self):
"""Get batteries capacity percent."""
if not self.initok or not self.bat.stat:
return []
@ -112,7 +115,7 @@ class GlancesGrabBat(object):
bsum = 0
for b in self.bat.stat:
try:
bsum = bsum + int(b.capacity)
bsum += int(b.capacity)
except ValueError:
return []

View File

@ -19,14 +19,14 @@
"""CPU core plugin."""
import psutil
from glances.plugins.glances_plugin import GlancesPlugin
import psutil
class Plugin(GlancesPlugin):
"""Glances' CPU core plugin.
"""Glances CPU core plugin.
Get stats about CPU core number.
@ -56,7 +56,7 @@ class Plugin(GlancesPlugin):
# Reset the stats
self.reset()
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats using the standard system lib
# The PSUtil 2.0 include psutil.cpu_count() and psutil.cpu_count(logical=False)
@ -70,7 +70,7 @@ class Plugin(GlancesPlugin):
except NameError:
self.reset()
elif self.get_input() == 'snmp':
elif self.input_method == 'snmp':
# Update stats using SNMP
# http://stackoverflow.com/questions/5662467/how-to-find-out-the-number-of-cpus-using-snmp
pass

View File

@ -19,10 +19,10 @@
"""CPU plugin."""
import psutil
from glances.core.glances_cpu_percent import cpu_percent
from glances.plugins.glances_plugin import GlancesPlugin
# from glances.core.glances_logging import logger
import psutil
# SNMP OID
# percentage of user CPU time: .1.3.6.1.4.1.2021.11.9.0
@ -77,25 +77,25 @@ class Plugin(GlancesPlugin):
# Grab CPU stats using psutil's cpu_percent and cpu_times_percent
# methods
if self.get_input() == 'local':
if self.input_method == 'local':
# Get all possible values for CPU stats: user, system, idle,
# nice (UNIX), iowait (Linux), irq (Linux, FreeBSD), steal (Linux 2.6.11+)
# The following stats are returned by the API but not displayed in the UI:
# softirq (Linux), guest (Linux 2.6.24+), guest_nice (Linux 3.2.0+)
self.stats['total'] = psutil.cpu_percent(interval=0.0)
self.stats['total'] = cpu_percent.get()
cpu_times_percent = psutil.cpu_times_percent(interval=0.0)
for stat in ['user', 'system', 'idle', 'nice', 'iowait',
'irq', 'softirq', 'steal', 'guest', 'guest_nice']:
if hasattr(cpu_times_percent, stat):
self.stats[stat] = getattr(cpu_times_percent, stat)
elif self.get_input() == 'snmp':
elif self.input_method == 'snmp':
# Update stats using SNMP
if self.get_short_system_name() in ('windows', 'esxi'):
if self.short_system_name in ('windows', 'esxi'):
# Windows or VMWare ESXi
# You can find the CPU utilization of windows system by querying the oid
# Give also the number of core (number of element in the table)
try:
cpu_stats = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
cpu_stats = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name],
bulk=True)
except KeyError:
self.reset()
@ -116,10 +116,10 @@ class Plugin(GlancesPlugin):
else:
# Default behavor
try:
self.stats = self.set_stats_snmp(
snmp_oid=snmp_oid[self.get_short_system_name()])
self.stats = self.get_stats_snmp(
snmp_oid=snmp_oid[self.short_system_name])
except KeyError:
self.stats = self.set_stats_snmp(
self.stats = self.get_stats_snmp(
snmp_oid=snmp_oid['default'])
if self.stats['idle'] == '':
@ -140,7 +140,7 @@ class Plugin(GlancesPlugin):
return self.stats
def update_views(self):
"""Update stats views"""
"""Update stats views."""
# Call the father's method
GlancesPlugin.update_views(self)
@ -149,9 +149,8 @@ class Plugin(GlancesPlugin):
for key in ['user', 'system', 'iowait']:
if key in self.stats:
self.views[key]['decoration'] = self.get_alert_log(self.stats[key], header=key)
self.views['total']['decoration'] = self.get_alert_log(self.stats['total'], header="system")
# Alert only
for key in ['steal']:
for key in ['steal', 'total']:
if key in self.stats:
self.views[key]['decoration'] = self.get_alert(self.stats[key], header=key)
# Optional
@ -160,12 +159,12 @@ class Plugin(GlancesPlugin):
self.views[key]['optional'] = True
def msg_curse(self, args=None):
"""Return the list to display in the UI"""
"""Return the list to display in the UI."""
# Init the return message
ret = []
# Only process if stats exist...
if self.stats == {}:
if not self.stats:
return ret
# Build the string message
@ -173,7 +172,7 @@ class Plugin(GlancesPlugin):
# exemple on Windows OS)
idle_tag = 'user' not in self.stats
# Header
msg = '{0:8}'.format(_("CPU"))
msg = '{0:8}'.format('CPU')
ret.append(self.curse_add_line(msg, "TITLE"))
# Total CPU usage
msg = '{0:>5}%'.format(self.stats['total'])
@ -184,7 +183,7 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_add_line(msg))
# Nice CPU
if 'nice' in self.stats:
msg = ' {0:8}'.format(_("nice:"))
msg = ' {0:8}'.format('nice:')
ret.append(self.curse_add_line(msg, optional=self.get_views(key='nice', option='optional')))
msg = '{0:>5}%'.format(self.stats['nice'])
ret.append(self.curse_add_line(msg, optional=self.get_views(key='nice', option='optional')))
@ -192,19 +191,19 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_new_line())
# User CPU
if 'user' in self.stats:
msg = '{0:8}'.format(_("user:"))
msg = '{0:8}'.format('user:')
ret.append(self.curse_add_line(msg))
msg = '{0:>5}%'.format(self.stats['user'])
ret.append(self.curse_add_line(
msg, self.get_views(key='user', option='decoration')))
elif 'idle' in self.stats:
msg = '{0:8}'.format(_("idle:"))
msg = '{0:8}'.format('idle:')
ret.append(self.curse_add_line(msg))
msg = '{0:>5}%'.format(self.stats['idle'])
ret.append(self.curse_add_line(msg))
# IRQ CPU
if 'irq' in self.stats:
msg = ' {0:8}'.format(_("irq:"))
msg = ' {0:8}'.format('irq:')
ret.append(self.curse_add_line(msg, optional=self.get_views(key='irq', option='optional')))
msg = '{0:>5}%'.format(self.stats['irq'])
ret.append(self.curse_add_line(msg, optional=self.get_views(key='irq', option='optional')))
@ -212,19 +211,19 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_new_line())
# System CPU
if 'system' in self.stats and not idle_tag:
msg = '{0:8}'.format(_("system:"))
msg = '{0:8}'.format('system:')
ret.append(self.curse_add_line(msg))
msg = '{0:>5}%'.format(self.stats['system'])
ret.append(self.curse_add_line(
msg, self.get_views(key='system', option='decoration')))
else:
msg = '{0:8}'.format(_("core:"))
msg = '{0:8}'.format('core:')
ret.append(self.curse_add_line(msg))
msg = '{0:>6}'.format(self.stats['nb_log_core'])
ret.append(self.curse_add_line(msg))
# IOWait CPU
if 'iowait' in self.stats:
msg = ' {0:8}'.format(_("iowait:"))
msg = ' {0:8}'.format('iowait:')
ret.append(self.curse_add_line(msg, optional=self.get_views(key='iowait', option='optional')))
msg = '{0:>5}%'.format(self.stats['iowait'])
ret.append(self.curse_add_line(
@ -234,13 +233,13 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_new_line())
# Idle CPU
if 'idle' in self.stats and not idle_tag:
msg = '{0:8}'.format(_("idle:"))
msg = '{0:8}'.format('idle:')
ret.append(self.curse_add_line(msg))
msg = '{0:>5}%'.format(self.stats['idle'])
ret.append(self.curse_add_line(msg))
# Steal CPU usage
if 'steal' in self.stats:
msg = ' {0:8}'.format(_("steal:"))
msg = ' {0:8}'.format('steal:')
ret.append(self.curse_add_line(msg, optional=self.get_views(key='steal', option='optional')))
msg = '{0:>5}%'.format(self.stats['steal'])
ret.append(self.curse_add_line(

View File

@ -21,12 +21,13 @@
import operator
import psutil
# Import Glances libs
from glances.core.glances_timer import getTimeSinceLastUpdate
from glances.plugins.glances_plugin import GlancesPlugin
import psutil
# Define the history items list
# All items in this list will be historised if the --enable-history tag is set
# 'color' define the graph color in #RGB format
@ -36,7 +37,7 @@ items_history_list = [{'name': 'read_bytes', 'color': '#00FF00', 'y_unit': 'B/s'
class Plugin(GlancesPlugin):
"""Glances' disks I/O plugin.
"""Glances disks I/O plugin.
stats is a list
"""
@ -53,7 +54,7 @@ class Plugin(GlancesPlugin):
self.reset()
def get_key(self):
"""Return the key of the list"""
"""Return the key of the list."""
return 'disk_name'
def reset(self):
@ -66,7 +67,7 @@ class Plugin(GlancesPlugin):
# Reset stats
self.reset()
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats using the standard system lib
# Grab the stat using the PsUtil disk_io_counters method
# read_count: number of reads
@ -96,16 +97,15 @@ class Plugin(GlancesPlugin):
diskio_new = diskiocounters
for disk in diskio_new:
try:
# Try necessary to manage dynamic disk creation/del
diskstat = {}
diskstat['time_since_update'] = time_since_update
diskstat['disk_name'] = disk
diskstat['read_bytes'] = (
diskio_new[disk].read_bytes -
read_bytes = (diskio_new[disk].read_bytes -
self.diskio_old[disk].read_bytes)
diskstat['write_bytes'] = (
diskio_new[disk].write_bytes -
write_bytes = (diskio_new[disk].write_bytes -
self.diskio_old[disk].write_bytes)
diskstat = {
'time_since_update': time_since_update,
'disk_name': disk,
'read_bytes': read_bytes,
'write_bytes': write_bytes}
except KeyError:
continue
else:
@ -114,7 +114,7 @@ class Plugin(GlancesPlugin):
# Save stats to compute next bitrate
self.diskio_old = diskio_new
elif self.get_input() == 'snmp':
elif self.input_method == 'snmp':
# Update stats using SNMP
# No standard way for the moment...
pass
@ -128,7 +128,7 @@ class Plugin(GlancesPlugin):
return self.stats
def update_views(self):
"""Update stats views"""
"""Update stats views."""
# Call the father's method
GlancesPlugin.update_views(self)
@ -152,11 +152,11 @@ class Plugin(GlancesPlugin):
# Build the string message
# Header
msg = '{0:9}'.format(_("DISK I/O"))
msg = '{0:9}'.format('DISK I/O')
ret.append(self.curse_add_line(msg, "TITLE"))
msg = '{0:>7}'.format(_("R/s"))
msg = '{0:>7}'.format('R/s')
ret.append(self.curse_add_line(msg))
msg = '{0:>7}'.format(_("W/s"))
msg = '{0:>7}'.format('W/s')
ret.append(self.curse_add_line(msg))
# Disk list (sorted by name)
for i in sorted(self.stats, key=operator.itemgetter(self.get_key())):

View File

@ -19,8 +19,13 @@
"""Docker plugin."""
import numbers
import os
import re
# Import Glances libs
from glances.core.glances_logging import logger
from glances.core.glances_timer import getTimeSinceLastUpdate
from glances.plugins.glances_plugin import GlancesPlugin
# Docker-py library (optional and Linux-only)
@ -33,14 +38,11 @@ except ImportError as e:
docker_tag = False
else:
docker_tag = True
import os
import re
import numbers
class Plugin(GlancesPlugin):
"""Glances' Docker plugin.
"""Glances Docker plugin.
stats is a list
"""
@ -59,7 +61,7 @@ class Plugin(GlancesPlugin):
self.docker_client = False
def connect(self, version=None):
"""Connect to the Docker server"""
"""Connect to the Docker server."""
# Init connection to the Docker API
try:
if version is None:
@ -82,7 +84,6 @@ class Plugin(GlancesPlugin):
# API error (Version mismatch ?)
logger.debug("Docker API error (%s)" % e)
# Try the connection with the server version
import re
version = re.search('server\:\ (.*)\)\".*\)', str(e))
if version:
logger.debug("Try connection with Docker API version %s" % version.group(1))
@ -112,8 +113,7 @@ class Plugin(GlancesPlugin):
@GlancesPlugin._log_result_decorator
def update(self):
"""Update Docker stats using the input method.
"""
"""Update Docker stats using the input method."""
# Reset stats
self.reset()
@ -129,7 +129,7 @@ class Plugin(GlancesPlugin):
if not docker_tag or (self.args is not None and self.args.disable_docker):
return self.stats
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats
# Exemple: {
# "KernelVersion": "3.16.4-tinycore64",
@ -150,26 +150,52 @@ class Plugin(GlancesPlugin):
# u'Names': [u'/webstack_nginx_1'],
# u'Id': u'b0da859e84eb4019cf1d965b15e9323006e510352c402d2f442ea632d61faaa5'}]
self.stats['containers'] = self.docker_client.containers()
# Get CPU and MEMORY stats for containers
# Get stats for all containers
for c in self.stats['containers']:
c['cpu'] = self.get_docker_cpu(c['Id'])
c['memory'] = self.get_docker_memory(c['Id'])
if not hasattr(self, 'docker_stats'):
# Create a dict with all the containers' stats instance
self.docker_stats = {}
elif self.get_input() == 'snmp':
# TODO: Find a way to correct this
# The following optimization is not compatible with the network stats
# The self.docker_client.stats method should be call every time in order to have network stats refreshed
# Nevertheless, if we call it every time, Glances is slow...
if c['Id'] not in self.docker_stats:
# Create the stats instance for the current container
try:
self.docker_stats[c['Id']] = self.docker_client.stats(c['Id'], decode=True)
logger.debug("Create Docker stats object for container {}".format(c['Id']))
except (AttributeError, docker.errors.InvalidVersion) as e:
logger.error("Can not call Docker stats method {}".format(e))
# Get the docker stats
try:
# self.docker_stats[c['Id']] = self.docker_client.stats(c['Id'], decode=True)
all_stats = self.docker_stats[c['Id']].next()
except Exception:
all_stats = {}
c['cpu'] = self.get_docker_cpu(c['Id'], all_stats)
c['memory'] = self.get_docker_memory(c['Id'], all_stats)
# c['network'] = self.get_docker_network(c['Id'], all_stats)
elif self.input_method == 'snmp':
# Update stats using SNMP
# Not available
pass
return self.stats
def get_docker_cpu(self, id):
"""Return the container CPU usage by reading /sys/fs/cgroup/...
def get_docker_cpu_old(self, container_id):
"""Return the container CPU usage by reading /sys/fs/cgroup/.
Input: id is the full container id
Output: a dict {'total': 1.49, 'user': 0.65, 'system': 0.84}"""
Output: a dict {'total': 1.49, 'user': 0.65, 'system': 0.84}
"""
ret = {}
# Read the stats
try:
with open('/sys/fs/cgroup/cpuacct/docker/' + id + '/cpuacct.stat', 'r') as f:
with open('/sys/fs/cgroup/cpuacct/docker/' + container_id + '/cpuacct.stat', 'r') as f:
for line in f:
m = re.search(r"(system|user)\s+(\d+)", line)
if m:
@ -177,23 +203,77 @@ class Plugin(GlancesPlugin):
except IOError as e:
logger.error("Can not grab container CPU stat ({0})".format(e))
return ret
# Get the user ticks
ticks = self.get_user_ticks()
if isinstance(ret["system"], numbers.Number) and isinstance(ret["user"], numbers.Number):
ret["total"] = ret["system"] + ret["user"]
for k in ret.keys():
ret[k] = float(ret[k]) / ticks
# Return the stats
return ret
def get_docker_memory(self, id):
"""Return the container MEMORY usage by reading /sys/fs/cgroup/...
def get_docker_cpu(self, container_id, all_stats):
"""Return the container CPU usage.
Input: id is the full container id
Output: a dict {'rss': 1015808, 'cache': 356352}"""
all_stats is the output of the stats method of the Docker API
Output: a dict {'total': 1.49}
"""
cpu_new = {}
ret = {'total': 0.0}
# Read the stats
# For each container, you will find a pseudo-file cpuacct.stat,
# containing the CPU usage accumulated by the processes of the container.
# Those times are expressed in ticks of 1/USER_HZ of a second.
# On x86 systems, USER_HZ is 100.
try:
cpu_new['total'] = all_stats['cpu_stats']['cpu_usage']['total_usage']
cpu_new['system'] = all_stats['cpu_stats']['system_cpu_usage']
cpu_new['nb_core'] = len(all_stats['cpu_stats']['cpu_usage']['percpu_usage'])
except KeyError as e:
# all_stats do not have CPU information
logger.debug("Can not grab CPU usage for container {0} ({1}). Trying fallback method.".format(container_id, e))
# Trying fallback to old grab method
ret = self.get_docker_cpu_old(container_id)
# Get the user ticks
ticks = self.get_user_ticks()
for k in ret.keys():
ret[k] = float(ret[k]) / ticks
else:
# Previous CPU stats stored in the cpu_old variable
if not hasattr(self, 'cpu_old'):
# First call, we init the cpu_old variable
self.cpu_old = {}
try:
self.cpu_old[container_id] = cpu_new
except (IOError, UnboundLocalError):
pass
if container_id not in self.cpu_old:
try:
self.cpu_old[container_id] = cpu_new
except (IOError, UnboundLocalError):
pass
else:
#
cpu_delta = float(cpu_new['total'] - self.cpu_old[container_id]['total'])
system_delta = float(cpu_new['system'] - self.cpu_old[container_id]['system'])
if cpu_delta > 0.0 and system_delta > 0.0:
ret['total'] = (cpu_delta / system_delta) * float(cpu_new['nb_core']) * 100
# Save stats to compute next stats
self.cpu_old[container_id] = cpu_new
# Return the stats
return ret
def get_docker_memory_old(self, container_id):
"""Return the container MEMORY usage by reading /sys/fs/cgroup/.
Input: id is the full container id
Output: a dict {'rss': 1015808, 'cache': 356352}
"""
ret = {}
# Read the stats
try:
with open('/sys/fs/cgroup/memory/docker/' + id + '/memory.stat', 'r') as f:
with open('/sys/fs/cgroup/memory/docker/' + container_id + '/memory.stat', 'r') as f:
for line in f:
m = re.search(r"(rss|cache)\s+(\d+)", line)
if m:
@ -204,8 +284,78 @@ class Plugin(GlancesPlugin):
# Return the stats
return ret
def get_docker_memory(self, container_id, all_stats):
"""Return the container MEMORY.
Input: id is the full container id
all_stats is the output of the stats method of the Docker API
Output: a dict {'rss': 1015808, 'cache': 356352, 'usage': ..., 'max_usage': ...}
"""
ret = {}
# Read the stats
try:
ret['rss'] = all_stats['memory_stats']['stats']['rss']
ret['cache'] = all_stats['memory_stats']['stats']['cache']
ret['usage'] = all_stats['memory_stats']['usage']
ret['max_usage'] = all_stats['memory_stats']['max_usage']
except KeyError as e:
# all_stats do not have MEM information
logger.debug("Can not grab MEM usage for container {0} ({1}). Trying fallback method.".format(container_id, e))
# Trying fallback to old grab method
ret = self.get_docker_memory_old(container_id)
# Return the stats
return ret
def get_docker_network(self, container_id, all_stats):
"""Return the container network usage using the Docker API (v1.0 or higher).
Input: id is the full container id
Output: a dict {'time_since_update': 3000, 'rx': 10, 'tx': 65}.
"""
# Init the returned dict
network_new = {}
# Read the rx/tx stats (in bytes)
try:
netiocounters = all_stats["network"]
except KeyError as e:
# all_stats do not have NETWORK information
logger.debug("Can not grab NET usage for container {0} ({1})".format(container_id, e))
# No fallback available...
return network_new
# Previous network interface stats are stored in the network_old variable
if not hasattr(self, 'netiocounters_old'):
# First call, we init the network_old var
self.netiocounters_old = {}
try:
self.netiocounters_old[container_id] = netiocounters
except (IOError, UnboundLocalError):
pass
if container_id not in self.netiocounters_old:
try:
self.netiocounters_old[container_id] = netiocounters
except (IOError, UnboundLocalError):
pass
else:
# By storing time data we enable Rx/s and Tx/s calculations in the
# XML/RPC API, which would otherwise be overly difficult work
# for users of the API
network_new['time_since_update'] = getTimeSinceLastUpdate('docker_net_{}'.format(container_id))
network_new['rx'] = netiocounters["rx_bytes"] - self.netiocounters_old[container_id]["rx_bytes"]
network_new['tx'] = netiocounters["tx_bytes"] - self.netiocounters_old[container_id]["tx_bytes"]
network_new['cumulative_rx'] = netiocounters["rx_bytes"]
network_new['cumulative_tx'] = netiocounters["tx_bytes"]
# Save stats to compute next bitrate
self.netiocounters_old[container_id] = netiocounters
# Return the stats
return network_new
def get_user_ticks(self):
"""return the user ticks by reading the environment variable"""
"""Return the user ticks by reading the environment variable."""
return os.sysconf(os.sysconf_names['SC_CLK_TCK'])
def msg_curse(self, args=None):
@ -214,32 +364,36 @@ class Plugin(GlancesPlugin):
ret = []
# Only process if stats exist (and non null) and display plugin enable...
if self.stats == {} or args.disable_docker or len(self.stats['containers']) == 0:
if not self.stats or args.disable_docker or len(self.stats['containers']) == 0:
return ret
# Build the string message
# Title
msg = '{0}'.format(_("CONTAINERS"))
msg = '{0}'.format('CONTAINERS')
ret.append(self.curse_add_line(msg, "TITLE"))
msg = ' {0}'.format(len(self.stats['containers']))
ret.append(self.curse_add_line(msg))
msg = ' ({0} {1})'.format(_("served by Docker"),
msg = ' ({0} {1})'.format('served by Docker',
self.stats['version']["Version"])
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
# Header
ret.append(self.curse_new_line())
msg = '{0:>14}'.format(_("Id"))
msg = '{0:>14}'.format('Id')
ret.append(self.curse_add_line(msg))
msg = ' {0:20}'.format(_("Name"))
msg = ' {0:20}'.format('Name')
ret.append(self.curse_add_line(msg))
msg = '{0:>26}'.format(_("Status"))
msg = '{0:>26}'.format('Status')
ret.append(self.curse_add_line(msg))
msg = '{0:>6}'.format(_("CPU%"))
msg = '{0:>6}'.format('CPU%')
ret.append(self.curse_add_line(msg))
msg = '{0:>6}'.format(_("MEM"))
msg = '{0:>7}'.format('MEM')
ret.append(self.curse_add_line(msg))
msg = ' {0:8}'.format(_("Command"))
# msg = '{0:>6}'.format('Rx/s')
# ret.append(self.curse_add_line(msg))
# msg = '{0:>6}'.format('Tx/s')
# ret.append(self.curse_add_line(msg))
msg = ' {0:8}'.format('Command')
ret.append(self.curse_add_line(msg))
# Data
for container in self.stats['containers']:
@ -252,7 +406,7 @@ class Plugin(GlancesPlugin):
if len(name) > 20:
name = '_' + name[:-19]
else:
name[0:20]
name = name[:20]
msg = ' {0:20}'.format(name)
ret.append(self.curse_add_line(msg))
# Status
@ -268,10 +422,18 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_add_line(msg))
# MEM
try:
msg = '{0:>6}'.format(self.auto_unit(container['memory']['rss']))
msg = '{0:>7}'.format(self.auto_unit(container['memory']['usage']))
except KeyError:
msg = '{0:>6}'.format('?')
msg = '{0:>7}'.format('?')
ret.append(self.curse_add_line(msg))
# NET RX/TX
# for r in ['rx', 'tx']:
# try:
# value = self.auto_unit(int(container['network'][r] // container['network']['time_since_update'] * 8)) + "b"
# msg = '{0:>6}'.format(value)
# except KeyError:
# msg = '{0:>6}'.format('?')
# ret.append(self.curse_add_line(msg))
# Command
msg = ' {0}'.format(container['Command'])
ret.append(self.curse_add_line(msg))
@ -279,7 +441,7 @@ class Plugin(GlancesPlugin):
return ret
def container_alert(self, status):
"""Analyse the container status"""
"""Analyse the container status."""
if "Paused" in status:
return 'CAREFUL'
else:

View File

@ -21,10 +21,10 @@
import operator
from glances.plugins.glances_plugin import GlancesPlugin
import psutil
from glances.plugins.glances_plugin import GlancesPlugin
from glances.core.glances_logging import logger
# SNMP OID
# The snmpd.conf needs to be edited.
@ -64,7 +64,7 @@ items_history_list = [{'name': 'percent', 'color': '#00FF00'}]
class Plugin(GlancesPlugin):
"""Glances' file system plugin.
"""Glances file system plugin.
stats is a list
"""
@ -81,7 +81,7 @@ class Plugin(GlancesPlugin):
self.reset()
def get_key(self):
"""Return the key of the list"""
"""Return the key of the list."""
return 'mnt_point'
def reset(self):
@ -94,7 +94,7 @@ class Plugin(GlancesPlugin):
# Reset the list
self.reset()
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats using the standard system lib
# Grab the stats using the PsUtil disk_partitions
@ -105,12 +105,17 @@ class Plugin(GlancesPlugin):
except UnicodeDecodeError:
return self.stats
# Optionnal hack to allow logicals mounts points (issue #448)
# Ex: Had to put 'allow=zfs' in the [fs] section of the conf file
# to allow zfs monitoring
for fstype in self.get_conf_value('allow'):
try:
fs_stat += [f for f in psutil.disk_partitions(all=True) if f.fstype.find(fstype) >= 0]
except UnicodeDecodeError:
return self.stats
# Loop over fs
for fs in fs_stat:
fs_current = {}
fs_current['device_name'] = fs.device
fs_current['fs_type'] = fs.fstype
fs_current['mnt_point'] = fs.mountpoint
# Grab the disk usage
try:
fs_usage = psutil.disk_usage(fs.mountpoint)
@ -118,52 +123,56 @@ class Plugin(GlancesPlugin):
# Correct issue #346
# Disk is ejected during the command
continue
fs_current['size'] = fs_usage.total
fs_current['used'] = fs_usage.used
fs_current['free'] = fs_usage.total - fs_usage.used
fs_current['percent'] = fs_usage.percent
fs_current['key'] = self.get_key()
fs_current = {
'device_name': fs.device,
'fs_type': fs.fstype,
'mnt_point': fs.mountpoint,
'size': fs_usage.total,
'used': fs_usage.used,
'free': fs_usage.total - fs_usage.used,
'percent': fs_usage.percent,
'key': self.get_key()}
self.stats.append(fs_current)
elif self.get_input() == 'snmp':
elif self.input_method == 'snmp':
# Update stats using SNMP
# SNMP bulk command to get all file system in one shot
try:
fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name],
bulk=True)
except KeyError:
fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid['default'],
fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid['default'],
bulk=True)
# Loop over fs
if self.get_short_system_name() in ('windows', 'esxi'):
if self.short_system_name in ('windows', 'esxi'):
# Windows or ESXi tips
for fs in fs_stat:
# Memory stats are grabed in the same OID table (ignore it)
# Memory stats are grabbed in the same OID table (ignore it)
if fs == 'Virtual Memory' or fs == 'Physical Memory' or fs == 'Real Memory':
continue
fs_current = {}
fs_current['device_name'] = ''
fs_current['mnt_point'] = fs.partition(' ')[0]
fs_current['size'] = int(
fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
fs_current['used'] = int(
fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit'])
fs_current['percent'] = float(
fs_current['used'] * 100 / fs_current['size'])
fs_current['key'] = self.get_key()
size = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
used = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit'])
percent = float(used * 100 / size)
fs_current = {
'device_name': '',
'mnt_point': fs.partition(' ')[0],
'size': size,
'used': used,
'percent': percent,
'key': self.get_key()}
self.stats.append(fs_current)
else:
# Default behavor
# Default behavior
for fs in fs_stat:
fs_current = {}
fs_current['device_name'] = fs_stat[fs]['device_name']
fs_current['mnt_point'] = fs
fs_current['size'] = int(fs_stat[fs]['size']) * 1024
fs_current['used'] = int(fs_stat[fs]['used']) * 1024
fs_current['percent'] = float(fs_stat[fs]['percent'])
fs_current['key'] = self.get_key()
fs_current = {
'device_name': fs_stat[fs]['device_name'],
'mnt_point': fs,
'size': int(fs_stat[fs]['size']) * 1024,
'used': int(fs_stat[fs]['used']) * 1024,
'percent': float(fs_stat[fs]['percent']),
'key': self.get_key()}
self.stats.append(fs_current)
# Update the history list
@ -175,14 +184,15 @@ class Plugin(GlancesPlugin):
return self.stats
def update_views(self):
"""Update stats views"""
"""Update stats views."""
# Call the father's method
GlancesPlugin.update_views(self)
# Add specifics informations
# Alert
for i in self.stats:
self.views[i[self.get_key()]]['used']['decoration'] = self.get_alert(i['used'], max=i['size'], header=i['mnt_point'])
self.views[i[self.get_key()]]['used']['decoration'] = self.get_alert(
i['used'], maximum=i['size'], header=i['mnt_point'])
def msg_curse(self, args=None, max_width=None):
"""Return the dict to display in the curse interface."""
@ -202,14 +212,14 @@ class Plugin(GlancesPlugin):
# Build the string message
# Header
msg = '{0:{width}}'.format(_("FILE SYS"), width=fsname_max_width)
msg = '{0:{width}}'.format('FILE SYS', width=fsname_max_width)
ret.append(self.curse_add_line(msg, "TITLE"))
if args.fs_free_space:
msg = '{0:>7}'.format(_("Free"))
msg = '{0:>7}'.format('Free')
else:
msg = '{0:>7}'.format(_("Used"))
msg = '{0:>7}'.format('Used')
ret.append(self.curse_add_line(msg))
msg = '{0:>7}'.format(_("Total"))
msg = '{0:>7}'.format('Total')
ret.append(self.curse_add_line(msg))
# Disk list (sorted by name)

View File

@ -24,12 +24,13 @@ import os
import socket
# Import Glances libs
from glances.core.glances_logging import logger
from glances.plugins.glances_plugin import GlancesPlugin
class Plugin(GlancesPlugin):
"""Glances' HDD temperature sensors plugin.
"""Glances HDD temperature sensors plugin.
stats is a list
"""
@ -39,7 +40,7 @@ class Plugin(GlancesPlugin):
GlancesPlugin.__init__(self, args=args)
# Init the sensor class
self.glancesgrabhddtemp = GlancesGrabHDDTemp()
self.glancesgrabhddtemp = GlancesGrabHDDTemp(args=args)
# We do not want to display the stat in a dedicated area
# The HDD temp is displayed within the sensors plugin
@ -57,7 +58,7 @@ class Plugin(GlancesPlugin):
# Reset stats
self.reset()
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats using the standard system lib
self.stats = self.glancesgrabhddtemp.get()
@ -73,8 +74,9 @@ class GlancesGrabHDDTemp(object):
"""Get hddtemp stats using a socket connection."""
def __init__(self, host='127.0.0.1', port=7634):
def __init__(self, host='127.0.0.1', port=7634, args=None):
"""Init hddtemp stats."""
self.args = args
self.host = host
self.port = port
self.cache = ""
@ -89,6 +91,10 @@ class GlancesGrabHDDTemp(object):
# Reset the list
self.reset()
# Only update if --disable-hddtemp is not set
if self.args is None or self.args.disable_hddtemp:
return
# Fetch the data
data = self.fetch()
@ -125,7 +131,10 @@ class GlancesGrabHDDTemp(object):
sck.connect((self.host, self.port))
data = sck.recv(4096)
sck.close()
except socket.error:
except socket.error as e:
logger.warning("Can not connect to an HDDtemp server ({0}:{1} => {2})".format(self.host, self.port, e))
logger.debug("Disable the HDDtemp module. Use the --disable-hddtemp to hide the previous message.")
self.args.disable_hddtemp = True
data = ""
return data

View File

@ -30,7 +30,7 @@ from glances.plugins.glances_plugin import GlancesPlugin
class Plugin(GlancesPlugin):
"""Glances' help plugin."""
"""Glances help plugin."""
def __init__(self, args=None, config=None):
"""Init the plugin."""
@ -42,10 +42,60 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# init data dictionary
self.view_data = {}
self.generate_view_data()
def update(self):
"""No stats. It is just a plugin to display the help."""
pass
def generate_view_data(self):
self.view_data['version'] = '{0} {1}'.format(appname.title(), version)
self.view_data['psutil_version'] = ' with PSutil {0}'.format(psutil_version)
try:
self.view_data['configuration_file'] = 'Configuration file: {0}'.format(self.config.loaded_config_file)
except AttributeError:
pass
msg_col = ' {0:1} {1:35}'
msg_col2 = ' {0:1} {1:35}'
self.view_data['sort_auto'] = msg_col.format('a', 'Sort processes automatically')
self.view_data['sort_network'] = msg_col2.format('b', 'Bytes or bits for network I/O')
self.view_data['sort_cpu'] = msg_col.format('c', 'Sort processes by CPU%')
self.view_data['show_hide_alert'] = msg_col2.format('l', 'Show/hide alert logs')
self.view_data['sort_mem'] = msg_col.format('m', 'Sort processes by MEM%')
self.view_data['sort_user'] = msg_col.format('u', 'Sort processes by USER')
self.view_data['delete_warning_alerts'] = msg_col2.format('w', 'Delete warning alerts')
self.view_data['sort_proc'] = msg_col.format('p', 'Sort processes by name')
self.view_data['delete_warning_critical_alerts'] = msg_col2.format('x', 'Delete warning and critical alerts')
self.view_data['sort_io'] = msg_col.format('i', 'Sort processes by I/O rate')
self.view_data['percpu'] = msg_col2.format('1', 'Global CPU or per-CPU stats')
self.view_data['sort_cpu_times'] = msg_col.format('t', 'Sort processes by TIME')
self.view_data['show_hide_help'] = msg_col2.format('h', 'Show/hide this help screen')
self.view_data['show_hide_diskio'] = msg_col.format('d', 'Show/hide disk I/O stats')
self.view_data['view_network_io_combination'] = msg_col2.format('T', 'View network I/O as combination')
self.view_data['show_hide_filesystem'] = msg_col.format('f', 'Show/hide filesystem stats')
self.view_data['view_cumulative_network'] = msg_col2.format('U', 'View cumulative network I/O')
self.view_data['show_hide_network'] = msg_col.format('n', 'Show/hide network stats')
self.view_data['show_hide_filesytem_freespace'] = msg_col2.format('F', 'Show filesystem free space')
self.view_data['show_hide_sensors'] = msg_col.format('s', 'Show/hide sensors stats')
self.view_data['generate_graphs'] = msg_col2.format('g', 'Generate graphs for current history')
self.view_data['show_hide_left_sidebar'] = msg_col.format('2', 'Show/hide left sidebar')
self.view_data['reset_history'] = msg_col2.format('r', 'Reset history')
self.view_data['enable_disable_process_stats'] = msg_col.format('z', 'Enable/disable processes stats')
self.view_data['quit'] = msg_col2.format('q', 'Quit (Esc and Ctrl-C also work)')
self.view_data['enable_disable_top_extends_stats'] = msg_col.format('e', 'Enable/disable top extended stats')
self.view_data['enable_disable_short_processname'] = msg_col.format('/', 'Enable/disable short processes name')
self.view_data['enable_disable_docker'] = msg_col2.format('D', 'Enable/disable Docker stats')
self.view_data['enable_disable_quick_look'] = msg_col.format('3', 'Enable/disable quick look plugin')
self.view_data['show_hide_ip'] = msg_col2.format('I', 'Show/hide IP module')
self.view_data['edit_pattern_filter'] = 'ENTER: Edit the process filter pattern'
def get_view_data(self, args=None):
return self.view_data
def msg_curse(self, args=None):
"""Return the list to display in the curse interface."""
# Init the return message
@ -53,101 +103,69 @@ class Plugin(GlancesPlugin):
# Build the string message
# Header
msg = '{0} {1}'.format(appname.title(), version)
ret.append(self.curse_add_line(msg, "TITLE"))
msg = _(" with PSutil {0}").format(psutil_version)
ret.append(self.curse_add_line(msg))
ret.append(self.curse_add_line(self.view_data['version'], 'TITLE'))
ret.append(self.curse_add_line(self.view_data['psutil_version']))
ret.append(self.curse_new_line())
# Configuration file path
try:
msg = '{0}: {1}'.format(_("Configuration file"), self.config.get_loaded_config_file())
except AttributeError:
pass
else:
if 'configuration_file' in self.view_data:
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(msg))
ret.append(self.curse_add_line(self.view_data['configuration_file']))
ret.append(self.curse_new_line())
# Keys
msg_col = ' {0:1} {1:35}'
msg_col2 = ' {0:1} {1:35}'
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['sort_auto']))
ret.append(self.curse_add_line(self.view_data['sort_network']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['sort_cpu']))
ret.append(self.curse_add_line(self.view_data['show_hide_alert']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['sort_mem']))
ret.append(self.curse_add_line(self.view_data['delete_warning_alerts']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['sort_user']))
ret.append(self.curse_add_line(self.view_data['delete_warning_critical_alerts']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['sort_proc']))
ret.append(self.curse_add_line(self.view_data['percpu']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['sort_io']))
ret.append(self.curse_add_line(self.view_data['show_hide_ip']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['sort_cpu_times']))
ret.append(self.curse_add_line(self.view_data['enable_disable_docker']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['show_hide_diskio']))
ret.append(self.curse_add_line(self.view_data['view_network_io_combination']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['show_hide_filesystem']))
ret.append(self.curse_add_line(self.view_data['view_cumulative_network']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['show_hide_network']))
ret.append(self.curse_add_line(self.view_data['show_hide_filesytem_freespace']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['show_hide_sensors']))
ret.append(self.curse_add_line(self.view_data['generate_graphs']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['show_hide_left_sidebar']))
ret.append(self.curse_add_line(self.view_data['reset_history']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['enable_disable_process_stats']))
ret.append(self.curse_add_line(self.view_data['show_hide_help']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['enable_disable_quick_look']))
ret.append(self.curse_add_line(self.view_data['quit']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['enable_disable_top_extends_stats']))
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(self.view_data['enable_disable_short_processname']))
ret.append(self.curse_new_line())
ret.append(self.curse_new_line())
msg = msg_col.format("a", _("Sort processes automatically"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("b", _("Bytes or bits for network I/O"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("c", _("Sort processes by CPU%"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("l", _("Show/hide alert logs"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("m", _("Sort processes by MEM%"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("w", _("Delete warning alerts"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("p", _("Sort processes by name"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("x", _("Delete warning and critical alerts"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("i", _("Sort processes by I/O rate"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("1", _("Global CPU or per-CPU stats"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("t", _("Sort processes by CPU times"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("h", _("Show/hide this help screen"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("d", _("Show/hide disk I/O stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("T", _("View network I/O as combination"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("f", _("Show/hide filesystem stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("u", _("View cumulative network I/O"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("n", _("Show/hide network stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("F", _("Show filesystem free space"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("s", _("Show/hide sensors stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("g", _("Generate graphs for current history"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("2", _("Show/hide left sidebar"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("r", _("Reset history"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("z", _("Enable/disable processes stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("q", _("Quit (Esc and Ctrl-C also work)"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("e", _("Enable/disable top extended stats"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("/", _("Enable/disable short processes name"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("D", _("Enable/disable Docker stats"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
ret.append(self.curse_new_line())
msg = '{0}: {1}'.format("ENTER", _("Edit the process filter pattern"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_add_line(self.view_data['edit_pattern_filter']))
# Return the message with decoration
return ret

View File

@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
#
# 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 <http://www.gnu.org/licenses/>.
"""IP plugin."""
# Import system libs
try:
import netifaces
netifaces_tag = True
except ImportError:
netifaces_tag = False
# Import Glances libs
from glances.core.glances_logging import logger
from glances.plugins.glances_plugin import GlancesPlugin
class Plugin(GlancesPlugin):
"""Glances IP Plugin.
stats is a dict
"""
def __init__(self, args=None):
"""Init the plugin."""
GlancesPlugin.__init__(self, args=args)
# We want to display the stat in the curse interface
self.display_curse = True
# Init the stats
self.reset()
def reset(self):
"""Reset/init the stats."""
self.stats = {}
@GlancesPlugin._log_result_decorator
def update(self):
"""Update IP stats using the input method.
Stats is dict
"""
# Reset stats
self.reset()
if self.input_method == 'local' and netifaces_tag:
# Update stats using the netifaces lib
try:
default_gw = netifaces.gateways()['default'][netifaces.AF_INET]
except KeyError:
logger.debug("Can not grab the default gateway")
else:
try:
self.stats['address'] = netifaces.ifaddresses(default_gw[1])[netifaces.AF_INET][0]['addr']
self.stats['mask'] = netifaces.ifaddresses(default_gw[1])[netifaces.AF_INET][0]['netmask']
self.stats['mask_cidr'] = self.ip_to_cidr(self.stats['mask'])
self.stats['gateway'] = netifaces.gateways()['default'][netifaces.AF_INET][0]
except KeyError as e:
logger.debug("Can not grab IP information (%s)".format(e))
elif self.input_method == 'snmp':
# Not implemented yet
pass
# Update the view
self.update_views()
return self.stats
def update_views(self):
"""Update stats views."""
# Call the father's method
GlancesPlugin.update_views(self)
# Add specifics informations
# Optional
for key in self.stats.keys():
self.views[key]['optional'] = True
def msg_curse(self, args=None):
"""Return the dict to display in the curse interface."""
# Init the return message
ret = []
# Only process if stats exist and display plugin enable...
if not self.stats or args.disable_ip:
return ret
# Build the string message
msg = ' - '
ret.append(self.curse_add_line(msg))
msg = 'IP '
ret.append(self.curse_add_line(msg, 'TITLE'))
msg = '{0:}/{1}'.format(self.stats['address'], self.stats['mask_cidr'])
ret.append(self.curse_add_line(msg))
return ret
@staticmethod
def ip_to_cidr(ip):
"""Convert IP address to CIDR.
Example: '255.255.255.0' will return 24
"""
return sum(map(lambda x: int(x) << 8, ip.split('.'))) // 8128

View File

@ -44,7 +44,7 @@ items_history_list = [{'name': 'min1', 'color': '#0000FF'},
class Plugin(GlancesPlugin):
"""Glances' load plugin.
"""Glances load plugin.
stats is a dict
"""
@ -76,7 +76,7 @@ class Plugin(GlancesPlugin):
# Reset stats
self.reset()
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats using the standard system lib
# Get the load using the os standard lib
@ -89,9 +89,9 @@ class Plugin(GlancesPlugin):
'min5': load[1],
'min15': load[2],
'cpucore': self.nb_log_core}
elif self.get_input() == 'snmp':
elif self.input_method == 'snmp':
# Update stats using SNMP
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid)
self.stats = self.get_stats_snmp(snmp_oid=snmp_oid)
if self.stats['min1'] == '':
self.reset()
@ -117,16 +117,16 @@ class Plugin(GlancesPlugin):
return self.stats
def update_views(self):
"""Update stats views"""
"""Update stats views."""
# Call the father's method
GlancesPlugin.update_views(self)
# Add specifics informations
try:
# Alert and log
self.views['min15']['decoration'] = self.get_alert_log(self.stats['min15'], max=100 * self.stats['cpucore'])
self.views['min15']['decoration'] = self.get_alert_log(self.stats['min15'], maximum=100 * self.stats['cpucore'])
# Alert only
self.views['min5']['decoration'] = self.get_alert(self.stats['min5'], max=100 * self.stats['cpucore'])
self.views['min5']['decoration'] = self.get_alert(self.stats['min5'], maximum=100 * self.stats['cpucore'])
except KeyError:
# try/except mandatory for Windows compatibility (no load stats)
pass
@ -137,28 +137,28 @@ class Plugin(GlancesPlugin):
ret = []
# Only process if stats exist...
if self.stats == {}:
if not self.stats:
return ret
# Build the string message
# Header
msg = '{0:8}'.format(_("LOAD"))
msg = '{0:8}'.format('LOAD')
ret.append(self.curse_add_line(msg, "TITLE"))
# Core number
if self.stats['cpucore'] > 0:
msg = _("{0:d}-core").format(int(self.stats['cpucore']), '>1')
msg = '{0}-core'.format(int(self.stats['cpucore']))
ret.append(self.curse_add_line(msg))
# New line
ret.append(self.curse_new_line())
# 1min load
msg = '{0:8}'.format(_("1 min:"))
msg = '{0:8}'.format('1 min:')
ret.append(self.curse_add_line(msg))
msg = '{0:>6.2f}'.format(self.stats['min1'])
ret.append(self.curse_add_line(msg))
# New line
ret.append(self.curse_new_line())
# 5min load
msg = '{0:8}'.format(_("5 min:"))
msg = '{0:8}'.format('5 min:')
ret.append(self.curse_add_line(msg))
msg = '{0:>6.2f}'.format(self.stats['min5'])
ret.append(self.curse_add_line(
@ -166,7 +166,7 @@ class Plugin(GlancesPlugin):
# New line
ret.append(self.curse_new_line())
# 15min load
msg = '{0:8}'.format(_("15 min:"))
msg = '{0:8}'.format('15 min:')
ret.append(self.curse_add_line(msg))
msg = '{0:>6.2f}'.format(self.stats['min15'])
ret.append(self.curse_add_line(

View File

@ -19,10 +19,10 @@
"""Virtual memory plugin."""
import psutil
from glances.plugins.glances_plugin import GlancesPlugin
import psutil
# SNMP OID
# Total RAM in machine: .1.3.6.1.4.1.2021.4.5.0
# Total RAM used: .1.3.6.1.4.1.2021.4.6.0
@ -78,7 +78,7 @@ class Plugin(GlancesPlugin):
# Reset stats
self.reset()
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats using the standard system lib
# Grab MEM using the PSUtil virtual_memory method
vm_stats = psutil.virtual_memory()
@ -112,18 +112,18 @@ class Plugin(GlancesPlugin):
self.stats['free'] += self.stats['cached']
# used=total-free
self.stats['used'] = self.stats['total'] - self.stats['free']
elif self.get_input() == 'snmp':
elif self.input_method == 'snmp':
# Update stats using SNMP
if self.get_short_system_name() in ('windows', 'esxi'):
if self.short_system_name in ('windows', 'esxi'):
# Mem stats for Windows|Vmware Esxi are stored in the FS table
try:
fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name],
bulk=True)
except KeyError:
self.reset()
else:
for fs in fs_stat:
# The Physical Memory (Windows) or Real Memory (VmWare)
# The Physical Memory (Windows) or Real Memory (VMware)
# gives statistics on RAM usage and availability.
if fs in ('Physical Memory', 'Real Memory'):
self.stats['total'] = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
@ -133,7 +133,7 @@ class Plugin(GlancesPlugin):
break
else:
# Default behavor for others OS
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default'])
self.stats = self.get_stats_snmp(snmp_oid=snmp_oid['default'])
if self.stats['total'] == '':
self.reset()
@ -161,13 +161,13 @@ class Plugin(GlancesPlugin):
return self.stats
def update_views(self):
"""Update stats views"""
"""Update stats views."""
# Call the father's method
GlancesPlugin.update_views(self)
# Add specifics informations
# Alert and log
self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], max=self.stats['total'])
self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], maximum=self.stats['total'])
# Optional
for key in ['active', 'inactive', 'buffers', 'cached']:
if key in self.stats:
@ -179,59 +179,59 @@ class Plugin(GlancesPlugin):
ret = []
# Only process if stats exist...
if self.stats == {}:
if not self.stats:
return ret
# Build the string message
# Header
msg = '{0:5} '.format(_("MEM"))
msg = '{0:5} '.format('MEM')
ret.append(self.curse_add_line(msg, "TITLE"))
# Percent memory usage
msg = '{0:>7.1%}'.format(self.stats['percent'] / 100)
ret.append(self.curse_add_line(msg))
# Active memory usage
if 'active' in self.stats:
msg = ' {0:9}'.format(_("active:"))
msg = ' {0:9}'.format('active:')
ret.append(self.curse_add_line(msg, optional=self.get_views(key='active', option='optional')))
msg = '{0:>7}'.format(self.auto_unit(self.stats['active']))
ret.append(self.curse_add_line(msg, optional=self.get_views(key='active', option='optional')))
# New line
ret.append(self.curse_new_line())
# Total memory usage
msg = '{0:6}'.format(_("total:"))
msg = '{0:6}'.format('total:')
ret.append(self.curse_add_line(msg))
msg = '{0:>7}'.format(self.auto_unit(self.stats['total']))
ret.append(self.curse_add_line(msg))
# Inactive memory usage
if 'inactive' in self.stats:
msg = ' {0:9}'.format(_("inactive:"))
msg = ' {0:9}'.format('inactive:')
ret.append(self.curse_add_line(msg, optional=self.get_views(key='inactive', option='optional')))
msg = '{0:>7}'.format(self.auto_unit(self.stats['inactive']))
ret.append(self.curse_add_line(msg, optional=self.get_views(key='inactive', option='optional')))
# New line
ret.append(self.curse_new_line())
# Used memory usage
msg = '{0:6}'.format(_("used:"))
msg = '{0:6}'.format('used:')
ret.append(self.curse_add_line(msg))
msg = '{0:>7}'.format(self.auto_unit(self.stats['used']))
ret.append(self.curse_add_line(
msg, self.get_views(key='used', option='decoration')))
# Buffers memory usage
if 'buffers' in self.stats:
msg = ' {0:9}'.format(_("buffers:"))
msg = ' {0:9}'.format('buffers:')
ret.append(self.curse_add_line(msg, optional=self.get_views(key='buffers', option='optional')))
msg = '{0:>7}'.format(self.auto_unit(self.stats['buffers']))
ret.append(self.curse_add_line(msg, optional=self.get_views(key='buffers', option='optional')))
# New line
ret.append(self.curse_new_line())
# Free memory usage
msg = '{0:6}'.format(_("free:"))
msg = '{0:6}'.format('free:')
ret.append(self.curse_add_line(msg))
msg = '{0:>7}'.format(self.auto_unit(self.stats['free']))
ret.append(self.curse_add_line(msg))
# Cached memory usage
if 'cached' in self.stats:
msg = ' {0:9}'.format(_("cached:"))
msg = ' {0:9}'.format('cached:')
ret.append(self.curse_add_line(msg, optional=self.get_views(key='cached', option='optional')))
msg = '{0:>7}'.format(self.auto_unit(self.stats['cached']))
ret.append(self.curse_add_line(msg, optional=self.get_views(key='cached', option='optional')))

View File

@ -19,10 +19,10 @@
"""Swap memory plugin."""
import psutil
from glances.plugins.glances_plugin import GlancesPlugin
import psutil
# SNMP OID
# Total Swap Size: .1.3.6.1.4.1.2021.4.3.0
# Available Swap Space: .1.3.6.1.4.1.2021.4.4.0
@ -41,7 +41,7 @@ items_history_list = [{'name': 'percent', 'color': '#00FF00', 'y_unit': '%'}]
class Plugin(GlancesPlugin):
"""Glances' swap memory plugin.
"""Glances swap memory plugin.
stats is a dict
"""
@ -67,7 +67,7 @@ class Plugin(GlancesPlugin):
# Reset stats
self.reset()
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats using the standard system lib
# Grab SWAP using the PSUtil swap_memory method
sm_stats = psutil.swap_memory()
@ -84,20 +84,21 @@ class Plugin(GlancesPlugin):
'sin', 'sout']:
if hasattr(sm_stats, swap):
self.stats[swap] = getattr(sm_stats, swap)
elif self.get_input() == 'snmp':
elif self.input_method == 'snmp':
# Update stats using SNMP
if self.get_short_system_name() == 'windows':
if self.short_system_name == 'windows':
# Mem stats for Windows OS are stored in the FS table
try:
fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name],
bulk=True)
except KeyError:
self.reset()
else:
for fs in fs_stat:
# The virtual memory concept is used by the operating system to extend (virtually) the physical
# memory and thus to run more programs by swapping
# unused memory zone (page) to a disk file.
# The virtual memory concept is used by the operating
# system to extend (virtually) the physical memory and
# thus to run more programs by swapping unused memory
# zone (page) to a disk file.
if fs == 'Virtual Memory':
self.stats['total'] = int(
fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
@ -109,7 +110,7 @@ class Plugin(GlancesPlugin):
'total'] - self.stats['used']
break
else:
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default'])
self.stats = self.get_stats_snmp(snmp_oid=snmp_oid['default'])
if self.stats['total'] == '':
self.reset()
@ -136,13 +137,13 @@ class Plugin(GlancesPlugin):
return self.stats
def update_views(self):
"""Update stats views"""
"""Update stats views."""
# Call the father's method
GlancesPlugin.update_views(self)
# Add specifics informations
# Alert and log
self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], max=self.stats['total'])
self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], maximum=self.stats['total'])
def msg_curse(self, args=None):
"""Return the dict to display in the curse interface."""
@ -150,12 +151,12 @@ class Plugin(GlancesPlugin):
ret = []
# Only process if stats exist...
if self.stats == {}:
if not self.stats:
return ret
# Build the string message
# Header
msg = '{0:7} '.format(_("SWAP"))
msg = '{0:7} '.format('SWAP')
ret.append(self.curse_add_line(msg, "TITLE"))
# Percent memory usage
msg = '{0:>6.1%}'.format(self.stats['percent'] / 100)
@ -163,14 +164,14 @@ class Plugin(GlancesPlugin):
# New line
ret.append(self.curse_new_line())
# Total memory usage
msg = '{0:8}'.format(_("total:"))
msg = '{0:8}'.format('total:')
ret.append(self.curse_add_line(msg))
msg = '{0:>6}'.format(self.auto_unit(self.stats['total']))
ret.append(self.curse_add_line(msg))
# New line
ret.append(self.curse_new_line())
# Used memory usage
msg = '{0:8}'.format(_("used:"))
msg = '{0:8}'.format('used:')
ret.append(self.curse_add_line(msg))
msg = '{0:>6}'.format(self.auto_unit(self.stats['used']))
ret.append(self.curse_add_line(
@ -178,7 +179,7 @@ class Plugin(GlancesPlugin):
# New line
ret.append(self.curse_new_line())
# Free memory usage
msg = '{0:8}'.format(_("free:"))
msg = '{0:8}'.format('free:')
ret.append(self.curse_add_line(msg))
msg = '{0:>6}'.format(self.auto_unit(self.stats['free']))
ret.append(self.curse_add_line(msg))

View File

@ -20,14 +20,13 @@
"""Monitor plugin."""
# Import Glances lib
from glances.core.glances_logging import logger
from glances.core.glances_monitor_list import MonitorList as glancesMonitorList
from glances.plugins.glances_plugin import GlancesPlugin
class Plugin(GlancesPlugin):
"""Glances' monitor plugin."""
"""Glances monitor plugin."""
def __init__(self, args=None):
"""Init the plugin."""
@ -41,13 +40,12 @@ class Plugin(GlancesPlugin):
self.stats = []
def load_limits(self, config):
"""Load the monitored list from the conf file."""
logger.debug("Monitor plugin configuration detected in the configuration file")
"""Load the monitored list from the config file, if it exists."""
self.glances_monitors = glancesMonitorList(config)
def update(self):
"""Update the monitored list."""
if self.get_input() == 'local':
if self.input_method == 'local':
# Monitor list only available in a full Glances environment
# Check if the glances_monitor instance is init
if self.glances_monitors is None:
@ -98,7 +96,7 @@ class Plugin(GlancesPlugin):
msg, self.get_alert(m['count'], m['countmin'], m['countmax'])))
msg = '{0:<3} '.format(m['count'] if m['count'] > 1 else '')
ret.append(self.curse_add_line(msg))
msg = '{0:13} '.format(_("RUNNING") if m['count'] >= 1 else _("NOT RUNNING"))
msg = '{0:13} '.format('RUNNING' if m['count'] >= 1 else 'NOT RUNNING')
ret.append(self.curse_add_line(msg))
# Decode to UTF8 (only for Python 3)
try:

View File

@ -22,11 +22,11 @@
import base64
import operator
import psutil
from glances.core.glances_timer import getTimeSinceLastUpdate
from glances.plugins.glances_plugin import GlancesPlugin
import psutil
# SNMP OID
# http://www.net-snmp.org/docs/mibs/interfaces.html
# Dict key = interface_name
@ -43,7 +43,7 @@ items_history_list = [{'name': 'rx', 'color': '#00FF00', 'y_unit': 'bit/s'},
class Plugin(GlancesPlugin):
"""Glances' network Plugin.
"""Glances network plugin.
stats is a list
"""
@ -59,7 +59,7 @@ class Plugin(GlancesPlugin):
self.reset()
def get_key(self):
"""Return the key of the list"""
"""Return the key of the list."""
return 'interface_name'
def reset(self):
@ -75,7 +75,7 @@ class Plugin(GlancesPlugin):
# Reset stats
self.reset()
if self.get_input() == 'local':
if self.input_method == 'local':
# Update stats using the standard system lib
# Grab network interface stat using the PsUtil net_io_counter method
@ -101,19 +101,21 @@ class Plugin(GlancesPlugin):
network_new = netiocounters
for net in network_new:
try:
# Try necessary to manage dynamic network interface
netstat = {}
netstat['interface_name'] = net
netstat['time_since_update'] = time_since_update
netstat['cumulative_rx'] = network_new[net].bytes_recv
netstat['rx'] = (network_new[net].bytes_recv -
self.network_old[net].bytes_recv)
netstat['cumulative_tx'] = network_new[net].bytes_sent
netstat['tx'] = (network_new[net].bytes_sent -
self.network_old[net].bytes_sent)
netstat['cumulative_cx'] = (netstat['cumulative_rx'] +
netstat['cumulative_tx'])
netstat['cx'] = netstat['rx'] + netstat['tx']
cumulative_rx = network_new[net].bytes_recv
cumulative_tx = network_new[net].bytes_sent
cumulative_cx = cumulative_rx + cumulative_tx
rx = cumulative_rx - self.network_old[net].bytes_recv
tx = cumulative_tx - self.network_old[net].bytes_sent
cx = rx + tx
netstat = {
'interface_name': net,
'time_since_update': time_since_update,
'cumulative_rx': cumulative_rx,
'rx': rx,
'cumulative_tx': cumulative_tx,
'tx': tx,
'cumulative_cx': cumulative_cx,
'cx': cx}
except KeyError:
continue
else:
@ -123,15 +125,15 @@ class Plugin(GlancesPlugin):
# Save stats to compute next bitrate
self.network_old = network_new
elif self.get_input() == 'snmp':
elif self.input_method == 'snmp':
# Update stats using SNMP
# SNMP bulk command to get all network interface in one shot
try:
netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
netiocounters = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name],
bulk=True)
except KeyError:
netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid['default'],
netiocounters = self.get_stats_snmp(snmp_oid=snmp_oid['default'],
bulk=True)
# Previous network interface stats are stored in the network_old variable
@ -150,27 +152,30 @@ class Plugin(GlancesPlugin):
for net in network_new:
try:
# Try necessary to manage dynamic network interface
netstat = {}
# Windows: a tips is needed to convert HEX to TXT
# http://blogs.technet.com/b/networking/archive/2009/12/18/how-to-query-the-list-of-network-interfaces-using-snmp-via-the-ifdescr-counter.aspx
if self.get_short_system_name() == 'windows':
if self.short_system_name == 'windows':
try:
netstat['interface_name'] = str(base64.b16decode(net[2:-2].upper()))
interface_name = str(base64.b16decode(net[2:-2].upper()))
except TypeError:
netstat['interface_name'] = net
interface_name = net
else:
netstat['interface_name'] = net
netstat['time_since_update'] = time_since_update
netstat['cumulative_rx'] = float(network_new[net]['cumulative_rx'])
netstat['rx'] = (float(network_new[net]['cumulative_rx']) -
float(self.network_old[net]['cumulative_rx']))
netstat['cumulative_tx'] = float(network_new[net]['cumulative_tx'])
netstat['tx'] = (float(network_new[net]['cumulative_tx']) -
float(self.network_old[net]['cumulative_tx']))
netstat['cumulative_cx'] = (netstat['cumulative_rx'] +
netstat['cumulative_tx'])
netstat['cx'] = netstat['rx'] + netstat['tx']
interface_name = net
cumulative_rx = float(network_new[net]['cumulative_rx'])
cumulative_tx = float(network_new[net]['cumulative_tx'])
cumulative_cx = cumulative_rx + cumulative_tx
rx = cumulative_rx - float(self.network_old[net]['cumulative_rx'])
tx = cumulative_tx - float(self.network_old[net]['cumulative_tx'])
cx = rx + tx
netstat = {
'interface_name': interface_name,
'time_since_update': time_since_update,
'cumulative_rx': cumulative_rx,
'rx': rx,
'cumulative_tx': cumulative_tx,
'tx': tx,
'cumulative_cx': cumulative_cx,
'cx': cx}
except KeyError:
continue
else:
@ -189,7 +194,7 @@ class Plugin(GlancesPlugin):
return self.stats
def update_views(self):
"""Update stats views"""
"""Update stats views."""
# Call the father's method
GlancesPlugin.update_views(self)
@ -204,7 +209,6 @@ class Plugin(GlancesPlugin):
def msg_curse(self, args=None, max_width=None):
"""Return the dict to display in the curse interface."""
# Init the return message
ret = []
@ -221,30 +225,30 @@ class Plugin(GlancesPlugin):
# Build the string message
# Header
msg = '{0:{width}}'.format(_("NETWORK"), width=ifname_max_width)
msg = '{0:{width}}'.format('NETWORK', width=ifname_max_width)
ret.append(self.curse_add_line(msg, "TITLE"))
if args.network_cumul:
# Cumulative stats
if args.network_sum:
# Sum stats
msg = '{0:>14}'.format(_("Rx+Tx"))
msg = '{0:>14}'.format('Rx+Tx')
ret.append(self.curse_add_line(msg))
else:
# Rx/Tx stats
msg = '{0:>7}'.format(_("Rx"))
msg = '{0:>7}'.format('Rx')
ret.append(self.curse_add_line(msg))
msg = '{0:>7}'.format(_("Tx"))
msg = '{0:>7}'.format('Tx')
ret.append(self.curse_add_line(msg))
else:
# Bitrate stats
if args.network_sum:
# Sum stats
msg = '{0:>14}'.format(_("Rx+Tx/s"))
msg = '{0:>14}'.format('Rx+Tx/s')
ret.append(self.curse_add_line(msg))
else:
msg = '{0:>7}'.format(_("Rx/s"))
msg = '{0:>7}'.format('Rx/s')
ret.append(self.curse_add_line(msg))
msg = '{0:>7}'.format(_("Tx/s"))
msg = '{0:>7}'.format('Tx/s')
ret.append(self.curse_add_line(msg))
# Interface list (sorted by name)
for i in sorted(self.stats, key=operator.itemgetter(self.get_key())):
@ -265,20 +269,20 @@ class Plugin(GlancesPlugin):
if args.network_cumul:
rx = self.auto_unit(int(i['cumulative_rx']))
tx = self.auto_unit(int(i['cumulative_tx']))
sx = self.auto_unit(int(i['cumulative_tx'])
+ int(i['cumulative_tx']))
sx = self.auto_unit(int(i['cumulative_tx']) +
int(i['cumulative_tx']))
else:
rx = self.auto_unit(int(i['rx'] // i['time_since_update']))
tx = self.auto_unit(int(i['tx'] // i['time_since_update']))
sx = self.auto_unit(int(i['rx'] // i['time_since_update'])
+ int(i['tx'] // i['time_since_update']))
sx = self.auto_unit(int(i['rx'] // i['time_since_update']) +
int(i['tx'] // i['time_since_update']))
else:
# Bits per second (for real network administrator | Default)
if args.network_cumul:
rx = self.auto_unit(int(i['cumulative_rx'] * 8)) + "b"
tx = self.auto_unit(int(i['cumulative_tx'] * 8)) + "b"
sx = self.auto_unit(int(i['cumulative_rx'] * 8)
+ int(i['cumulative_tx'] * 8)) + "b"
sx = self.auto_unit(int(i['cumulative_rx'] * 8) +
int(i['cumulative_tx'] * 8)) + "b"
else:
rx = self.auto_unit(int(i['rx'] // i['time_since_update'] * 8)) + "b"
tx = self.auto_unit(int(i['tx'] // i['time_since_update'] * 8)) + "b"

Some files were not shown because too many files have changed in this diff Show More