├── dashboard-1.png
├── dashboard-2.png
├── app
├── static
│ ├── img
│ │ ├── favicon.ico
│ │ ├── glyphicons.png
│ │ └── openstack-logo-full.png
│ ├── js
│ │ ├── globalize.min.js
│ │ ├── routes.js
│ │ ├── jquery.flot.byte.js
│ │ ├── signals.min.js
│ │ ├── crossroads.min.js
│ │ ├── openstack.dash.js
│ │ ├── jquery.flot.time.js
│ │ ├── jquery.flot.tooltip.js
│ │ └── bootstrap.min.js
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.svg
│ ├── report.html
│ ├── instances.html
│ ├── index.html
│ └── css
│ │ ├── dash.css
│ │ └── bootstrap.icon-large.min.css
├── static.py
├── mongo.py
├── config
│ └── application_sample.yaml
├── utils.py
├── settings.py
├── app.py
├── caching.py
├── routes.py
├── api.py
└── openstack.py
├── setup
├── setup.sh
└── requirements.txt
├── scripts
├── toprc
└── monit_instances.sh
├── .gitignore
├── README.md
└── LICENSE
/dashboard-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renato-farias/openstack-instances-monitoring/HEAD/dashboard-1.png
--------------------------------------------------------------------------------
/dashboard-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renato-farias/openstack-instances-monitoring/HEAD/dashboard-2.png
--------------------------------------------------------------------------------
/app/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renato-farias/openstack-instances-monitoring/HEAD/app/static/img/favicon.ico
--------------------------------------------------------------------------------
/app/static/img/glyphicons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renato-farias/openstack-instances-monitoring/HEAD/app/static/img/glyphicons.png
--------------------------------------------------------------------------------
/app/static/js/globalize.min.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renato-farias/openstack-instances-monitoring/HEAD/app/static/js/globalize.min.js
--------------------------------------------------------------------------------
/app/static/img/openstack-logo-full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renato-farias/openstack-instances-monitoring/HEAD/app/static/img/openstack-logo-full.png
--------------------------------------------------------------------------------
/setup/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sudo apt-get update
4 | sudo apt-get -y install vim virtualenvwrapper python-pip python-dev mongodb memcached curl
5 |
--------------------------------------------------------------------------------
/setup/requirements.txt:
--------------------------------------------------------------------------------
1 | flask==0.10.1
2 | gunicorn==19.3.0
3 | pyaml==15.5.7
4 | pymongo==3.0.2
5 | requests==2.7.0
6 | ipython==3.1.0
7 | python-memcached==1.57
8 |
--------------------------------------------------------------------------------
/app/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renato-farias/openstack-instances-monitoring/HEAD/app/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/app/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renato-farias/openstack-instances-monitoring/HEAD/app/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/app/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renato-farias/openstack-instances-monitoring/HEAD/app/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/app/static.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from flask import current_app
4 |
5 | def index(resource=None):
6 | return current_app.send_static_file('index.html')
7 |
8 |
9 | def instances():
10 | return current_app.send_static_file('instances.html')
11 |
12 |
13 | def report():
14 | return current_app.send_static_file('report.html')
15 |
--------------------------------------------------------------------------------
/app/mongo.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import pymongo
4 |
5 | import settings
6 |
7 | def get_mongodb():
8 | try:
9 | c = pymongo.MongoClient(settings.MONGODB_HOST,
10 | settings.MONGODB_PORT)
11 | return c[settings.MONGODB_BASE]
12 | except:
13 | return None
14 |
15 | def get_instance_collection(instance_id):
16 | c = get_mongodb()
17 | if c:
18 | return c['instance_%s' % instance_id]
19 | return None
20 |
--------------------------------------------------------------------------------
/app/config/application_sample.yaml:
--------------------------------------------------------------------------------
1 | mongodb:
2 | host: localhost
3 | port: 27017
4 |
5 |
6 | memcache:
7 | host: 'localhost'
8 | port: 11211
9 |
10 |
11 | projects:
12 | Project1:
13 | auth_url: 'http://keystone.endopint.project1:35357'
14 | auth_user: admin
15 | auth_pass: admin
16 | auth_tentant: 'admin'
17 | all_tentants: true
18 | Project2:
19 | auth_url: 'http://keystone.endopint.project1:35357'
20 | auth_user: admin
21 | auth_pass: admin
22 | auth_tentant: 'admin'
23 | all_tentants: true
24 |
--------------------------------------------------------------------------------
/app/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 |
5 | from flask import make_response
6 |
7 | def myjsonify(data=None, code=200, headers=None):
8 | data = [] if not data else data
9 | r = make_response(json.dumps(data,
10 | indent=2,
11 | sort_keys=True,
12 | ensure_ascii=False,
13 | encoding='utf8') + '\n',
14 | code)
15 | r.headers['Content-Type'] = 'application/json; charset=utf-8'
16 | if headers:
17 | for k,v in headers.items(): r.headers[k] = v
18 | return r
19 |
--------------------------------------------------------------------------------
/app/static/js/routes.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | var url_prefix = '/dashboard';
4 |
5 | crossroads.addRoute('/', function() {
6 | _load_all_instances();
7 | });
8 | crossroads.addRoute(url_prefix, function() {
9 | _load_all_instances();
10 | });
11 | crossroads.addRoute(url_prefix + '/instances', function() {
12 | _load_all_instances();
13 | });
14 | crossroads.addRoute(url_prefix + '/report', function() {
15 | _load_report();
16 | });
17 | crossroads.parse(document.location.pathname);
18 | });
19 |
--------------------------------------------------------------------------------
/app/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import yaml
4 |
5 | from os import getenv
6 |
7 | config = yaml.load(open('config/application.yaml'))
8 |
9 | APP_ENV = getenv('APP_ENV', 'development')
10 | APPLICATION_NAME = 'openstack_monitoring'
11 |
12 | MONGODB_HOST = config.get('mongodb', {}).get('host', 'localhost')
13 | MONGODB_PORT = config.get('mongodb', {}).get('port', 27017)
14 | MONGODB_BASE = config.get('mongodb', {}).get('base', APPLICATION_NAME)
15 |
16 | MEMCACHED_HOST = config.get('memcached', {}).get('host', 'localhost')
17 | MEMCACHED_PORT = config.get('memcached', {}).get('port', 11211)
18 |
19 | PROJECTS = config.get('projects', [])
20 |
--------------------------------------------------------------------------------
/scripts/toprc:
--------------------------------------------------------------------------------
1 | RCfile for "top with windows" # shameless braggin'
2 | Id:a, Mode_altscr=0, Mode_irixps=0, Delay_time=3.000, Curwin=0
3 | Def fieldscur=AEHIOQTWKNMbcdfgjplrsuvyzX
4 | winflags=62777, sortindx=10, maxtasks=0
5 | summclr=1, msgsclr=1, headclr=3, taskclr=1
6 | Job fieldscur=ABcefgjlrstuvyzMKNHIWOPQDX
7 | winflags=62777, sortindx=0, maxtasks=0
8 | summclr=6, msgsclr=6, headclr=7, taskclr=6
9 | Mem fieldscur=ANOPQRSTUVbcdefgjlmyzWHIKX
10 | winflags=62777, sortindx=13, maxtasks=0
11 | summclr=5, msgsclr=5, headclr=4, taskclr=5
12 | Usr fieldscur=ABDECGfhijlopqrstuvyzMKNWX
13 | winflags=62777, sortindx=4, maxtasks=0
14 | summclr=3, msgsclr=3, headclr=2, taskclr=3
15 |
--------------------------------------------------------------------------------
/app/app.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import caching
5 | import settings
6 | import memcache
7 |
8 | from flask import Flask
9 | from routes import routes
10 | from werkzeug.contrib.cache import MemcachedCache
11 |
12 |
13 | app = Flask(settings.APPLICATION_NAME, static_url_path='')
14 | app.secret_key = 'openstack_monitoring'
15 |
16 | app.register_blueprint(routes)
17 |
18 | servers = ['%s:%s' % (settings.MEMCACHED_HOST, str(settings.MEMCACHED_PORT))]
19 | memcache.SERVER_MAX_VALUE_LENGTH = 1024*1024*10
20 |
21 | cache = MemcachedCache(servers)
22 |
23 | def setup_app(app):
24 | caching.load_servers()
25 |
26 |
27 | #setup_app(app)
28 |
29 |
30 | if __name__ == '__main__':
31 | try:
32 | app.run(debug=True)
33 | except Exception, e:
34 | import sys, traceback
35 | traceback.print_exc(file=sys.stdout)
36 | print str(e)
37 |
--------------------------------------------------------------------------------
/app/static/report.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Usage Report
5 |
6 |
7 |
8 |
9 |
10 |
Current Usage
11 |
12 |
13 |
14 | | Project |
15 | VMs Total |
16 | vCPUs Total |
17 | Memory Total (GB) |
18 | Disk Total (GB) |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
--------------------------------------------------------------------------------
/app/caching.py:
--------------------------------------------------------------------------------
1 | import app
2 | import settings
3 |
4 | from openstack import load_servers_list, load_users_list, load_flavors_list, load_tenants_list, load_images_list
5 |
6 |
7 | def load_users():
8 | users = load_users_list()
9 | app.cache.set('users_list', users, timeout=0)
10 |
11 |
12 | def load_servers():
13 | servers = load_servers_list()
14 | app.cache.set('servers_list', servers, timeout=0)
15 |
16 |
17 | def load_flavors():
18 | flavors = load_flavors_list()
19 | app.cache.set('flavors_list', flavors, timeout=0)
20 |
21 |
22 | def load_tenants():
23 | tenants = load_tenants_list()
24 | app.cache.set('tenants_list', tenants, timeout=0)
25 |
26 |
27 | def load_images():
28 | images = load_images_list()
29 | app.cache.set('images_list', images, timeout=0)
30 |
31 |
32 | def renew():
33 | load_images()
34 | load_tenants()
35 | load_flavors()
36 | load_users()
37 | load_servers()
38 | print 'Cache loaded'
39 | return 'okay'
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 |
55 | # Sphinx documentation
56 | docs/_build/
57 |
58 | # PyBuilder
59 | target/
60 |
61 | #Ipython Notebook
62 | .ipynb_checkpoints
63 |
64 | application.yaml
65 |
--------------------------------------------------------------------------------
/scripts/monit_instances.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export COLUMNS=512
4 |
5 | IFS='
6 | '
7 |
8 | MONITORING_SERVICE_IP="http://10.131.80.28:8000"
9 |
10 | instance_ps='qemu-system'
11 |
12 | if [ ! -e "${HOME}/.toprc" ]; then
13 | wget -c ${MONITORING_SERVICE_IP}/scripts/toprc -O ${HOME}/.toprc
14 | fi
15 |
16 |
17 | PSS=$(top -c -b -p $(pgrep ${instance_ps} -d ',') -n 1 |grep ${instance_ps})
18 |
19 | hv_mem=$(free -k |sed -n '2p' |awk '{print $2}')
20 | hv_cpu=$(cat /proc/cpuinfo |grep processor |wc -l)
21 |
22 |
23 | for i in ${PSS}; do
24 | IFS=' '
25 | read cpu mem instance <<<$(echo ${i}| awk '{match($0,"instance-[0-9a-zA-Z]{8}",a)}END{print $9, $10, a[0]}')
26 | instance_cpu=$(virsh vcpucount ${instance} |awk '{if (($1=="current") && ($2=="live")) {print $3}}')
27 | instance_mem=$(virsh dommemstat ${instance} |awk '{if ($1=="actual") {print $2}}')
28 | total_mem=$(echo "scale=4;(((${hv_mem}/100)*${mem})/${instance_mem})*100"|bc)
29 | total_cpu=$(echo "scale=4;(((${hv_cpu}/100)*${cpu})/${instance_cpu})*100"|bc)
30 | instance_id=$(virsh dominfo ${instance} |awk '{if ($1=="UUID:") {print $2}}')
31 | curl --silent -d "instance_id=${instance_id}&cpu=${total_cpu}&mem=${total_mem}" "${MONITORING_SERVICE_IP}/monitoring/post"
32 | done
33 |
--------------------------------------------------------------------------------
/app/static/instances.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Search
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Instances
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
53 |
--------------------------------------------------------------------------------
/app/routes.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | from flask import Blueprint, current_app
4 |
5 | from api import _servers, \
6 | _search, \
7 | _users, \
8 | _get_server, \
9 | _flavors, \
10 | _tenants, \
11 | _images, \
12 | _post_monitoring, \
13 | _get_monitoring, \
14 | _get_usage_report
15 | from static import index, report, instances
16 | from caching import renew
17 |
18 | routes = Blueprint('routes', __name__, static_folder='../static')
19 |
20 | routes.add_url_rule('/', view_func=index, methods=['GET'])
21 | routes.add_url_rule('/report', view_func=report, methods=['GET'])
22 | routes.add_url_rule('/instances', view_func=instances, methods=['GET'])
23 | routes.add_url_rule('/dashboard', view_func=index, methods=['GET'])
24 | routes.add_url_rule('/dashboard/', view_func=index, methods=['GET'])
25 | routes.add_url_rule('/users', view_func=_users, methods=['GET'])
26 | routes.add_url_rule('/servers', view_func=_servers, methods=['GET'])
27 | routes.add_url_rule('/flavors', view_func=_flavors, methods=['GET'])
28 | routes.add_url_rule('/tenants', view_func=_tenants, methods=['GET'])
29 | routes.add_url_rule('/images', view_func=_images, methods=['GET'])
30 | routes.add_url_rule('/server/', view_func=_get_server, methods=['GET'])
31 | routes.add_url_rule('/search/', view_func=_search, methods=['GET'])
32 | routes.add_url_rule('/renew', view_func=renew, methods=['GET'])
33 | routes.add_url_rule('/monitoring//', view_func=_get_monitoring, methods=['GET'])
34 | routes.add_url_rule('/monitoring/post', view_func=_post_monitoring, methods=['POST'])
35 | routes.add_url_rule('/report/resources', view_func=_get_usage_report, methods=['GET'])
36 |
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # openstack-instances-monitoring
2 |
3 | OpenStack Instances Monitor is a simple project to show how much each instance in your project is consuming on your hypervisors.
4 |
5 |
6 | ### Installing the tool
7 |
8 | * clone this repository
9 | ```sh
10 | $ git clone git@github.com:renato-farias/openstack-instances-monitoring.git
11 | ```
12 | * installing all dependecies (OS)
13 | ```sh
14 | $ cd openstack-instances-monitoring
15 | $ setup/setup.sh
16 | ```
17 | * installing all dependecies (Python Application)
18 | ```sh
19 | $ cd openstack-instances-monitoring
20 | $ mkvirtualenv monitoring
21 | $ workon monitoring
22 | $ pip install -r setup/requirements.txt
23 | ```
24 | * change the setting and run the app
25 | ```sh
26 | $ cd openstack-instances-monitoring/app
27 | $ mv application_sample.yaml application.yaml
28 | $ gunicorn -b 0.0.0.0:8000 app:app --log-level info --env APP_ENV='production' --reload -w 4 --timeout 60
29 | ```
30 | * point your browser to http://SERVER:8000/
31 | * In your first access access: http://SERVER:8000/renew - To make the initial cache.
32 | * insert in the root's cron the following entry (It's responsible to collect data from openstack API avoiding to take too time to get some informations about all instances):
33 | ```sh
34 | 1 * * * * curl http://localhost:8000/renew
35 | ```
36 |
37 | ### Installing the collector on the Hypervisors
38 | * Get the script
39 | ```sh
40 | # wget -c http://SERVER:8000/scripts/monit_instances.sh
41 | ```
42 | * Edit the script pointing the server IP or Name to send data.
43 | * insert in the root's cron the following entry:
44 | ```sh
45 | * * * * * /root/monit_instances.sh
46 | ```
47 |
48 |
49 | ### Customization
50 | It's a tool with a simple endpoit API to collect data. Feel free using your imagination to customize this.
51 |
52 |
53 | ### How it looks
54 | 
55 | 
56 |
57 | I'd like to say thank you to Crapworks. I used his template from [Ceph-Dash](https://github.com/Crapworks/ceph-dash) to turn easy my job =]
58 |
--------------------------------------------------------------------------------
/app/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OpenStack Instances Monitor
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |

18 |
Instances Monitor
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/static/css/dash.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #fff;
3 | }
4 |
5 | .well {
6 | background-color: #ffe;
7 | }
8 |
9 | h1 {
10 | color: #1c1e22;
11 | }
12 |
13 | .panel-body, .panel-instance-list {
14 | background-color: #ffc;
15 | }
16 |
17 | .panel-instance-list {
18 | padding-top: 20px;
19 | padding-left: 10px;
20 | padding-right: 10px;
21 | }
22 |
23 | .panel-instance {
24 | /*background-color: #7a8288;*/
25 | /*background-color: #414344;*/
26 | background-color: #fff;
27 | border-color: #999;
28 | }
29 |
30 | .panel-instance .panel-body {
31 | background-color: #fff;
32 | color: #000;
33 | font-weight: normal;
34 | }
35 |
36 | .strong {
37 | font-weight: bold;
38 | }
39 |
40 | .block {
41 | display: block;
42 | }
43 |
44 | .panel-instance .panel-title {
45 | color: #000;
46 | }
47 |
48 | .panel.panel-primary {
49 | background-color: #fff;
50 | }
51 |
52 | .panel-instance h3 {
53 | color: #1c1e22;
54 | }
55 |
56 | .icon-ok {
57 | color: #62C462;
58 | }
59 |
60 | .icon-warn {
61 | color: #F89406;
62 | }
63 |
64 | .icon-err {
65 | color: #EE5F5B;
66 | }
67 |
68 | .panel-body {
69 | font-weight: bold;
70 | }
71 |
72 | .panel-title {
73 | font-weight: bold;
74 | }
75 |
76 | .cd-collapsable h3:after {
77 | font-family: 'Glyphicons Halflings';
78 | content: "\e113";
79 | cursor: pointer;
80 | float: right;
81 | }
82 |
83 | .cd-collapsed h3:after {
84 | font-family: 'Glyphicons Halflings';
85 | content: "\e114";
86 | cursor: pointer;
87 | float: right;
88 | }
89 |
90 | .graphite {
91 | width: 500px;
92 | height: 150px;
93 | font-size: 14px;
94 | line-height: 1.2em;
95 | }
96 |
97 | .influxdb {
98 | width: 500px;
99 | height: 150px;
100 | font-size: 14px;
101 | line-height: 1.2em;
102 | }
103 |
104 | #tooltip {
105 | position: absolute;
106 | display: none;
107 | border: 1px solid #000;
108 | padding: 2px;
109 | background-color: #272b30;
110 | }
111 |
112 | .row.menu {
113 | margin-top: 10px;
114 | }
115 |
116 | span.link {
117 | color: #000;
118 | cursor: pointer;
119 | }
120 |
121 | table {
122 | background-color: #fff;
123 | }
124 |
125 | table thead, table tbody, table tr, table td {
126 | color: #000;
127 | }
128 |
129 | h4 {
130 | color: #111;
131 | }
132 |
--------------------------------------------------------------------------------
/app/static/js/jquery.flot.byte.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 | "use strict";
3 |
4 | var options = {};
5 |
6 | //Round to nearby lower multiple of base
7 | function floorInBase(n, base) {
8 | return base * Math.floor(n / base);
9 | }
10 |
11 | function init(plot) {
12 | plot.hooks.processDatapoints.push(function (plot) {
13 | $.each(plot.getAxes(), function(axisName, axis) {
14 | var opts = axis.options;
15 | if (opts.mode === "byte" || opts.mode === "byteRate") {
16 | axis.tickGenerator = function (axis) {
17 | var returnTicks = [],
18 | tickSize = 2,
19 | delta = axis.delta,
20 | steps = 0,
21 | tickMin = 0,
22 | tickVal,
23 | tickCount = 0;
24 |
25 | //Set the reference for the formatter
26 | if (opts.mode === "byteRate") {
27 | axis.rate = true;
28 | }
29 |
30 | //Enforce maximum tick Decimals
31 | if (typeof opts.tickDecimals === "number") {
32 | axis.tickDecimals = opts.tickDecimals;
33 | } else {
34 | axis.tickDecimals = 2;
35 | }
36 |
37 | //Count the steps
38 | while (Math.abs(delta) >= 1024) {
39 | steps++;
40 | delta /= 1024;
41 | }
42 |
43 | //Set the tick size relative to the remaining delta
44 | while (tickSize <= 1024) {
45 | if (delta <= tickSize) {
46 | break;
47 | }
48 | tickSize *= 2;
49 | }
50 |
51 | //Tell flot the tickSize we've calculated
52 | if (typeof opts.minTickSize !== "undefined" && tickSize < opts.minTickSize) {
53 | axis.tickSize = opts.minTickSize;
54 | } else {
55 | axis.tickSize = tickSize * Math.pow(1024,steps);
56 | }
57 |
58 | //Calculate the new ticks
59 | tickMin = floorInBase(axis.min, axis.tickSize);
60 | do {
61 | tickVal = tickMin + (tickCount++) * axis.tickSize;
62 | returnTicks.push(tickVal);
63 | } while (tickVal < axis.max);
64 |
65 | return returnTicks;
66 | };
67 |
68 | axis.tickFormatter = function(size, axis) {
69 | var ext, steps = 0;
70 |
71 | while (Math.abs(size) >= 1024) {
72 | steps++;
73 | size /= 1024;
74 | }
75 |
76 |
77 | switch (steps) {
78 | case 0: ext = " B"; break;
79 | case 1: ext = " KiB"; break;
80 | case 2: ext = " MiB"; break;
81 | case 3: ext = " GiB"; break;
82 | case 4: ext = " TiB"; break;
83 | case 5: ext = " PiB"; break;
84 | case 6: ext = " EiB"; break;
85 | case 7: ext = " ZiB"; break;
86 | case 8: ext = " YiB"; break;
87 | }
88 |
89 |
90 | if (typeof axis.rate !== "undefined") {
91 | ext += "/s";
92 | }
93 |
94 | return (size.toFixed(axis.tickDecimals) + ext);
95 | };
96 | }
97 | });
98 | });
99 | }
100 |
101 | $.plot.plugins.push({
102 | init: init,
103 | options: options,
104 | name: "byte",
105 | version: "0.1"
106 | });
107 | })(jQuery);
--------------------------------------------------------------------------------
/app/static/js/signals.min.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | JS Signals
4 | Released under the MIT license
5 | Author: Miller Medeiros
6 | Version: 1.0.0 - Build: 268 (2012/11/29 05:48 PM)
7 | */
8 | (function(i){function h(a,b,c,d,e){this._listener=b;this._isOnce=c;this.context=d;this._signal=a;this._priority=e||0}function g(a,b){if(typeof a!=="function")throw Error("listener is a required param of {fn}() and should be a Function.".replace("{fn}",b));}function e(){this._bindings=[];this._prevParams=null;var a=this;this.dispatch=function(){e.prototype.dispatch.apply(a,arguments)}}h.prototype={active:!0,params:null,execute:function(a){var b;this.active&&this._listener&&(a=this.params?this.params.concat(a):
9 | a,b=this._listener.apply(this.context,a),this._isOnce&&this.detach());return b},detach:function(){return this.isBound()?this._signal.remove(this._listener,this.context):null},isBound:function(){return!!this._signal&&!!this._listener},isOnce:function(){return this._isOnce},getListener:function(){return this._listener},getSignal:function(){return this._signal},_destroy:function(){delete this._signal;delete this._listener;delete this.context},toString:function(){return"[SignalBinding isOnce:"+this._isOnce+
10 | ", isBound:"+this.isBound()+", active:"+this.active+"]"}};e.prototype={VERSION:"1.0.0",memorize:!1,_shouldPropagate:!0,active:!0,_registerListener:function(a,b,c,d){var e=this._indexOfListener(a,c);if(e!==-1){if(a=this._bindings[e],a.isOnce()!==b)throw Error("You cannot add"+(b?"":"Once")+"() then add"+(!b?"":"Once")+"() the same listener without removing the relationship first.");}else a=new h(this,a,b,c,d),this._addBinding(a);this.memorize&&this._prevParams&&a.execute(this._prevParams);return a},
11 | _addBinding:function(a){var b=this._bindings.length;do--b;while(this._bindings[b]&&a._priority<=this._bindings[b]._priority);this._bindings.splice(b+1,0,a)},_indexOfListener:function(a,b){for(var c=this._bindings.length,d;c--;)if(d=this._bindings[c],d._listener===a&&d.context===b)return c;return-1},has:function(a,b){return this._indexOfListener(a,b)!==-1},add:function(a,b,c){g(a,"add");return this._registerListener(a,!1,b,c)},addOnce:function(a,b,c){g(a,"addOnce");return this._registerListener(a,
12 | !0,b,c)},remove:function(a,b){g(a,"remove");var c=this._indexOfListener(a,b);c!==-1&&(this._bindings[c]._destroy(),this._bindings.splice(c,1));return a},removeAll:function(){for(var a=this._bindings.length;a--;)this._bindings[a]._destroy();this._bindings.length=0},getNumListeners:function(){return this._bindings.length},halt:function(){this._shouldPropagate=!1},dispatch:function(a){if(this.active){var b=Array.prototype.slice.call(arguments),c=this._bindings.length,d;if(this.memorize)this._prevParams=
13 | b;if(c){d=this._bindings.slice();this._shouldPropagate=!0;do c--;while(d[c]&&this._shouldPropagate&&d[c].execute(b)!==!1)}}},forget:function(){this._prevParams=null},dispose:function(){this.removeAll();delete this._bindings;delete this._prevParams},toString:function(){return"[Signal active:"+this.active+" numListeners:"+this.getNumListeners()+"]"}};var f=e;f.Signal=e;typeof define==="function"&&define.amd?define(function(){return f}):typeof module!=="undefined"&&module.exports?module.exports=f:i.signals=
14 | f})(this);
15 |
--------------------------------------------------------------------------------
/app/api.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import re
4 | import datetime
5 |
6 | from flask import request
7 | from utils import myjsonify
8 | from mongo import get_instance_collection
9 | from openstack import get_servers, \
10 | get_users, \
11 | get_flavors, \
12 | get_tenants, \
13 | get_images, \
14 | get_server, \
15 | get_user, \
16 | get_flavor, \
17 | get_tenant, \
18 | get_image
19 |
20 | def _servers():
21 | return myjsonify({'instances': get_servers()})
22 |
23 | def _users():
24 | return myjsonify({'users': get_users()})
25 |
26 | def _flavors():
27 | return myjsonify({'flavors': get_flavors()})
28 |
29 |
30 | def _tenants():
31 | return myjsonify({'flavors': get_tenants()})
32 |
33 |
34 | def _images():
35 | return myjsonify({'images': get_images()})
36 |
37 | def _search(s):
38 | l = []
39 |
40 | if len(s) >= 3:
41 | servers = get_servers()
42 | try:
43 | for i in servers:
44 | if s in i['name']:
45 | l.append(i)
46 | except Exception, e:
47 | print str(e)
48 |
49 | return myjsonify({'instances': l})
50 |
51 |
52 | def _get_server(server_id):
53 | s = get_server(server_id)
54 | if s is not None:
55 | u = get_user(s['user_id'])
56 | s.pop('user_id')
57 | s['user'] = u
58 |
59 | f = get_flavor(s['flavor_id'])
60 | s.pop('flavor_id')
61 | s['flavor'] = f
62 |
63 | t = get_tenant(s['tenant_id'])
64 | s.pop('tenant_id')
65 | s['tenant'] = t
66 |
67 | i = get_image(s['image_id'])
68 | s.pop('image_id')
69 | s['image'] = i
70 |
71 | return myjsonify(s)
72 |
73 |
74 | def _post_monitoring():
75 | f = request.form
76 | instance_id = f['instance_id']
77 | cpu_usage = f['cpu']
78 | mem_usage = f['mem']
79 |
80 | if cpu_usage == '' or cpu_usage is None:
81 | cpu_usage = 0
82 |
83 | if mem_usage == '' or mem_usage is None:
84 | mem_usage = 0
85 |
86 | d = {
87 | 'log_date': datetime.datetime.now(),
88 | 'cpu': float(cpu_usage),
89 | 'mem': float(mem_usage)
90 | }
91 | get_instance_collection(instance_id).insert_one(d)
92 | return myjsonify({'status': 200})
93 |
94 |
95 | def _get_monitoring(instance_id, monitorying_type):
96 |
97 | q = {}
98 | s = {
99 | '_id': 0,
100 | 'log_date': 1
101 | }
102 |
103 | if monitorying_type == 'cpu':
104 | s['cpu'] = 1
105 | elif monitorying_type == 'mem':
106 | s['mem'] = 1
107 |
108 | d = []
109 | result = get_instance_collection(instance_id).find(q, s).sort('log_date')
110 | for r in result:
111 | if r[monitorying_type] == '' or r[monitorying_type] == None:
112 | r[monitorying_type] = 0
113 | d.append([
114 | # new_date,
115 | int(r['log_date'].strftime('%s')) * 1000,
116 | float(r[monitorying_type])
117 | ])
118 | return myjsonify({'data': d})
119 |
120 |
121 | def _get_usage_report():
122 | # initializing the counter
123 | counter_zero = {'vms': 0, 'mem': 0, 'cpu': 0, 'disk': 0}
124 | report = {'_total': counter_zero.copy()}
125 | servers = get_servers()
126 | flavors = get_flavors()
127 | for s in servers:
128 | if s['project'] not in report.keys():
129 | report[s['project']] = counter_zero.copy()
130 | # increasing the vm number
131 | report['_total']['vms'] += 1
132 | report[s['project']]['vms'] += 1
133 | # get flavor's information
134 | for f in flavors:
135 | if f['id'] == s['flavor_id']:
136 | # increasing the cpu number
137 | report['_total']['cpu'] += f['vcpus']
138 | report[s['project']]['cpu'] += f['vcpus']
139 | # increasing the mem number
140 | report['_total']['mem'] += (f['ram']/1024)
141 | report[s['project']]['mem'] += (f['ram']/1024)
142 | # increasing the mem number
143 | report['_total']['disk'] += f['disk']
144 | report[s['project']]['disk'] += f['disk']
145 | break
146 |
147 | report['_size'] = len(report.keys())-1
148 |
149 | return myjsonify(report)
150 |
--------------------------------------------------------------------------------
/app/openstack.py:
--------------------------------------------------------------------------------
1 | import app
2 | import json
3 | import requests
4 | import settings
5 |
6 | os_token = None
7 | headers = {'content-type': 'application/json'}
8 |
9 | auth_cache = {}
10 |
11 | projects = settings.PROJECTS
12 |
13 | def auth(project_name):
14 | project = projects[project_name]
15 | auth_cache[project_name] = {}
16 |
17 | json_post = {
18 | 'auth': {
19 | 'tenantName': project['auth_tentant'],
20 | 'passwordCredentials': {
21 | 'username': project['auth_user'],
22 | 'password': project['auth_pass']
23 | }
24 | }
25 | }
26 |
27 | # cleaning up older token
28 | if 'X-Auth-Token' in headers.keys():
29 | headers.pop('X-Auth-Token')
30 |
31 | r = requests.post('%s/v2.0/tokens' % project['auth_url'], data=json.dumps(json_post), headers=headers)
32 | auth_cache[project_name]['token'] = r.json()['access']['token']['id']
33 | for item in r.json()['access']['serviceCatalog']:
34 | if item['name'] == 'nova':
35 | auth_cache[project_name]['nova_admin_url'] = item['endpoints'][0]['publicURL']
36 | if item['name'] == 'keystone':
37 | auth_cache[project_name]['keystone_admin_url'] = item['endpoints'][0]['adminURL']
38 |
39 |
40 | def get_token(project_name):
41 | if project_name not in auth_cache.keys():
42 | auth(project_name)
43 | return auth_cache[project_name]['token']
44 |
45 |
46 | def load_tenants_list():
47 | l = []
48 | for project in projects.keys():
49 | headers['X-Auth-Token'] = get_token(project)
50 | r = requests.get("%s/tenants" % auth_cache[project]['keystone_admin_url'], headers=headers)
51 | for t in r.json()['tenants']:
52 | l.append({
53 | 'id': t['id'],
54 | 'name': t['name']
55 | })
56 | return l
57 |
58 |
59 | def load_images_list():
60 | l = []
61 | for project in projects.keys():
62 | headers['X-Auth-Token'] = get_token(project)
63 | r = requests.get("%s/images/detail?all_tenants=1" % auth_cache[project]['nova_admin_url'], headers=headers)
64 | for i in r.json()['images']:
65 | l.append({
66 | 'id': i['id'],
67 | 'name': i['name']
68 | })
69 | return l
70 |
71 |
72 | def load_servers_list():
73 | l = []
74 | for project in projects.keys():
75 | headers['X-Auth-Token'] = get_token(project)
76 | r = requests.get("%s/servers/detail?all_tenants=1" % auth_cache[project]['nova_admin_url'], headers=headers)
77 | for s in r.json()['servers']:
78 |
79 | networks = []
80 | for nets in s['addresses']:
81 | for net in s['addresses'][nets]:
82 | networks.append({
83 | 'type': net['OS-EXT-IPS:type'],
84 | 'addr': net['addr'],
85 | })
86 |
87 | l.append({
88 | 'id': s['id'],
89 | 'name': s['name'],
90 | 'status': s['status'].lower(),
91 | 'networks': networks,
92 | 'user_id': s['user_id'],
93 | 'flavor_id': s['flavor']['id'],
94 | 'image_id': s['image']['id'],
95 | 'tenant_id': s['tenant_id'],
96 | 'hypervisor': s['OS-EXT-SRV-ATTR:host'],
97 | 'project': str(project)
98 | })
99 | return l
100 |
101 |
102 | def load_users_list():
103 | l = []
104 | for project in projects.keys():
105 | headers['X-Auth-Token'] = get_token(project)
106 | r = requests.get("%s/users" % auth_cache[project]['keystone_admin_url'], headers=headers)
107 | for u in r.json()['users']:
108 | l.append({
109 | 'id': u['id'],
110 | 'name': u['name'],
111 | 'email': u['email'],
112 | 'username': u['username']
113 | })
114 | return l
115 |
116 |
117 | def load_flavors_list():
118 | l = []
119 | for project in projects.keys():
120 | headers['X-Auth-Token'] = get_token(project)
121 | r = requests.get('%s/flavors/detail' % auth_cache[project]['nova_admin_url'], headers=headers)
122 | for f in r.json()['flavors']:
123 | l.append({
124 | 'id': f['id'],
125 | 'name': f['name'],
126 | 'ram': f['ram'],
127 | 'vcpus': f['vcpus'],
128 | 'disk': f['disk']
129 | })
130 | return l
131 |
132 |
133 | def list_servers():
134 | return app.cache.get('servers_list')
135 |
136 |
137 | def list_users():
138 | return app.cache.get('users_list')
139 |
140 |
141 | def list_flavors():
142 | return app.cache.get('flavors_list')
143 |
144 |
145 | def list_tenants():
146 | return app.cache.get('tenants_list')
147 |
148 |
149 | def list_images():
150 | return app.cache.get('images_list')
151 |
152 |
153 | def get_servers():
154 | return list_servers()
155 |
156 |
157 | def get_server(server_id):
158 | for s in get_servers():
159 | if s['id'] == server_id:
160 | return s
161 | return None
162 |
163 |
164 | def get_users():
165 | return list_users()
166 |
167 |
168 | def get_user(user_id):
169 | for u in get_users():
170 | if u['id'] == user_id:
171 | return u
172 | return None
173 |
174 |
175 | def get_flavors():
176 | return list_flavors()
177 |
178 |
179 | def get_flavor(flavor_id):
180 | for f in get_flavors():
181 | if f['id'] == flavor_id:
182 | return f
183 | return None
184 |
185 |
186 | def get_tenants():
187 | return list_tenants()
188 |
189 |
190 | def get_tenant(tenant_id):
191 | for t in get_tenants():
192 | if t['id'] == tenant_id:
193 | return t
194 | return None
195 |
196 |
197 | def get_images():
198 | return list_images()
199 |
200 |
201 | def get_image(image_id):
202 | for i in get_images():
203 | if i['id'] == image_id:
204 | return i
205 | return None
206 |
--------------------------------------------------------------------------------
/app/static/js/crossroads.min.js:
--------------------------------------------------------------------------------
1 | /** @license
2 | * crossroads
3 | * Author: Miller Medeiros | MIT License
4 | * v0.12.2 (2015/07/31 18:37)
5 | */
6 | ;(function(){var a=function(a){function e(a,b){if(a.indexOf)return a.indexOf(b);var c=a.length;while(c--)if(a[c]===b)return c;return-1}function f(a,b){var c=e(a,b);c!==-1&&a.splice(c,1)}function g(a,b){return"[object "+b+"]"===Object.prototype.toString.call(a)}function h(a){return g(a,"RegExp")}function i(a){return g(a,"Array")}function j(a){return typeof a=="function"}function k(a){var b;return a===null||a==="null"?b=null:a==="true"?b=!0:a==="false"?b=!1:a===d||a==="undefined"?b=d:a===""||isNaN(a)?b=a:b=parseFloat(a),b}function l(a){var b=a.length,c=[];while(b--)c[b]=k(a[b]);return c}function m(a,b){var c=(a||"").replace("?","").split("&"),d=/([^=]+)=(.+)/,e=-1,f={},g,h,j,l;while(h=c[++e])g=h.indexOf("="),l=h.substring(0,g),j=decodeURIComponent(h.substring(g+1)),b!==!1&&(j=k(j)),l in f?i(f[l])?f[l].push(j):f[l]=[f[l],j]:f[l]=j;return f}function n(){this.bypassed=new a.Signal,this.routed=new a.Signal,this._routes=[],this._prevRoutes=[],this._piped=[],this.resetState()}function o(b,c,d,e){var f=h(b),g=e.patternLexer;this._router=e,this._pattern=b,this._paramsIds=f?null:g.getParamIds(b),this._optionalParamsIds=f?null:g.getOptionalParamsIds(b),this._matchRegexp=f?b:g.compilePattern(b,e.ignoreCase),this.matched=new a.Signal,this.switched=new a.Signal,c&&this.matched.add(c),this._priority=d||0}var b,c,d;return c=/t(.+)?/.exec("t")[1]==="",n.prototype={greedy:!1,greedyEnabled:!0,ignoreCase:!0,ignoreState:!1,shouldTypecast:!1,normalizeFn:null,resetState:function(){this._prevRoutes.length=0,this._prevMatchedRequest=null,this._prevBypassedRequest=null},create:function(){return new n},addRoute:function(a,b,c){var d=new o(a,b,c,this);return this._sortedInsert(d),d},removeRoute:function(a){f(this._routes,a),a._destroy()},removeAllRoutes:function(){var a=this.getNumRoutes();while(a--)this._routes[a]._destroy();this._routes.length=0},parse:function(a,b){a=a||"",b=b||[];if(!this.ignoreState&&(a===this._prevMatchedRequest||a===this._prevBypassedRequest))return;var c=this._getMatchedRoutes(a),d=0,e=c.length,f;if(e){this._prevMatchedRequest=a,this._notifyPrevRoutes(c,a),this._prevRoutes=c;while(d'
26 | + ''
27 | + '
'
28 | + ' '
29 | + instance.name
30 | + ' ('
31 | + instance.id
32 | + ')'
33 | + '
'
34 | + ''
35 | + ''
36 | + '
'
37 | + ''
38 | }
39 |
40 |
41 | print_server_details = function(server) {
42 | ips = ''
43 | for (i=0; i < server.networks.length; i++) {
44 | ips += 'IP: ' + server.networks[i].addr + ' (' + server.networks[i].type + ')
'
45 | }
46 | return ''
47 | + '
'
48 | + '
Environment: ' + server.project + '
'
49 | + '
Hypervisor Host: ' + server.hypervisor + '
'
50 | + '
Owner: ' + server.user.name + ' (' + server.user.email + ')
'
51 | + '
Tenant: ' + server.tenant.name + '
'
52 | + '
'
53 | + '
'
54 | + ips
55 | + '
'
56 | + '
'
57 | + '
Flavor Name: ' + server.flavor.name + '
'
58 | + '
vCPUs: ' + server.flavor.vcpus + '
'
59 | + '
RAM: ' + fmtBytes(server.flavor.ram*1024*1024) + '
'
60 | + '
Disk: ' + fmtBytes(server.flavor.disk*1024*1024*1024) + '
'
61 | + '
'
62 | + '
'
63 | + '
Image Name: ' + server.image.name + '
'
64 | + '
'
65 | + '
'
66 | + ''
67 | + '
'
70 | + '
'
73 | + '
'
74 | }
75 |
76 | print_current_report = function(n, d) {
77 | line = ''
78 | + '| ' + n + ' | '
79 | + '' + d.vms + ' | '
80 | + '' + d.cpu + ' | '
81 | + '' + d.mem + ' | '
82 | + '' + d.disk + ' | '
83 | + '
';
84 | return line;
85 | }
86 |
87 | load_servers = function() {
88 | $('.panel-instance-list').empty();
89 | $.ajax({
90 | url: '/servers',
91 | cache: false,
92 | statusCode: {
93 | 200: function(data) {
94 | for (i = 0; i < data.instances.length; i++) {
95 | $('.panel-instance-list').append(
96 | print_server_container(data.instances[i])
97 | );
98 | if (i == data.instances.length-1) {
99 | turn_collapsable();
100 | }
101 | }
102 | }
103 | }
104 | });
105 | };
106 |
107 | load_current_report = function() {
108 | $('.table-report > tbody').empty();
109 | $.ajax({
110 | url: '/report/resources',
111 | cache: false,
112 | statusCode: {
113 | 200: function(data) {
114 | $.each(data, function(index, value) {
115 | if ( index != '_total' && index != '_size') {
116 | $('.table-report > tbody').append(
117 | print_current_report(index, data[index])
118 | );
119 | }
120 | if (data['_size'] == $('.table-report > tbody tr').length ) {
121 | $('.table-report > tbody').append(
122 | print_current_report('Total', data['_total'])
123 | );
124 | }
125 | });
126 | }
127 | }
128 | });
129 | };
130 |
131 | search_servers = function(s) {
132 | $('.panel-instance-list').empty();
133 | $.ajax({
134 | url: '/search/' + s,
135 | cache: false,
136 | statusCode: {
137 | 200: function(data) {
138 | for (i = 0; i < data.instances.length; i++) {
139 | $('.panel-instance-list').append(
140 | print_server_container(data.instances[i])
141 | );
142 | if (i == data.instances.length-1) {
143 | turn_collapsable();
144 | }
145 | }
146 | }
147 | }
148 | });
149 | };
150 |
151 | get_server = function(id) {
152 | $.ajax({
153 | url: '/server/' + id,
154 | cache: false,
155 | statusCode: {
156 | 200: function(data) {
157 | $('.panel-instance[data-instance="' + id + '"] .panel-body').html(
158 | print_server_details(data)
159 | );
160 | load_graph('.cpu_usage', data.id, 'cpu');
161 | load_graph('.mem_usage', data.id, 'mem');
162 | reloadGraph = setInterval(function() {
163 | load_graph('.cpu_usage', data.id, 'cpu');
164 | load_graph('.mem_usage', data.id, 'mem');
165 | }, 60000);
166 | }
167 | }
168 | });
169 | };
170 |
171 | turn_collapsable = function() {
172 | $('.cd-collapsable').on("click", function (e) {
173 | clearInterval(reloadGraph);
174 | $('.panel-heading.cd-collapsable').not('.cd-collapsed').parent('.panel').find('.panel-body').empty().slideUp();
175 | if ($(this).hasClass('cd-collapsed')) {
176 | // expand the panel
177 | $(this).parent('.panel').find('.panel-body.hide');
178 | $(this).parent('.panel').find('.panel-body').slideDown().removeClass('hide');
179 | $(this).removeClass('cd-collapsed');
180 | get_server($(this).parent('.panel').data('instance'));
181 | }
182 | else {
183 | // collapse the panel
184 | $(this).parent('.panel').find('.panel-body').slideUp();
185 | $(this).addClass('cd-collapsed');
186 | }
187 | });
188 | }
189 |
190 |
191 | load_graph = function(target, instance_id, chart_type) {
192 | var chart_type = typeof chart_type !== 'undefined' ? chart_type : 'cpu';
193 | var chart_title = '';
194 | var chart_label = '';
195 | if (chart_type == 'cpu') {
196 | chart_title = 'CPU usage';
197 | chart_label = 'CPU';
198 | } else if (chart_type == 'mem') {
199 | chart_title = 'Memory usage';
200 | chart_label = 'Memory';
201 | }
202 | $.getJSON('/monitoring/' + instance_id + '/' + chart_type, function (data) {
203 |
204 | $(target).highcharts({
205 | chart: {
206 | zoomType: 'x',
207 | height: '300'
208 | },
209 | title: {
210 | text: chart_title,
211 | align: 'left'
212 | },
213 | xAxis: {
214 | type: 'datetime'
215 | },
216 | yAxis: {
217 | title: {
218 | text: '% of usage'
219 | },
220 | min: 0
221 | },
222 | legend: {
223 | enabled: false
224 | },
225 | plotOptions: {
226 | area: {
227 | fillColor: {
228 | linearGradient: {
229 | x1: 0,
230 | y1: 0,
231 | x2: 0,
232 | y2: 1
233 | },
234 | stops: [
235 | [0, Highcharts.getOptions().colors[0]],
236 | [1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
237 | ]
238 | },
239 | marker: {
240 | radius: 2
241 | },
242 | lineWidth: 1,
243 | states: {
244 | hover: {
245 | lineWidth: 1
246 | }
247 | },
248 | threshold: null
249 | }
250 | },
251 |
252 | series: [{
253 | type: 'area',
254 | name: chart_label,
255 | data: data.data
256 | }]
257 | });
258 | });
259 | }
260 |
261 | load_url = function(url, container) {
262 | $.ajax({
263 | url: url,
264 | cache: false,
265 | statusCode: {
266 | 200: function(data) {
267 | $(container).empty();
268 | $(container).html(data);
269 | }
270 | }
271 | });
272 | };
273 |
274 | change_url = function(url) {
275 | fullUrl = '/dashboard'
276 | if (url) {
277 | fullUrl += url;
278 | }
279 | window.history.pushState('', '', fullUrl);
280 | }
281 |
282 | _load_all_instances = function() {
283 | url = '/instances';
284 | load_url(url, '.content');
285 | change_url(url);
286 | }
287 |
288 | _load_report = function() {
289 | url = '/report';
290 | load_url(url, '.content');
291 | change_url(url);
292 | }
293 |
294 | $('.link.instances').click(function() {
295 | _load_all_instances();
296 | });
297 |
298 | $('.link.report').click(function() {
299 | _load_report();
300 | });
301 |
302 | });
303 |
--------------------------------------------------------------------------------
/app/static/js/jquery.flot.time.js:
--------------------------------------------------------------------------------
1 | /* Pretty handling of time axes.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | Set axis.mode to "time" to enable. See the section "Time series data" in
7 | API.txt for details.
8 |
9 | */
10 |
11 | (function($) {
12 |
13 | var options = {
14 | xaxis: {
15 | timezone: null, // "browser" for local to the client or timezone for timezone-js
16 | timeformat: null, // format string to use
17 | twelveHourClock: false, // 12 or 24 time in time mode
18 | monthNames: null // list of names of months
19 | }
20 | };
21 |
22 | // round to nearby lower multiple of base
23 |
24 | function floorInBase(n, base) {
25 | return base * Math.floor(n / base);
26 | }
27 |
28 | // Returns a string with the date d formatted according to fmt.
29 | // A subset of the Open Group's strftime format is supported.
30 |
31 | function formatDate(d, fmt, monthNames, dayNames) {
32 |
33 | if (typeof d.strftime == "function") {
34 | return d.strftime(fmt);
35 | }
36 |
37 | var leftPad = function(n, pad) {
38 | n = "" + n;
39 | pad = "" + (pad == null ? "0" : pad);
40 | return n.length == 1 ? pad + n : n;
41 | };
42 |
43 | var r = [];
44 | var escape = false;
45 | var hours = d.getHours();
46 | var isAM = hours < 12;
47 |
48 | if (monthNames == null) {
49 | monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
50 | }
51 |
52 | if (dayNames == null) {
53 | dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
54 | }
55 |
56 | var hours12;
57 |
58 | if (hours > 12) {
59 | hours12 = hours - 12;
60 | } else if (hours == 0) {
61 | hours12 = 12;
62 | } else {
63 | hours12 = hours;
64 | }
65 |
66 | for (var i = 0; i < fmt.length; ++i) {
67 |
68 | var c = fmt.charAt(i);
69 |
70 | if (escape) {
71 | switch (c) {
72 | case 'a': c = "" + dayNames[d.getDay()]; break;
73 | case 'b': c = "" + monthNames[d.getMonth()]; break;
74 | case 'd': c = leftPad(d.getDate()); break;
75 | case 'e': c = leftPad(d.getDate(), " "); break;
76 | case 'h': // For back-compat with 0.7; remove in 1.0
77 | case 'H': c = leftPad(hours); break;
78 | case 'I': c = leftPad(hours12); break;
79 | case 'l': c = leftPad(hours12, " "); break;
80 | case 'm': c = leftPad(d.getMonth() + 1); break;
81 | case 'M': c = leftPad(d.getMinutes()); break;
82 | // quarters not in Open Group's strftime specification
83 | case 'q':
84 | c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
85 | case 'S': c = leftPad(d.getSeconds()); break;
86 | case 'y': c = leftPad(d.getFullYear() % 100); break;
87 | case 'Y': c = "" + d.getFullYear(); break;
88 | case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
89 | case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
90 | case 'w': c = "" + d.getDay(); break;
91 | }
92 | r.push(c);
93 | escape = false;
94 | } else {
95 | if (c == "%") {
96 | escape = true;
97 | } else {
98 | r.push(c);
99 | }
100 | }
101 | }
102 |
103 | return r.join("");
104 | }
105 |
106 | // To have a consistent view of time-based data independent of which time
107 | // zone the client happens to be in we need a date-like object independent
108 | // of time zones. This is done through a wrapper that only calls the UTC
109 | // versions of the accessor methods.
110 |
111 | function makeUtcWrapper(d) {
112 |
113 | function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
114 | sourceObj[sourceMethod] = function() {
115 | return targetObj[targetMethod].apply(targetObj, arguments);
116 | };
117 | };
118 |
119 | var utc = {
120 | date: d
121 | };
122 |
123 | // support strftime, if found
124 |
125 | if (d.strftime != undefined) {
126 | addProxyMethod(utc, "strftime", d, "strftime");
127 | }
128 |
129 | addProxyMethod(utc, "getTime", d, "getTime");
130 | addProxyMethod(utc, "setTime", d, "setTime");
131 |
132 | var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
133 |
134 | for (var p = 0; p < props.length; p++) {
135 | addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
136 | addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
137 | }
138 |
139 | return utc;
140 | };
141 |
142 | // select time zone strategy. This returns a date-like object tied to the
143 | // desired timezone
144 |
145 | function dateGenerator(ts, opts) {
146 | if (opts.timezone == "browser") {
147 | return new Date(ts);
148 | } else if (!opts.timezone || opts.timezone == "utc") {
149 | return makeUtcWrapper(new Date(ts));
150 | } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
151 | var d = new timezoneJS.Date();
152 | // timezone-js is fickle, so be sure to set the time zone before
153 | // setting the time.
154 | d.setTimezone(opts.timezone);
155 | d.setTime(ts);
156 | return d;
157 | } else {
158 | return makeUtcWrapper(new Date(ts));
159 | }
160 | }
161 |
162 | // map of app. size of time units in milliseconds
163 |
164 | var timeUnitSize = {
165 | "second": 1000,
166 | "minute": 60 * 1000,
167 | "hour": 60 * 60 * 1000,
168 | "day": 24 * 60 * 60 * 1000,
169 | "month": 30 * 24 * 60 * 60 * 1000,
170 | "quarter": 3 * 30 * 24 * 60 * 60 * 1000,
171 | "year": 365.2425 * 24 * 60 * 60 * 1000
172 | };
173 |
174 | // the allowed tick sizes, after 1 year we use
175 | // an integer algorithm
176 |
177 | var baseSpec = [
178 | [1, "second"], [2, "second"], [5, "second"], [10, "second"],
179 | [30, "second"],
180 | [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
181 | [30, "minute"],
182 | [1, "hour"], [2, "hour"], [4, "hour"],
183 | [8, "hour"], [12, "hour"],
184 | [1, "day"], [2, "day"], [3, "day"],
185 | [0.25, "month"], [0.5, "month"], [1, "month"],
186 | [2, "month"]
187 | ];
188 |
189 | // we don't know which variant(s) we'll need yet, but generating both is
190 | // cheap
191 |
192 | var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
193 | [1, "year"]]);
194 | var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
195 | [1, "year"]]);
196 |
197 | function init(plot) {
198 | plot.hooks.processOptions.push(function (plot, options) {
199 | $.each(plot.getAxes(), function(axisName, axis) {
200 |
201 | var opts = axis.options;
202 |
203 | if (opts.mode == "time") {
204 | axis.tickGenerator = function(axis) {
205 |
206 | var ticks = [];
207 | var d = dateGenerator(axis.min, opts);
208 | var minSize = 0;
209 |
210 | // make quarter use a possibility if quarters are
211 | // mentioned in either of these options
212 |
213 | var spec = (opts.tickSize && opts.tickSize[1] ===
214 | "quarter") ||
215 | (opts.minTickSize && opts.minTickSize[1] ===
216 | "quarter") ? specQuarters : specMonths;
217 |
218 | if (opts.minTickSize != null) {
219 | if (typeof opts.tickSize == "number") {
220 | minSize = opts.tickSize;
221 | } else {
222 | minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
223 | }
224 | }
225 |
226 | for (var i = 0; i < spec.length - 1; ++i) {
227 | if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
228 | + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
229 | && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
230 | break;
231 | }
232 | }
233 |
234 | var size = spec[i][0];
235 | var unit = spec[i][1];
236 |
237 | // special-case the possibility of several years
238 |
239 | if (unit == "year") {
240 |
241 | // if given a minTickSize in years, just use it,
242 | // ensuring that it's an integer
243 |
244 | if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
245 | size = Math.floor(opts.minTickSize[0]);
246 | } else {
247 |
248 | var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
249 | var norm = (axis.delta / timeUnitSize.year) / magn;
250 |
251 | if (norm < 1.5) {
252 | size = 1;
253 | } else if (norm < 3) {
254 | size = 2;
255 | } else if (norm < 7.5) {
256 | size = 5;
257 | } else {
258 | size = 10;
259 | }
260 |
261 | size *= magn;
262 | }
263 |
264 | // minimum size for years is 1
265 |
266 | if (size < 1) {
267 | size = 1;
268 | }
269 | }
270 |
271 | axis.tickSize = opts.tickSize || [size, unit];
272 | var tickSize = axis.tickSize[0];
273 | unit = axis.tickSize[1];
274 |
275 | var step = tickSize * timeUnitSize[unit];
276 |
277 | if (unit == "second") {
278 | d.setSeconds(floorInBase(d.getSeconds(), tickSize));
279 | } else if (unit == "minute") {
280 | d.setMinutes(floorInBase(d.getMinutes(), tickSize));
281 | } else if (unit == "hour") {
282 | d.setHours(floorInBase(d.getHours(), tickSize));
283 | } else if (unit == "month") {
284 | d.setMonth(floorInBase(d.getMonth(), tickSize));
285 | } else if (unit == "quarter") {
286 | d.setMonth(3 * floorInBase(d.getMonth() / 3,
287 | tickSize));
288 | } else if (unit == "year") {
289 | d.setFullYear(floorInBase(d.getFullYear(), tickSize));
290 | }
291 |
292 | // reset smaller components
293 |
294 | d.setMilliseconds(0);
295 |
296 | if (step >= timeUnitSize.minute) {
297 | d.setSeconds(0);
298 | }
299 | if (step >= timeUnitSize.hour) {
300 | d.setMinutes(0);
301 | }
302 | if (step >= timeUnitSize.day) {
303 | d.setHours(0);
304 | }
305 | if (step >= timeUnitSize.day * 4) {
306 | d.setDate(1);
307 | }
308 | if (step >= timeUnitSize.month * 2) {
309 | d.setMonth(floorInBase(d.getMonth(), 3));
310 | }
311 | if (step >= timeUnitSize.quarter * 2) {
312 | d.setMonth(floorInBase(d.getMonth(), 6));
313 | }
314 | if (step >= timeUnitSize.year) {
315 | d.setMonth(0);
316 | }
317 |
318 | var carry = 0;
319 | var v = Number.NaN;
320 | var prev;
321 |
322 | do {
323 |
324 | prev = v;
325 | v = d.getTime();
326 | ticks.push(v);
327 |
328 | if (unit == "month" || unit == "quarter") {
329 | if (tickSize < 1) {
330 |
331 | // a bit complicated - we'll divide the
332 | // month/quarter up but we need to take
333 | // care of fractions so we don't end up in
334 | // the middle of a day
335 |
336 | d.setDate(1);
337 | var start = d.getTime();
338 | d.setMonth(d.getMonth() +
339 | (unit == "quarter" ? 3 : 1));
340 | var end = d.getTime();
341 | d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
342 | carry = d.getHours();
343 | d.setHours(0);
344 | } else {
345 | d.setMonth(d.getMonth() +
346 | tickSize * (unit == "quarter" ? 3 : 1));
347 | }
348 | } else if (unit == "year") {
349 | d.setFullYear(d.getFullYear() + tickSize);
350 | } else {
351 | d.setTime(v + step);
352 | }
353 | } while (v < axis.max && v != prev);
354 |
355 | return ticks;
356 | };
357 |
358 | axis.tickFormatter = function (v, axis) {
359 |
360 | var d = dateGenerator(v, axis.options);
361 |
362 | // first check global format
363 |
364 | if (opts.timeformat != null) {
365 | return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
366 | }
367 |
368 | // possibly use quarters if quarters are mentioned in
369 | // any of these places
370 |
371 | var useQuarters = (axis.options.tickSize &&
372 | axis.options.tickSize[1] == "quarter") ||
373 | (axis.options.minTickSize &&
374 | axis.options.minTickSize[1] == "quarter");
375 |
376 | var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
377 | var span = axis.max - axis.min;
378 | var suffix = (opts.twelveHourClock) ? " %p" : "";
379 | var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
380 | var fmt;
381 |
382 | if (t < timeUnitSize.minute) {
383 | fmt = hourCode + ":%M:%S" + suffix;
384 | } else if (t < timeUnitSize.day) {
385 | if (span < 2 * timeUnitSize.day) {
386 | fmt = hourCode + ":%M" + suffix;
387 | } else {
388 | fmt = "%b %d " + hourCode + ":%M" + suffix;
389 | }
390 | } else if (t < timeUnitSize.month) {
391 | fmt = "%b %d";
392 | } else if ((useQuarters && t < timeUnitSize.quarter) ||
393 | (!useQuarters && t < timeUnitSize.year)) {
394 | if (span < timeUnitSize.year) {
395 | fmt = "%b";
396 | } else {
397 | fmt = "%b %Y";
398 | }
399 | } else if (useQuarters && t < timeUnitSize.year) {
400 | if (span < timeUnitSize.year) {
401 | fmt = "Q%q";
402 | } else {
403 | fmt = "Q%q %Y";
404 | }
405 | } else {
406 | fmt = "%Y";
407 | }
408 |
409 | var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
410 |
411 | return rt;
412 | };
413 | }
414 | });
415 | });
416 | }
417 |
418 | $.plot.plugins.push({
419 | init: init,
420 | options: options,
421 | name: 'time',
422 | version: '1.0'
423 | });
424 |
425 | // Time-axis support used to be in Flot core, which exposed the
426 | // formatDate function on the plot object. Various plugins depend
427 | // on the function, so we need to re-expose it here.
428 |
429 | $.plot.formatDate = formatDate;
430 | $.plot.dateGenerator = dateGenerator;
431 |
432 | })(jQuery);
433 |
--------------------------------------------------------------------------------
/app/static/js/jquery.flot.tooltip.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jquery.flot.tooltip
3 | *
4 | * description: easy-to-use tooltips for Flot charts
5 | * version: 0.8.4
6 | * authors: Krzysztof Urbas @krzysu [myviews.pl],Evan Steinkerchner @Roundaround
7 | * website: https://github.com/krzysu/flot.tooltip
8 | *
9 | * build on 2014-08-06
10 | * released under MIT License, 2012
11 | */
12 | (function ($) {
13 | // plugin options, default values
14 | var defaultOptions = {
15 | tooltip: false,
16 | tooltipOpts: {
17 | id: "flotTip",
18 | content: "%s | X: %x | Y: %y",
19 | // allowed templates are:
20 | // %s -> series label,
21 | // %lx -> x axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
22 | // %ly -> y axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
23 | // %x -> X value,
24 | // %y -> Y value,
25 | // %x.2 -> precision of X value,
26 | // %p -> percent
27 | xDateFormat: null,
28 | yDateFormat: null,
29 | monthNames: null,
30 | dayNames: null,
31 | shifts: {
32 | x: 10,
33 | y: 20
34 | },
35 | defaultTheme: true,
36 | lines: false,
37 |
38 | // callbacks
39 | onHover: function (flotItem, $tooltipEl) {},
40 |
41 | $compat: false
42 | }
43 | };
44 |
45 | // object
46 | var FlotTooltip = function (plot) {
47 | // variables
48 | this.tipPosition = {x: 0, y: 0};
49 |
50 | this.init(plot);
51 | };
52 |
53 | // main plugin function
54 | FlotTooltip.prototype.init = function (plot) {
55 | var that = this;
56 |
57 | // detect other flot plugins
58 | var plotPluginsLength = $.plot.plugins.length;
59 | this.plotPlugins = [];
60 |
61 | if (plotPluginsLength) {
62 | for (var p = 0; p < plotPluginsLength; p++) {
63 | this.plotPlugins.push($.plot.plugins[p].name);
64 | }
65 | }
66 |
67 | plot.hooks.bindEvents.push(function (plot, eventHolder) {
68 |
69 | // get plot options
70 | that.plotOptions = plot.getOptions();
71 |
72 | // if not enabled return
73 | if (that.plotOptions.tooltip === false || typeof that.plotOptions.tooltip === 'undefined') return;
74 |
75 | // shortcut to access tooltip options
76 | that.tooltipOptions = that.plotOptions.tooltipOpts;
77 |
78 | if (that.tooltipOptions.$compat) {
79 | that.wfunc = 'width';
80 | that.hfunc = 'height';
81 | } else {
82 | that.wfunc = 'innerWidth';
83 | that.hfunc = 'innerHeight';
84 | }
85 |
86 | // create tooltip DOM element
87 | var $tip = that.getDomElement();
88 |
89 | // bind event
90 | $( plot.getPlaceholder() ).bind("plothover", plothover);
91 |
92 | $(eventHolder).bind('mousemove', mouseMove);
93 | });
94 |
95 | plot.hooks.shutdown.push(function (plot, eventHolder){
96 | $(plot.getPlaceholder()).unbind("plothover", plothover);
97 | $(eventHolder).unbind("mousemove", mouseMove);
98 | });
99 |
100 | function mouseMove(e){
101 | var pos = {};
102 | pos.x = e.pageX;
103 | pos.y = e.pageY;
104 | plot.setTooltipPosition(pos);
105 | }
106 |
107 | function plothover(event, pos, item) {
108 | // Simple distance formula.
109 | var lineDistance = function (p1x, p1y, p2x, p2y) {
110 | return Math.sqrt((p2x - p1x) * (p2x - p1x) + (p2y - p1y) * (p2y - p1y));
111 | };
112 |
113 | // Here is some voodoo magic for determining the distance to a line form a given point {x, y}.
114 | var dotLineLength = function (x, y, x0, y0, x1, y1, o) {
115 | if (o && !(o =
116 | function (x, y, x0, y0, x1, y1) {
117 | if (typeof x0 !== 'undefined') return { x: x0, y: y };
118 | else if (typeof y0 !== 'undefined') return { x: x, y: y0 };
119 |
120 | var left,
121 | tg = -1 / ((y1 - y0) / (x1 - x0));
122 |
123 | return {
124 | x: left = (x1 * (x * tg - y + y0) + x0 * (x * -tg + y - y1)) / (tg * (x1 - x0) + y0 - y1),
125 | y: tg * left - tg * x + y
126 | };
127 | } (x, y, x0, y0, x1, y1),
128 | o.x >= Math.min(x0, x1) && o.x <= Math.max(x0, x1) && o.y >= Math.min(y0, y1) && o.y <= Math.max(y0, y1))
129 | ) {
130 | var l1 = lineDistance(x, y, x0, y0), l2 = lineDistance(x, y, x1, y1);
131 | return l1 > l2 ? l2 : l1;
132 | } else {
133 | var a = y0 - y1, b = x1 - x0, c = x0 * y1 - y0 * x1;
134 | return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b);
135 | }
136 | };
137 |
138 | if (item) {
139 | plot.showTooltip(item, pos);
140 | } else if (that.plotOptions.series.lines.show && that.tooltipOptions.lines === true) {
141 | var maxDistance = that.plotOptions.grid.mouseActiveRadius;
142 |
143 | var closestTrace = {
144 | distance: maxDistance + 1
145 | };
146 |
147 | $.each(plot.getData(), function (i, series) {
148 | var xBeforeIndex = 0,
149 | xAfterIndex = -1;
150 |
151 | // Our search here assumes our data is sorted via the x-axis.
152 | // TODO: Improve efficiency somehow - search smaller sets of data.
153 | for (var j = 1; j < series.data.length; j++) {
154 | if (series.data[j - 1][0] <= pos.x && series.data[j][0] >= pos.x) {
155 | xBeforeIndex = j - 1;
156 | xAfterIndex = j;
157 | }
158 | }
159 |
160 | if (xAfterIndex === -1) {
161 | plot.hideTooltip();
162 | return;
163 | }
164 |
165 | var pointPrev = { x: series.data[xBeforeIndex][0], y: series.data[xBeforeIndex][1] },
166 | pointNext = { x: series.data[xAfterIndex][0], y: series.data[xAfterIndex][1] };
167 |
168 | var distToLine = dotLineLength(series.xaxis.p2c(pos.x), series.yaxis.p2c(pos.y), series.xaxis.p2c(pointPrev.x),
169 | series.yaxis.p2c(pointPrev.y), series.xaxis.p2c(pointNext.x), series.yaxis.p2c(pointNext.y), false);
170 |
171 | if (distToLine < closestTrace.distance) {
172 |
173 | var closestIndex = lineDistance(pointPrev.x, pointPrev.y, pos.x, pos.y) <
174 | lineDistance(pos.x, pos.y, pointNext.x, pointNext.y) ? xBeforeIndex : xAfterIndex;
175 |
176 | var pointSize = series.datapoints.pointsize;
177 |
178 | // Calculate the point on the line vertically closest to our cursor.
179 | var pointOnLine = [
180 | pos.x,
181 | pointPrev.y + ((pointNext.y - pointPrev.y) * ((pos.x - pointPrev.x) / (pointNext.x - pointPrev.x)))
182 | ];
183 |
184 | var item = {
185 | datapoint: pointOnLine,
186 | dataIndex: closestIndex,
187 | series: series,
188 | seriesIndex: i
189 | };
190 |
191 | closestTrace = {
192 | distance: distToLine,
193 | item: item
194 | };
195 | }
196 | });
197 |
198 | if (closestTrace.distance < maxDistance + 1)
199 | plot.showTooltip(closestTrace.item, pos);
200 | else
201 | plot.hideTooltip();
202 | } else {
203 | plot.hideTooltip();
204 | }
205 | }
206 |
207 | // Quick little function for setting the tooltip position.
208 | plot.setTooltipPosition = function (pos) {
209 | var $tip = that.getDomElement();
210 |
211 | var totalTipWidth = $tip.outerWidth() + that.tooltipOptions.shifts.x;
212 | var totalTipHeight = $tip.outerHeight() + that.tooltipOptions.shifts.y;
213 | if ((pos.x - $(window).scrollLeft()) > ($(window)[that.wfunc]() - totalTipWidth)) {
214 | pos.x -= totalTipWidth;
215 | }
216 | if ((pos.y - $(window).scrollTop()) > ($(window)[that.hfunc]() - totalTipHeight)) {
217 | pos.y -= totalTipHeight;
218 | }
219 | that.tipPosition.x = pos.x;
220 | that.tipPosition.y = pos.y;
221 | };
222 |
223 | // Quick little function for showing the tooltip.
224 | plot.showTooltip = function (target, position) {
225 | var $tip = that.getDomElement();
226 |
227 | // convert tooltip content template to real tipText
228 | var tipText = that.stringFormat(that.tooltipOptions.content, target);
229 |
230 | $tip.html(tipText);
231 | plot.setTooltipPosition({ x: position.pageX, y: position.pageY });
232 | $tip.css({
233 | left: that.tipPosition.x + that.tooltipOptions.shifts.x,
234 | top: that.tipPosition.y + that.tooltipOptions.shifts.y
235 | }).show();
236 |
237 | // run callback
238 | if (typeof that.tooltipOptions.onHover === 'function') {
239 | that.tooltipOptions.onHover(target, $tip);
240 | }
241 | };
242 |
243 | // Quick little function for hiding the tooltip.
244 | plot.hideTooltip = function () {
245 | that.getDomElement().hide().html('');
246 | };
247 | };
248 |
249 | /**
250 | * get or create tooltip DOM element
251 | * @return jQuery object
252 | */
253 | FlotTooltip.prototype.getDomElement = function () {
254 | var $tip = $('#' + this.tooltipOptions.id);
255 |
256 | if( $tip.length === 0 ){
257 | $tip = $('').attr('id', this.tooltipOptions.id);
258 | $tip.appendTo('body').hide().css({position: 'absolute'});
259 |
260 | if(this.tooltipOptions.defaultTheme) {
261 | $tip.css({
262 | 'background': '#fff',
263 | 'z-index': '1040',
264 | 'padding': '0.4em 0.6em',
265 | 'border-radius': '0.5em',
266 | 'font-size': '0.8em',
267 | 'border': '1px solid #111',
268 | 'display': 'none',
269 | 'white-space': 'nowrap'
270 | });
271 | }
272 | }
273 |
274 | return $tip;
275 | };
276 |
277 | /**
278 | * core function, create tooltip content
279 | * @param {string} content - template with tooltip content
280 | * @param {object} item - Flot item
281 | * @return {string} real tooltip content for current item
282 | */
283 | FlotTooltip.prototype.stringFormat = function (content, item) {
284 |
285 | var percentPattern = /%p\.{0,1}(\d{0,})/;
286 | var seriesPattern = /%s/;
287 | var xLabelPattern = /%lx/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
288 | var yLabelPattern = /%ly/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
289 | var xPattern = /%x\.{0,1}(\d{0,})/;
290 | var yPattern = /%y\.{0,1}(\d{0,})/;
291 | var xPatternWithoutPrecision = "%x";
292 | var yPatternWithoutPrecision = "%y";
293 | var customTextPattern = "%ct";
294 |
295 | var x, y, customText, p;
296 |
297 | // for threshold plugin we need to read data from different place
298 | if (typeof item.series.threshold !== "undefined") {
299 | x = item.datapoint[0];
300 | y = item.datapoint[1];
301 | customText = item.datapoint[2];
302 | } else if (typeof item.series.lines !== "undefined" && item.series.lines.steps) {
303 | x = item.series.datapoints.points[item.dataIndex * 2];
304 | y = item.series.datapoints.points[item.dataIndex * 2 + 1];
305 | // TODO: where to find custom text in this variant?
306 | customText = "";
307 | } else {
308 | x = item.series.data[item.dataIndex][0];
309 | y = item.series.data[item.dataIndex][1];
310 | customText = item.series.data[item.dataIndex][2];
311 | }
312 |
313 | // I think this is only in case of threshold plugin
314 | if (item.series.label === null && item.series.originSeries) {
315 | item.series.label = item.series.originSeries.label;
316 | }
317 |
318 | // if it is a function callback get the content string
319 | if (typeof(content) === 'function') {
320 | content = content(item.series.label, x, y, item);
321 | }
322 |
323 | // percent match for pie charts and stacked percent
324 | if (typeof (item.series.percent) !== 'undefined') {
325 | p = item.series.percent;
326 | } else if (typeof (item.series.percents) !== 'undefined') {
327 | p = item.series.percents[item.dataIndex];
328 | }
329 | if (typeof p === 'number') {
330 | content = this.adjustValPrecision(percentPattern, content, p);
331 | }
332 |
333 | // series match
334 | if (typeof(item.series.label) !== 'undefined') {
335 | content = content.replace(seriesPattern, item.series.label);
336 | } else {
337 | //remove %s if label is undefined
338 | content = content.replace(seriesPattern, "");
339 | }
340 |
341 | // x axis label match
342 | if (this.hasAxisLabel('xaxis', item)) {
343 | content = content.replace(xLabelPattern, item.series.xaxis.options.axisLabel);
344 | } else {
345 | //remove %lx if axis label is undefined or axislabels plugin not present
346 | content = content.replace(xLabelPattern, "");
347 | }
348 |
349 | // y axis label match
350 | if (this.hasAxisLabel('yaxis', item)) {
351 | content = content.replace(yLabelPattern, item.series.yaxis.options.axisLabel);
352 | } else {
353 | //remove %ly if axis label is undefined or axislabels plugin not present
354 | content = content.replace(yLabelPattern, "");
355 | }
356 |
357 | // time mode axes with custom dateFormat
358 | if (this.isTimeMode('xaxis', item) && this.isXDateFormat(item)) {
359 | content = content.replace(xPattern, this.timestampToDate(x, this.tooltipOptions.xDateFormat, item.series.xaxis.options));
360 | }
361 | if (this.isTimeMode('yaxis', item) && this.isYDateFormat(item)) {
362 | content = content.replace(yPattern, this.timestampToDate(y, this.tooltipOptions.yDateFormat, item.series.yaxis.options));
363 | }
364 |
365 | // set precision if defined
366 | if (typeof x === 'number') {
367 | content = this.adjustValPrecision(xPattern, content, x);
368 | }
369 | if (typeof y === 'number') {
370 | content = this.adjustValPrecision(yPattern, content, y);
371 | }
372 |
373 | // change x from number to given label, if given
374 | if (typeof item.series.xaxis.ticks !== 'undefined') {
375 |
376 | var ticks;
377 | if (this.hasRotatedXAxisTicks(item)) {
378 | // xaxis.ticks will be an empty array if tickRotor is being used, but the values are available in rotatedTicks
379 | ticks = 'rotatedTicks';
380 | } else {
381 | ticks = 'ticks';
382 | }
383 |
384 | // see https://github.com/krzysu/flot.tooltip/issues/65
385 | var tickIndex = item.dataIndex + item.seriesIndex;
386 |
387 | if (item.series.xaxis[ticks].length > tickIndex && !this.isTimeMode('xaxis', item)) {
388 | var valueX = (this.isCategoriesMode('xaxis', item)) ? item.series.xaxis[ticks][tickIndex].label : item.series.xaxis[ticks][tickIndex].v;
389 | if (valueX === x) {
390 | content = content.replace(xPattern, item.series.xaxis[ticks][tickIndex].label);
391 | }
392 | }
393 | }
394 |
395 | // change y from number to given label, if given
396 | if (typeof item.series.yaxis.ticks !== 'undefined') {
397 | for (var index in item.series.yaxis.ticks) {
398 | if (item.series.yaxis.ticks.hasOwnProperty(index)) {
399 | var valueY = (this.isCategoriesMode('yaxis', item)) ? item.series.yaxis.ticks[index].label : item.series.yaxis.ticks[index].v;
400 | if (valueY === y) {
401 | content = content.replace(yPattern, item.series.yaxis.ticks[index].label);
402 | }
403 | }
404 | }
405 | }
406 |
407 | // if no value customization, use tickFormatter by default
408 | if (typeof item.series.xaxis.tickFormatter !== 'undefined') {
409 | //escape dollar
410 | content = content.replace(xPatternWithoutPrecision, item.series.xaxis.tickFormatter(x, item.series.xaxis).replace(/\$/g, '$$'));
411 | }
412 | if (typeof item.series.yaxis.tickFormatter !== 'undefined') {
413 | //escape dollar
414 | content = content.replace(yPatternWithoutPrecision, item.series.yaxis.tickFormatter(y, item.series.yaxis).replace(/\$/g, '$$'));
415 | }
416 |
417 | if (customText)
418 | content = content.replace(customTextPattern, customText);
419 |
420 | return content;
421 | };
422 |
423 | // helpers just for readability
424 | FlotTooltip.prototype.isTimeMode = function (axisName, item) {
425 | return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'time');
426 | };
427 |
428 | FlotTooltip.prototype.isXDateFormat = function (item) {
429 | return (typeof this.tooltipOptions.xDateFormat !== 'undefined' && this.tooltipOptions.xDateFormat !== null);
430 | };
431 |
432 | FlotTooltip.prototype.isYDateFormat = function (item) {
433 | return (typeof this.tooltipOptions.yDateFormat !== 'undefined' && this.tooltipOptions.yDateFormat !== null);
434 | };
435 |
436 | FlotTooltip.prototype.isCategoriesMode = function (axisName, item) {
437 | return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'categories');
438 | };
439 |
440 | //
441 | FlotTooltip.prototype.timestampToDate = function (tmst, dateFormat, options) {
442 | var theDate = $.plot.dateGenerator(tmst, options);
443 | return $.plot.formatDate(theDate, dateFormat, this.tooltipOptions.monthNames, this.tooltipOptions.dayNames);
444 | };
445 |
446 | //
447 | FlotTooltip.prototype.adjustValPrecision = function (pattern, content, value) {
448 |
449 | var precision;
450 | var matchResult = content.match(pattern);
451 | if( matchResult !== null ) {
452 | if(RegExp.$1 !== '') {
453 | precision = RegExp.$1;
454 | value = value.toFixed(precision);
455 |
456 | // only replace content if precision exists, in other case use thickformater
457 | content = content.replace(pattern, value);
458 | }
459 | }
460 | return content;
461 | };
462 |
463 | // other plugins detection below
464 |
465 | // check if flot-axislabels plugin (https://github.com/markrcote/flot-axislabels) is used and that an axis label is given
466 | FlotTooltip.prototype.hasAxisLabel = function (axisName, item) {
467 | return ($.inArray(this.plotPlugins, 'axisLabels') !== -1 && typeof item.series[axisName].options.axisLabel !== 'undefined' && item.series[axisName].options.axisLabel.length > 0);
468 | };
469 |
470 | // check whether flot-tickRotor, a plugin which allows rotation of X-axis ticks, is being used
471 | FlotTooltip.prototype.hasRotatedXAxisTicks = function (item) {
472 | return ($.inArray(this.plotPlugins, 'tickRotor') !== -1 && typeof item.series.xaxis.rotatedTicks !== 'undefined');
473 | };
474 |
475 | //
476 | var init = function (plot) {
477 | new FlotTooltip(plot);
478 | };
479 |
480 | // define Flot plugin
481 | $.plot.plugins.push({
482 | init: init,
483 | options: defaultOptions,
484 | name: 'tooltip',
485 | version: '0.8.4'
486 | });
487 |
488 | })(jQuery);
489 |
--------------------------------------------------------------------------------
/app/static/css/bootstrap.icon-large.min.css:
--------------------------------------------------------------------------------
1 | .icon-large{background-image:url("../img/glyphicons.png");background-position:24px 24px;background-repeat:no-repeat;display:inline-block;height:28px;line-height:28px;vertical-align:text-bottom;width:28px}.icon-large.icon-glass{background-position:0 0}.icon-large.icon-leaf{background-position:0 -34px}.icon-large.icon-dog{background-position:0 -69px}.icon-large.icon-user{background-position:0 -104px}.icon-large.icon-girl{background-position:0 -136px}.icon-large.icon-car{background-position:0 -168px}.icon-large.icon-user-add{background-position:0 -200px}.icon-large.icon-user-remove{background-position:0 -232px}.icon-large.icon-film{background-position:0 -264px}.icon-large.icon-magic{background-position:0 -300px}.icon-large.icon-envelope{background-position:0 -330px}.icon-large.icon-camera{background-position:0 -360px}.icon-large.icon-heart{background-position:0 -390px}.icon-large.icon-beach-umbrella{background-position:0 -422px}.icon-large.icon-train{background-position:0 -457px}.icon-large.icon-print{background-position:0 -494px}.icon-large.icon-bin{background-position:0 -528px}.icon-large.icon-trash{background-position:0 -528px}.icon-large.icon-music{background-position:0 -566px}.icon-large.icon-note{background-position:0 -601px}.icon-large.icon-cogwheel{background-position:0 -636px}.icon-large.icon-cog{background-position:0 -636px}.icon-large.icon-home{background-position:0 -670px}.icon-large.icon-snowflake{background-position:0 -706px}.icon-large.icon-fire{background-position:0 -744px}.icon-large.icon-cogwheels{background-position:0 -780px}.icon-large.icon-parents{background-position:0 -816px}.icon-large.icon-binoculars{background-position:0 -848px}.icon-large.icon-road{background-position:0 -882px}.icon-large.icon-search{background-position:0 -916px}.icon-large.icon-cars{background-position:0 -950px}.icon-large.icon-pencil{background-position:0 -985px}.icon-large.icon-bus{background-position:0 -1020px}.icon-large.icon-wifi-alt{background-position:0 -1055px}.icon-large.icon-luggage{background-position:0 -1091px}.icon-large.icon-old-man{background-position:0 -1128px}.icon-large.icon-woman{background-position:0 -1162px}.icon-large.icon-file{background-position:0 -1194px}.icon-large.icon-credit{background-position:0 -1228px}.icon-large.icon-airplane,.icon-large.icon-plane{background-position:0 -1262px}.icon-large.icon-notes{background-position:0 -1297px}.icon-large.icon-stats{background-position:0 -1332px}.icon-large.icon-charts{background-position:0 -1367px}.icon-large.icon-pie-chart{background-position:0 -1401px}.icon-large.icon-group{background-position:0 -1436px}.icon-large.icon-keys{background-position:0 -1468px}.icon-large.icon-calendar{background-position:0 -1504px}.icon-large.icon-router{background-position:0 -1539px}.icon-large.icon-camera-small{background-position:0 -1575px}.icon-large.icon-dislikes{background-position:0 -1609px}.icon-large.icon-star-empty{background-position:0 -1609px}.icon-large.icon-star{background-position:0 -1643px}.icon-large.icon-link{background-position:0 -1677px}.icon-large.icon-eye-open{background-position:-1px -1704px}.icon-large.icon-eye-close{background-position:-1px -1737px}.icon-large.icon-alarm{background-position:0 -1771px}.icon-large.icon-clock{background-position:0 -1807px}.icon-large.icon-time{background-position:0 -1807px}.icon-large.icon-stopwatch{background-position:0 -1841px}.icon-large.icon-projector{background-position:0 -1878px}.icon-large.icon-history{background-position:0 -1913px}.icon-large.icon-truck{background-position:0 -1949px}.icon-large.icon-cargo{background-position:0 -1986px}.icon-large.icon-compass{background-position:-46px 0}.icon-large.icon-keynote{background-position:-46px -34px}.icon-large.icon-attach{background-position:-46px -74px}.icon-large.icon-power{background-position:-46px -108px}.icon-large.icon-off{background-position:-46px -108px}.icon-large.icon-lightbulb{background-position:-46px -142px}.icon-large.icon-tag{background-position:-46px -178px}.icon-large.icon-tags{background-position:-46px -212px}.icon-large.icon-cleaning{background-position:-46px -246px}.icon-large.icon-ruller{background-position:-46px -281px}.icon-large.icon-gift{background-position:-46px -305px}.icon-large.icon-umbrella{background-position:-46px -340px}.icon-large.icon-book{background-position:-46px -378px}.icon-large.icon-bookmark{background-position:-44px -412px}.icon-large.icon-signal{background-position:-46px -446px}.icon-large.icon-cup{background-position:-46px -479px}.icon-large.icon-stroller{background-position:-46px -513px}.icon-large.icon-headphones{background-position:-46px -549px}.icon-large.icon-headset{background-position:-46px -583px}.icon-large.icon-warning-sign{background-position:-46px -621px}.icon-large.icon-signal{background-position:-46px -655px}.icon-large.icon-retweet{background-position:-47px -680px}.icon-large.icon-refresh{background-position:-46px -714px}.icon-large.icon-roundabout{background-position:-46px -750px}.icon-large.icon-random{background-position:-46px -787px}.icon-large.icon-heat{background-position:-46px -817px}.icon-large.icon-repeat{background-position:-46px -852px}.icon-large.icon-display{background-position:-46px -888px}.icon-large.icon-log-book{background-position:-46px -922px}.icon-large.icon-adress-book{background-position:-46px -956px}.icon-large.icon-magnet{background-position:-46px -990px}.icon-large.icon-table{background-position:-46px -1023px}.icon-large.icon-adjust{background-position:-46px -1057px}.icon-large.icon-tint{background-position:-46px -1093px}.icon-large.icon-crop{background-position:-46px -1129px}.icon-large.icon-vector-path-square{background-position:-46px -1165px}.icon-large.icon-vector-path-circle{background-position:-46px -1199px}.icon-large.icon-vector-path-polygon{background-position:-46px -1233px}.icon-large.icon-vector-path-line{background-position:-46px -1268px}.icon-large.icon-vector-path-curve{background-position:-46px -1302px}.icon-large.icon-vector-path-all{background-position:-46px -1336px}.icon-large.icon-font{background-position:-46px -1370px}.icon-large.icon-italic{background-position:-46px -1403px}.icon-large.icon-bold{background-position:-46px -1437px}.icon-large.icon-text-underline{background-position:-46px -1471px}.icon-large.icon-text-strike{background-position:-46px -1505px}.icon-large.icon-text-height{background-position:-46px -1537px}.icon-large.icon-text-width{background-position:-46px -1571px}.icon-large.icon-text-resize{background-position:-46px -1605px}.icon-large.icon-left-indent,.icon-large.icon-indent-left{background-position:-46px -1641px}.icon-large.icon-right-indent,.icon-large.icon-indent-right{background-position:-46px -1673px}.icon-large.icon-align-left{background-position:-46px -1705px}.icon-large.icon-align-center{background-position:-46px -1736px}.icon-large.icon-align-right{background-position:-46px -1767px}.icon-large.icon-justify{background-position:-46px -1798px}.icon-large.icon-align-justify{background-position:-46px -1798px}.icon-large.icon-list{background-position:-46px -1829px}.icon-large.icon-text-smaller{background-position:-46px -1860px}.icon-large.icon-text-bigger{background-position:-46px -1886px}.icon-large.icon-embed{background-position:-47px -1910px}.icon-large.icon-embed-close{background-position:-47px -1940px}.icon-large.icon-adjust{background-position:-46px -1976px}.icon-large.icon-message-full{background-position:-92px 0}.icon-large.icon-message-empty{background-position:-92px -38px}.icon-large.icon-message-in{background-position:-92px -76px}.icon-large.icon-message-out{background-position:-92px -114px}.icon-large.icon-message-plus{background-position:-92px -152px}.icon-large.icon-message-minus{background-position:-92px -185px}.icon-large.icon-message-ban{background-position:-92px -218px}.icon-large.icon-message-flag{background-position:-92px -251px}.icon-large.icon-message-lock{background-position:-92px -284px}.icon-large.icon-message-new{background-position:-92px -318px}.icon-large.icon-inbox{background-position:-92px -350px}.icon-large.icon-inbox-plus{background-position:-92px -383px}.icon-large.icon-inbox-minus{background-position:-92px -420px}.icon-large.icon-inbox-lock{background-position:-92px -457px}.icon-large.icon-inbox-in{background-position:-92px -495px}.icon-large.icon-inbox-out{background-position:-92px -531px}.icon-large.icon-computer-locked{background-position:-92px -567px}.icon-large.icon-computer-service{background-position:-92px -601px}.icon-large.icon-computer-proces{background-position:-92px -635px}.icon-large.icon-phone{background-position:-92px -669px}.icon-large.icon-database-lock{background-position:-92px -704px}.icon-large.icon-database-plus{background-position:-92px -742px}.icon-large.icon-database-minus{background-position:-92px -779px}.icon-large.icon-database-ban{background-position:-92px -816px}.icon-large.icon-folder-open{background-position:-92px -853px}.icon-large.icon-folder-plus{background-position:-92px -885px}.icon-large.icon-folder-minus{background-position:-92px -920px}.icon-large.icon-folder-lock{background-position:-92px -955px}.icon-large.icon-folder-flag{background-position:-92px -991px}.icon-large.icon-folder-new{background-position:-92px -1026px}.icon-large.icon-check{background-position:-92px -1060px}.icon-large.icon-edit{background-position:-92px -1088px}.icon-large.icon-new-window{background-position:-92px -1119px}.icon-large.icon-more-windows{background-position:-92px -1151px}.icon-large.icon-show-big-thumbnails{background-position:-92px -1184px}.icon-large.icon-th-large{background-position:-92px -1184px}.icon-large.icon-show-thumbnails{background-position:-92px -1216px}.icon-large.icon-th{background-position:-92px -1216px}.icon-large.icon-show-thumbnails-with-lines{background-position:-92px -1248px}.icon-large.icon-th-list{background-position:-92px -1248px}.icon-large.icon-show-lines{background-position:-92px -1273px}.icon-large.icon-playlist{background-position:-92px -1298px}.icon-large.icon-picture{background-position:-92px -1332px}.icon-large.icon-imac{background-position:-92px -1362px}.icon-large.icon-macbook{background-position:-92px -1394px}.icon-large.icon-ipad{background-position:-92px -1419px}.icon-large.icon-iphone{background-position:-92px -1456px}.icon-large.icon-iphone-transfer{background-position:-92px -1490px}.icon-large.icon-iphone-exchange{background-position:-92px -1524px}.icon-large.icon-ipod{background-position:-92px -1558px}.icon-large.icon-ipod-shuffle{background-position:-92px -1590px}.icon-large.icon-ear-plugs{background-position:-92px -1613px}.icon-large.icon-albums{background-position:-92px -1647px}.icon-large.icon-step-backward{background-position:-92px -1675px}.icon-large.icon-fast-backward{background-position:-92px -1703px}.icon-large.icon-rewind,.icon-large.icon-backwards{background-position:-92px -1731px}.icon-large.icon-play{background-position:-92px -1759px}.icon-large.icon-pause{background-position:-92px -1787px}.icon-large.icon-stop{background-position:-92px -1813px}.icon-large.icon-forward{background-position:-92px -1837px}.icon-large.icon-fast-forward{background-position:-92px -1865px}.icon-large.icon-step-forward{background-position:-92px -1893px}.icon-large.icon-eject{background-position:-92px -1921px}.icon-large.icon-facetime-video{background-position:-92px -1948px}.icon-large.icon-download-alt{background-position:-92px -1974px}.icon-large.icon-mute,.icon-large.icon-volume-off{background-position:-138px 4px}.icon-large.icon-volume-down{background-position:-134px -22px}.icon-large.icon-volume-up{background-position:-138px -52px}.icon-large.icon-screenshot{background-position:-138px -88px}.icon-large.icon-move{background-position:-138px -125px}.icon-large.icon-more{background-position:-138px -159px}.icon-large.icon-brightness-reduce{background-position:-138px -176px}.icon-large.icon-brightness-increase{background-position:-138px -206px}.icon-large.icon-circle-plus,.icon-large.icon-plus-sign{background-position:-138px -240px}.icon-large.icon-circle-minus,.icon-large.icon-minus-sign{background-position:-138px -276px}.icon-large.icon-circle-remove,.icon-large.icon-remove-sign{background-position:-138px -312px}.icon-large.icon-circle-ok,.icon-large.icon-ok-sign{background-position:-138px -348px}.icon-large.icon-circle-question-mark,.icon-large.icon-question-sign{background-position:-138px -384px}.icon-large.icon-circle-info,.icon-large.icon-info-sign{background-position:-138px -420px}.icon-large.icon-circle-exclamation-mark,.icon-large.icon-exclamation-sign{background-position:-138px -456px}.icon-large.icon-remove{background-position:-138px -492px}.icon-large.icon-ok{background-position:-138px -528px}.icon-large.icon-ban{background-position:-138px -564px}.icon-large.icon-download{background-position:-138px -600px}.icon-large.icon-upload{background-position:-138px -636px}.icon-large.icon-shopping-cart{background-position:-138px -672px}.icon-large.icon-lock{background-position:-138px -705px}.icon-large.icon-unlock{background-position:-138px -741px}.icon-large.icon-electricity{background-position:-138px -777px}.icon-large.icon-cart-out{background-position:-138px -811px}.icon-large.icon-cart-in{background-position:-138px -846px}.icon-large.icon-left-arrow{background-position:-138px -880px}.icon-large.icon-right-arrow{background-position:-138px -908px}.icon-large.icon-down-arrow{background-position:-138px -936px}.icon-large.icon-up-arrow{background-position:-138px -966px}.icon-large.icon-resize-small{background-position:-138px -996px}.icon-large.icon-resize-full{background-position:-138px -1030px}.icon-large.icon-circle-arrow-left{background-position:-138px -1064px}.icon-large.icon-circle-arrow-right{background-position:-138px -1100px}.icon-large.icon-circle-arrow-top,.icon-large.icon-circle-arrow-up{background-position:-138px -1136px}.icon-large.icon-circle-arrow-down{background-position:-138px -1172px}.icon-large.icon-play-button{background-position:-138px -1208px}.icon-large.icon-play-circle{background-position:-138px -1208px}.icon-large.icon-unshare{background-position:-138px -1244px}.icon-large.icon-share{background-position:-138px -1272px}.icon-large.icon-thin-right-arrow,.icon-large.icon-chevron-right{background-position:-138px -1300px}.icon-large.icon-thin-arrow-left,.icon-large.icon-chevron-left{background-position:-138px -1332px}.icon-large.icon-bluetooth{background-position:-138px -1364px}.icon-large.icon-euro{background-position:-138px -1398px}.icon-large.icon-usd{background-position:-138px -1431px}.icon-large.icon-bp{background-position:-138px -1467px}.icon-large.icon-moon{background-position:-138px -1501px}.icon-large.icon-sun{background-position:-138px -1536px}.icon-large.icon-cloud{background-position:-138px -1570px}.icon-large.icon-direction{background-position:-138px -1597px}.icon-large.icon-brush{background-position:-138px -1633px}.icon-large.icon-pen{background-position:-138px -1666px}.icon-large.icon-zoom-in{background-position:-138px -1700px}.icon-large.icon-zoom-out{background-position:-138px -1735px}.icon-large.icon-pin{background-position:-138px -1770px}.icon-large.icon-riflescope{background-position:-138px -1805px}.icon-large.icon-rotation-lock{background-position:-138px -1840px}.icon-large.icon-flash{background-position:-138px -1874px}.icon-large.icon-google-maps,.icon-large.icon-map-marker{background-position:-138px -1909px}.icon-large.icon-anchor{background-position:-138px -1943px}.icon-large.icon-conversation{background-position:-138px -1978px}.icon-large.icon-chat{background-position:-184px 0}.icon-large.icon-male{background-position:-184px -29px}.icon-large.icon-female{background-position:-184px -61px}.icon-large.icon-asterisk{background-position:-184px -98px}.icon-large.icon-divide{background-position:-184px -128px}.icon-large.icon-snorkel-diving{background-position:-184px -154px}.icon-large.icon-scuba-diving{background-position:-184px -189px}.icon-large.icon-oxygen-bottle{background-position:-184px -223px}.icon-large.icon-fins{background-position:-184px -260px}.icon-large.icon-fishes{background-position:-184px -297px}.icon-large.icon-boat{background-position:-184px -337px}.icon-large.icon-delete-point{background-position:-184px -371px}.icon-large.icon-qrcode{background-position:-184px -398px}.icon-large.icon-barcode{background-position:-184px -432px}.icon-large.icon-pool{background-position:-184px -466px}.icon-large.icon-buoy{background-position:-184px -500px}.icon-large.icon-spade{background-position:-184px -534px}.icon-large.icon-bank{background-position:-184px -568px}.icon-large.icon-vcard{background-position:-184px -602px}.icon-large.icon-electrical-plug{background-position:-184px -636px}.icon-large.icon-flag{background-position:-184px -671px}.icon-large.icon-credit-card{background-position:-184px -707px}.icon-large.icon-keyboard-wireless{background-position:-184px -736px}.icon-large.icon-keyboard-wired{background-position:-184px -765px}.icon-large.icon-shield{background-position:-184px -800px}.icon-large.icon-ring{background-position:-184px -834px}.icon-large.icon-cake{background-position:-184px -868px}.icon-large.icon-drink{background-position:-184px -902px}.icon-large.icon-beer{background-position:-184px -936px}.icon-large.icon-fast-food{background-position:-184px -970px}.icon-large.icon-cutlery{background-position:-184px -1004px}.icon-large.icon-pizza{background-position:-184px -1038px}.icon-large.icon-birthday-cake{background-position:-184px -1077px}.icon-large.icon-tablet{background-position:-184px -1116px}.icon-large.icon-settings{background-position:-184px -1151px}.icon-large.icon-bullets{background-position:-184px -1185px}.icon-large.icon-cardio{background-position:-184px -1218px}.icon-large.icon-pants{background-position:-184px -1254px}.icon-large.icon-sweater{background-position:-184px -1288px}.icon-large.icon-fabric{background-position:-184px -1322px}.icon-large.icon-leather{background-position:-184px -1354px}.icon-large.icon-scissors{background-position:-184px -1388px}.icon-large.icon-podium{background-position:-184px -1425px}.icon-large.icon-skull{background-position:-184px -1456px}.icon-large.icon-celebration{background-position:-184px -1490px}.icon-large.icon-tea-kettle{background-position:-184px -1525px}.icon-large.icon-french-press{background-position:-184px -1558px}.icon-large.icon-coffe-cup{background-position:-184px -1593px}.icon-large.icon-pot{background-position:-184px -1622px}.icon-large.icon-grater{background-position:-184px -1654px}.icon-large.icon-kettle{background-position:-184px -1688px}.icon-large.icon-hospital{background-position:-184px -1722px}.icon-large.icon-hospital-h{background-position:-184px -1756px}.icon-large.icon-microphone{background-position:-184px -1790px}.icon-large.icon-webcam{background-position:-184px -1824px}.icon-large.icon-temple-christianity-church{background-position:-184px -1858px}.icon-large.icon-temple-islam{background-position:-184px -1893px}.icon-large.icon-temple-hindu{background-position:-184px -1927px}.icon-large.icon-temple-buddhist{background-position:-184px -1961px}.icon-large.icon-electrical-socket-eu{background-position:-230px 0}.icon-large.icon-electrical-socket-us{background-position:-230px -33px}.icon-large.icon-bomb{background-position:-230px -66px}.icon-large.icon-comments,.icon-large.icon-comment{background-position:-230px -102px}.icon-large.icon-flower{background-position:-230px -135px}.icon-large.icon-baseball{background-position:-230px -170px}.icon-large.icon-rugby{background-position:-230px -206px}.icon-large.icon-ax{background-position:-230px -240px}.icon-large.icon-table-tennis{background-position:-230px -275px}.icon-large.icon-bowling{background-position:-230px -309px}.icon-large.icon-tree-conifer{background-position:-230px -343px}.icon-large.icon-tree-deciduous{background-position:-230px -377px}.icon-large.icon-sort{background-position:-230px -412px}.icon-large.icon-filter{background-position:-230px -447px}.icon-large.icon-gamepad{background-position:-230px -481px}.icon-large.icon-playing-dices{background-position:-230px -510px}.icon-large.icon-calculator{background-position:-230px -543px}.icon-large.icon-tie{background-position:-230px -577px}.icon-large.icon-wallet{background-position:-230px -613px}.icon-large.icon-share{background-position:-230px -643px}.icon-large.icon-sampler{background-position:-230px -675px}.icon-large.icon-piano{background-position:-230px -707px}.icon-large.icon-web-browser{background-position:-230px -741px}.icon-large.icon-blog{background-position:-230px -773px}.icon-large.icon-dashboard{background-position:-230px -806px}.icon-large.icon-certificate{background-position:-230px -840px}.icon-large.icon-bell{background-position:-230px -875px}.icon-large.icon-candle{background-position:-230px -909px}.icon-large.icon-pin-classic{background-position:-230px -944px}.icon-large.icon-iphone-shake{background-position:-230px -978px}.icon-large.icon-pin-flag{background-position:-230px -1012px}.icon-large.icon-turtle{background-position:-230px -1044px}.icon-large.icon-rabbit{background-position:-230px -1070px}.icon-large.icon-globe{background-position:-230px -1102px}.icon-large.icon-briefcase{background-position:-230px -1136px}.icon-large.icon-hdd{background-position:-230px -1167px}.icon-large.icon-thumbs-up{background-position:-230px -1198px}.icon-large.icon-thumbs-down{background-position:-230px -1229px}.icon-large.icon-hand-right{background-position:-230px -1260px}.icon-large.icon-hand-left{background-position:-230px -1289px}.icon-large.icon-hand-up{background-position:-230px -1318px}.icon-large.icon-hand-down{background-position:-230px -1354px}.icon-large.icon-fullscreen{background-position:-230px -1391px}.icon-large.icon-shopping-bag{background-position:-230px -1425px}.icon-large.icon-book-open{background-position:-230px -1461px}.icon-large.icon-nameplate{background-position:-230px -1494px}.icon-large.icon-nameplate-alt{background-position:-230px -1525px}.icon-large.icon-vases{background-position:-230px -1557px}.icon-large.icon-announcement,.icon-large.icon-bullhorn{background-position:-230px -1591px}.icon-large.icon-dumbbell{background-position:-230px -1621px}.icon-large.icon-suitcase{background-position:-230px -1647px}.icon-large.icon-file-import{background-position:-230px -1679px}.icon-large.icon-file-export{background-position:-230px -1713px}.icon-large.icon-pinterest{background-position:-230px -1747px}.icon-large.icon-dropbox{background-position:-230px -1781px}.icon-large.icon-jolicloud{background-position:-230px -1815px}.icon-large.icon-yahoo{background-position:-230px -1849px}.icon-large.icon-blogger{background-position:-230px -1883px}.icon-large.icon-picasa{background-position:-230px -1917px}.icon-large.icon-amazon{background-position:-230px -1951px}.icon-large.icon-tumblr{background-position:-230px -1985px}.icon-large.icon-wordpress{background-position:-276px 0}.icon-large.icon-instapaper{background-position:-276px -34px}.icon-large.icon-evernote{background-position:-276px -68px}.icon-large.icon-xing{background-position:-276px -102px}.icon-large.icon-zootool{background-position:-276px -136px}.icon-large.icon-dribbble{background-position:-276px -170px}.icon-large.icon-deviantart{background-position:-276px -204px}.icon-large.icon-read-it-later{background-position:-276px -238px}.icon-large.icon-linked-in{background-position:-276px -272px}.icon-large.icon-forrst{background-position:-276px -306px}.icon-large.icon-pinboard{background-position:-276px -340px}.icon-large.icon-behance{background-position:-276px -374px}.icon-large.icon-github{background-position:-276px -408px}.icon-large.icon-youtube{background-position:-276px -442px}.icon-large.icon-skitch{background-position:-276px -476px}.icon-large.icon-quora{background-position:-276px -510px}.icon-large.icon-google-plus{background-position:-276px -544px}.icon-large.icon-spootify{background-position:-276px -578px}.icon-large.icon-stumbleupon{background-position:-276px -612px}.icon-large.icon-readability{background-position:-276px -646px}.icon-large.icon-facebook{background-position:-276px -680px}.icon-large.icon-twitter-t{background-position:-276px -714px}.icon-large.icon-twitter{background-position:-276px -748px}.icon-large.icon-buzz{background-position:-276px -782px}.icon-large.icon-vimeo{background-position:-276px -816px}.icon-large.icon-flickr{background-position:-276px -850px}.icon-large.icon-last-fm{background-position:-276px -884px}.icon-large.icon-rss{background-position:-276px -918px}.icon-large.icon-skype{background-position:-276px -952px}
--------------------------------------------------------------------------------
/app/static/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.0.2 by @fat and @mdo
3 | * Copyright 2013 Twitter, Inc.
4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0
5 | *
6 | * Designed and built with all the love in the world by @mdo and @fat.
7 | */
8 |
9 | if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('').insertAfter(a(this)).on("click",b),f.trigger(d=a.Event("show.bs.dropdown")),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown"),e.focus()}return!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=a("[role=menu] li:not(.divider):visible a",f);if(h.length){var i=h.index(h.filter(":focus"));38==b.keyCode&&i>0&&i--,40==b.keyCode&&i').appendTo(document.body),this.$element.on("click.dismiss.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},b.DEFAULTS,e.data(),"object"==typeof c&&c);f||e.data("bs.modal",f=new b(this,g)),"string"==typeof c?f[c](d):g.show&&f.show(d)})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f,this).one("hide",function(){c.is(":visible")&&c.focus()})}),a(document).on("show.bs.modal",".modal",function(){a(document.body).addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){a(document.body).removeClass("modal-open")})}(jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focus",i="hover"==g?"mouseleave":"blur";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},b.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show),void 0):c.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide),void 0):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this.tip();this.setContent(),this.options.animation&&c.addClass("fade");var d="function"==typeof this.options.placement?this.options.placement.call(this,c[0],this.$element[0]):this.options.placement,e=/\s?auto?\s?/i,f=e.test(d);f&&(d=d.replace(e,"")||"top"),c.detach().css({top:0,left:0,display:"block"}).addClass(d),this.options.container?c.appendTo(this.options.container):c.insertAfter(this.$element);var g=this.getPosition(),h=c[0].offsetWidth,i=c[0].offsetHeight;if(f){var j=this.$element.parent(),k=d,l=document.documentElement.scrollTop||document.body.scrollTop,m="body"==this.options.container?window.innerWidth:j.outerWidth(),n="body"==this.options.container?window.innerHeight:j.outerHeight(),o="body"==this.options.container?0:j.offset().left;d="bottom"==d&&g.top+g.height+i-l>n?"top":"top"==d&&g.top-l-i<0?"bottom":"right"==d&&g.right+h>m?"left":"left"==d&&g.left-h'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery);
--------------------------------------------------------------------------------
/app/static/fonts/glyphicons-halflings-regular.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------