├── static ├── 502.html ├── 503.html ├── 504.html ├── 500.html ├── 404.html ├── favicon.ico ├── img │ ├── forkme.png │ ├── mailgun.png │ ├── mailgun_small.png │ ├── powered-by-dotcloud.png │ ├── glyphicons-halflings.png │ └── glyphicons-halflings-white.png ├── js │ └── jquery.sparkline.min.js └── css │ └── bootstrap.min.css ├── wsgi.py ├── nginx.conf ├── dotcloud.yml ├── .gitignore ├── requirements.txt ├── crontab ├── iploc.py ├── postinstall ├── app.py ├── test_run.py ├── daily.py ├── pypi_mirrors.py ├── README.rst ├── config.py ├── notification.py ├── utils.py ├── templates └── index.html └── mirrorlib.py /static/502.html: -------------------------------------------------------------------------------- 1 | 502 error -------------------------------------------------------------------------------- /static/503.html: -------------------------------------------------------------------------------- 1 | 503 error -------------------------------------------------------------------------------- /static/504.html: -------------------------------------------------------------------------------- 1 | 504 error -------------------------------------------------------------------------------- /static/500.html: -------------------------------------------------------------------------------- 1 | 500 - Server error -------------------------------------------------------------------------------- /static/404.html: -------------------------------------------------------------------------------- 1 | 404 - Page not found -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | 3 | application = app 4 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | location /static/ { root /home/dotcloud/current; } -------------------------------------------------------------------------------- /dotcloud.yml: -------------------------------------------------------------------------------- 1 | mirror: 2 | type: python 3 | cache: 4 | type: redis -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.pyc 3 | environment.json 4 | environment.txt 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | redis 2 | hiredis 3 | requests 4 | lxml 5 | flask 6 | tweepy -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kencochrane/pypi-mirrors/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /static/img/forkme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kencochrane/pypi-mirrors/HEAD/static/img/forkme.png -------------------------------------------------------------------------------- /static/img/mailgun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kencochrane/pypi-mirrors/HEAD/static/img/mailgun.png -------------------------------------------------------------------------------- /static/img/mailgun_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kencochrane/pypi-mirrors/HEAD/static/img/mailgun_small.png -------------------------------------------------------------------------------- /static/img/powered-by-dotcloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kencochrane/pypi-mirrors/HEAD/static/img/powered-by-dotcloud.png -------------------------------------------------------------------------------- /static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kencochrane/pypi-mirrors/HEAD/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kencochrane/pypi-mirrors/HEAD/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /crontab: -------------------------------------------------------------------------------- 1 | MAILTO="" 2 | */6 * * * * /home/dotcloud/env/bin/python /home/dotcloud/current/pypi_mirrors.py >> /var/log/supervisor/cron.log 3 | 24 12 * * * /home/dotcloud/env/bin/python /home/dotcloud/current/daily.py >> /var/log/supervisor/cron-daily.log 4 | 5 * * * * killall -q --older-than 1h '/home/dotcloud/env/bin/python' 5 | -------------------------------------------------------------------------------- /iploc.py: -------------------------------------------------------------------------------- 1 | import json, urllib, urllib2 2 | 3 | 4 | def get_city(apikey, ip): 5 | """ get city location for an ip """ 6 | base_url = "http://api.ipinfodb.com/v3/ip-city/" 7 | variables = {"format":"json", 8 | "key":apikey, 9 | "ip":ip,} 10 | 11 | urldata = urllib.urlencode(variables) 12 | url = "{0}?{1}".format(base_url, urldata) 13 | urlobj = urllib2.urlopen(url) 14 | data = urlobj.read() 15 | urlobj.close() 16 | return json.loads(data) 17 | -------------------------------------------------------------------------------- /postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "HOSTNAME=$(cat /etc/hostname)" 3 | case "$(cat /etc/hostname)" in 4 | *-mirror-0) 5 | echo "We are on mirror-0; executing specific management commands..." 6 | 7 | echo "Install crontab" 8 | crontab ~/current/crontab 9 | 10 | echo "Run script to get updated information" 11 | /home/dotcloud/env/bin/python /home/dotcloud/current/pypi_mirrors.py >> /var/log/supervisor/cron 12 | ;; 13 | *) 14 | echo "We are not on mirror-0; skipping specific management commands..." 15 | exit 0 16 | ;; 17 | esac 18 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, Response 2 | from utils import get_page_data, get_json_data 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def index(): 8 | context = get_page_data() 9 | return render_template('index.html', **context) 10 | 11 | @app.route('/data.json') 12 | def json_data(): 13 | return Response(get_json_data(),mimetype='application/json') 14 | 15 | if __name__ == '__main__': 16 | params = {"debug": True, 17 | "host":"0.0.0.0",} 18 | 19 | app.run(**params) -------------------------------------------------------------------------------- /test_run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from jinja2 import Environment, FileSystemLoader 4 | from mirrorlib import mirror_statuses 5 | 6 | from utils import (find_number_of_packages) 7 | 8 | from config import MIRRORS 9 | 10 | def process_results(results): 11 | """ process the results and gather data """ 12 | 13 | new_results = [] 14 | for d in results: 15 | mirror = d.get('mirror') 16 | status = d.get('status') 17 | d['location'] = "n/a" 18 | if status != 'Unavailable': 19 | resp_list = ["1","2","3","4","5","6","7","8","9","10"] # faked out for test 20 | age_list = ["1","2","3","4","5","6","7","8","9","10"] # faked out for test 21 | d['num_packages'] = find_number_of_packages(mirror) 22 | d['resp_list'] = ",".join(resp_list) 23 | d['age_list'] = ",".join(age_list) 24 | new_results.append(d) 25 | return new_results 26 | 27 | 28 | def url_for(something): 29 | return something 30 | 31 | def run(): 32 | """ run everything """ 33 | results = mirror_statuses(mirrors=MIRRORS) 34 | if results: 35 | time_now = results[0].get('time_now', None) 36 | data = process_results(results) 37 | 38 | env = Environment(loader=FileSystemLoader('templates')) 39 | # add the dummy url_for so it doesn't throw error. 40 | env.globals.update(url_for=url_for) 41 | template = env.get_template('index.html') 42 | context = {'data': data, 'date_now': time_now} 43 | print template.render(**context) 44 | 45 | if __name__ == '__main__': 46 | run() 47 | -------------------------------------------------------------------------------- /daily.py: -------------------------------------------------------------------------------- 1 | from mirrorlib import find_out_of_date_mirrors 2 | from config import MIRRORS 3 | from notification import (update_twitter_status, send_warning_email, 4 | send_status_email) 5 | 6 | 7 | def __tweet_outofdate(mirror, last_update): 8 | """ Send a tweet saying we have a mirror out of date """ 9 | status = "{0} is out of date, it was last updated {1}".format(mirror, 10 | last_update) 11 | update_twitter_status(status) 12 | 13 | 14 | def daily_out_of_date_mirror_check(): 15 | """ run everything """ 16 | results = find_out_of_date_mirrors(mirrors=MIRRORS) 17 | 18 | if results: 19 | email_message = "" 20 | for res in results: 21 | email_message += "{0} was last updated {1}\n".format( 22 | res.get('mirror'), 23 | res.get('time_diff_human')) 24 | 25 | print("{0} is out of date. {1}".format( 26 | res.get('mirror'), res.get('time_diff_human'))) 27 | 28 | # one tweet for each out of date mirror 29 | __tweet_outofdate(res.get('mirror'), res.get('time_diff_human')) 30 | 31 | # one email for all out of date mirrors 32 | send_warning_email(email_message) 33 | else: 34 | print("All is good, sending Good message!") 35 | send_status_email("[All Mirrors are up to date]") 36 | 37 | 38 | def run(): 39 | """ run all of the daily cron jobs.""" 40 | daily_out_of_date_mirror_check() 41 | 42 | 43 | if __name__ == '__main__': 44 | run() 45 | -------------------------------------------------------------------------------- /pypi_mirrors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | from mirrorlib import mirror_statuses 4 | 5 | from utils import (cache_key, location_name, get_total_seconds, 6 | get_connection, store_page_data, find_number_of_packages, 7 | get_location_for_mirror, store_json_data) 8 | 9 | from config import MIRRORS 10 | 11 | def process_results(results): 12 | """ process the results and gather data """ 13 | 14 | conn = get_connection() 15 | new_results = [] 16 | for d in results: 17 | mirror = d.get('mirror') 18 | status = d.get('status') 19 | location = get_location_for_mirror(mirror) 20 | d['location'] = location_name(location) 21 | if status != 'Unavailable': 22 | resp_time = d.get('response_time') 23 | age = get_total_seconds(d.get('time_diff')) 24 | conn.rpush(cache_key('RESPTIME', mirror), resp_time ) 25 | conn.rpush(cache_key('AGE', mirror), age) 26 | resp_list = conn.lrange(cache_key('RESPTIME', mirror), -60, -1) 27 | age_list = conn.lrange(cache_key('AGE', mirror), -60, -1) 28 | d['num_packages'] = find_number_of_packages(mirror) 29 | d['resp_list'] = ",".join(resp_list) 30 | d['age_list'] = ",".join(age_list) 31 | new_results.append(d) 32 | return new_results 33 | 34 | def json_results(data): 35 | results = {} 36 | for mirror in data: 37 | results[mirror.get('mirror')] = { 38 | 'status': mirror.get('status', 'n/a'), 39 | 'location': mirror.get('location', 'n/a'), 40 | 'num_packages': mirror.get('num_packages', 'n/a'), 41 | 'last_updated': mirror.get('time_diff_human', 'n/a'), 42 | } 43 | return json.dumps(results) 44 | 45 | def run(): 46 | """ run everything """ 47 | results = mirror_statuses(mirrors=MIRRORS) 48 | if results: 49 | time_now = results[0].get('time_now', None) 50 | data = process_results(results) 51 | json_data = json_results(data) 52 | 53 | store_json_data(json_data) 54 | store_page_data(data, time_now) 55 | 56 | if __name__ == '__main__': 57 | run() 58 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pypi-mirrors 2 | ============ 3 | 4 | Very simple tool that pings the PyPI mirrors and tells us when they were updated last. 5 | 6 | I threw this together very quickly as a proof of concept feel free to fork, and send pull requests. 7 | 8 | Config 9 | ------ 10 | 11 | Redis 12 | ~~~~~ 13 | It requires redis in order to cache some of the data. For local development it is assuming it to be running 14 | at localhost:6379 db:1 and no password. see ``config.py`` for more info. 15 | 16 | GeoIP 17 | ~~~~~ 18 | In order to get the IP address geolocation lookup, you need to sign up for an account from http://ipinfodb.com/register.php . If you don't have the env variable set, you will not have access to the geo location information. set IPLOC_API_KEY with the API key they give you. 19 | 20 | Email & Twitter 21 | ~~~~~~~~~~~~~~~ 22 | 23 | To get the twitter and email notifications to work correctly you need to create an environment.json file in ``/tmp`` with the variables and values shown below. replace with the real values. 24 | 25 | ``/tmp/environment.json``:: 26 | 27 | { 28 | "IPLOC_API_KEY": "", 29 | "TWITTER_CONSUMER_KEY" : "", 30 | "TWITTER_CONSUMER_SECRET" : "", 31 | "TWITTER_ACCESS_KEY" : "", 32 | "TWITTER_ACCESS_SECRET" : "", 33 | "EMAIL_HOST" : "", 34 | "EMAIL_PORT" : "", 35 | "EMAIL_USER" : "", 36 | "EMAIL_PASSWORD" : "", 37 | "EMAIL_FROM" : "", 38 | "EMAIL_TO" : "", 39 | "EMAIL_BCC" : "", 40 | "EMAIL_TO_ADMIN": "" 41 | } 42 | 43 | 44 | For installing the API Key on dotCloud you need to run the following command. replace with the real values. 45 | 46 | env variables:: 47 | 48 | dotcloud var set pypimirrors \ 49 | 'IPLOC_API_KEY=' \ 50 | 'TWITTER_CONSUMER_KEY=' \ 51 | 'TWITTER_CONSUMER_SECRET=' \ 52 | 'TWITTER_ACCESS_KEY=' \ 53 | 'TWITTER_ACCESS_SECRET=' \ 54 | 'EMAIL_HOST=' \ 55 | 'EMAIL_PORT=' \ 56 | 'EMAIL_USER=' \ 57 | 'EMAIL_PASSWORD=' \ 58 | 'EMAIL_FROM=' \ 59 | 'EMAIL_TO=' \ 60 | 'EMAIL_BCC=' \ 61 | 'EMAIL_TO_ADMIN=' 62 | 63 | 64 | How it works 65 | ------------ 66 | The ``pypi_mirrors.py`` script runs via a cron job and puts data into redis. There is one webpage that pull the data from redis and 67 | displays it. There is a daily cron job that runs and sends out notifications if the mirrors are out of date. 68 | 69 | Demo 70 | ---- 71 | http://www.pypi-mirrors.org 72 | 73 | How to help 74 | ----------- 75 | Pick one of the things on the TODO list and implement it and send a pull request. 76 | 77 | Running locally 78 | --------------- 79 | Make sure redis is running 80 | 81 | 1. Collecting Data:: 82 | 83 | $ python pypi_mirrors.py 84 | 85 | 2. Running web server:: 86 | 87 | $ python app.py 88 | # connect to http://localhost:5000 in browser 89 | 90 | 91 | TODO: 92 | ----- 93 | - Create a setup.py and add to PyPI 94 | - Add better documentation -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | # mirrors listed here 5 | MIRRORS = [ 6 | ('http', 'pypi.douban.com'), 7 | ('http', 'pypi.hustunique.com'), 8 | ('http', 'pypi.gocept.com'), 9 | ('http', 'pypi.tuna.tsinghua.edu.cn'), 10 | ('http', 'mirror.picosecond.org/pypi'), 11 | ('http', 'mirrors.aliyun.com/pypi'), 12 | ('http', 'pypi.pubyun.com'), 13 | ('http', 'mirrors-uk.go-parts.com/python'), 14 | ('http', 'mirrors-ru.go-parts.com/python'), 15 | ('http', 'mirrors-au.go-parts.com/python'), 16 | ] 17 | 18 | EMAIL_OVERRIDE = None # None or "blah@example.com" 19 | 20 | def load_config(): 21 | # if at dotcloud load the dotcloud settings 22 | dotcloud_config = '/home/dotcloud/environment.json' 23 | if os.path.exists(dotcloud_config): 24 | env = json.load(open(dotcloud_config)) 25 | return {'host': env['DOTCLOUD_CACHE_REDIS_HOST'], 26 | 'port': env['DOTCLOUD_CACHE_REDIS_PORT'], 27 | 'password': env['DOTCLOUD_CACHE_REDIS_PASSWORD'], 28 | 'db': 1, 29 | 'ip_api_key': env.get('IPLOC_API_KEY', None), 30 | 'twitter_consumer_key' : env.get('TWITTER_CONSUMER_KEY', None), 31 | 'twitter_consumer_secret' : env.get('TWITTER_CONSUMER_SECRET', None), 32 | 'twitter_access_key' : env.get('TWITTER_ACCESS_KEY', None), 33 | 'twitter_access_secret' : env.get('TWITTER_ACCESS_SECRET', None), 34 | 'email_host' : env.get('EMAIL_HOST', None), 35 | 'email_port' : env.get('EMAIL_PORT', None), 36 | 'email_user' : env.get('EMAIL_USER', None), 37 | 'email_password' : env.get('EMAIL_PASSWORD', None), 38 | 'email_from' : env.get('EMAIL_FROM', None), 39 | 'email_to' : env.get('EMAIL_TO', None), 40 | 'email_bcc' : env.get('EMAIL_BCC', None), 41 | 'email_to_admin': env.get('EMAIL_TO_ADMIN', None), 42 | } 43 | else: 44 | # local config 45 | dotcloud_config = '/tmp/environment.json' 46 | if os.path.exists(dotcloud_config): 47 | env = json.load(open(dotcloud_config)) 48 | return { 'host': 'localhost', 49 | 'port': 6379, 50 | 'password': None, 51 | 'db': 0, 52 | 'ip_api_key': env.get('IPLOC_API_KEY', None), 53 | 'twitter_consumer_key' : env.get('TWITTER_CONSUMER_KEY', None), 54 | 'twitter_consumer_secret' : env.get('TWITTER_CONSUMER_SECRET', None), 55 | 'twitter_access_key' : env.get('TWITTER_ACCESS_KEY', None), 56 | 'twitter_access_secret' : env.get('TWITTER_ACCESS_SECRET', None), 57 | 'email_host' : env.get('EMAIL_HOST', None), 58 | 'email_port' : env.get('EMAIL_PORT', None), 59 | 'email_user' : env.get('EMAIL_USER', None), 60 | 'email_password' : env.get('EMAIL_PASSWORD', None), 61 | 'email_from' : env.get('EMAIL_FROM', None), 62 | 'email_to' : env.get('EMAIL_TO', None), 63 | 'email_bcc' : env.get('EMAIL_BCC', None), 64 | 'email_to_admin': env.get('EMAIL_TO_ADMIN', None), 65 | } 66 | else: 67 | print("can't find a local environment file here '/tmp/environment.json' ") 68 | return None #TODO throw exception? 69 | -------------------------------------------------------------------------------- /notification.py: -------------------------------------------------------------------------------- 1 | import tweepy 2 | import smtplib 3 | 4 | from config import load_config, EMAIL_OVERRIDE 5 | 6 | CONFIG = load_config() 7 | 8 | def prepare_twitter_message(status): 9 | """ shrink to the right size and add link to site. """ 10 | link = "http://www.pypi-mirrors.org" 11 | link_len = len(link) + 4 12 | message_len = 140 - link_len 13 | status_new = status[:message_len] 14 | if len(status) > message_len: 15 | status_new += "..." 16 | status_new += " {0}".format(link) 17 | return status_new 18 | 19 | 20 | def update_twitter_status(status): 21 | """ update the twitter account's status """ 22 | 23 | consumer_key=CONFIG.get('twitter_consumer_key') 24 | consumer_secret=CONFIG.get('twitter_consumer_secret') 25 | 26 | access_token=CONFIG.get('twitter_access_key') 27 | access_token_secret=CONFIG.get('twitter_access_secret') 28 | 29 | message = prepare_twitter_message(status) 30 | 31 | auth = tweepy.OAuthHandler(consumer_key, consumer_secret) 32 | auth.set_access_token(access_token, access_token_secret) 33 | api = tweepy.API(auth) 34 | api.update_status(message) 35 | 36 | 37 | def send_warning_email(message): 38 | """ send a message saying a mirror(s) is out of date. """ 39 | email_to = CONFIG.get('email_to') 40 | email_from = CONFIG.get('email_from') 41 | email_template = '''Subject: [pypi-mirrors] Mirror is out of Date Notice 42 | 43 | This is an automated email from http://www.pypi-mirrors.org to let you 44 | know that the following mirrors are out of date. 45 | 46 | {message} 47 | 48 | -- 49 | This automated message is sent to you by http://www.pypi-mirrors.org If you no 50 | longer want to receive these emails, please contact Ken Cochrane (@KenCochrane) on twitter 51 | or reply to this email. 52 | ''' 53 | email_body = email_template.format(message=message) 54 | 55 | send_email(email_body, email_to, email_from) 56 | 57 | 58 | def send_status_email(message): 59 | """ send a daily status message """ 60 | email_to = CONFIG.get('email_to_admin') 61 | email_from = CONFIG.get('email_from') 62 | email_template = '''Subject: [pypi-mirrors] Mirrors are all up to date 63 | 64 | This is an automated email from http://www.pypi-mirrors.org to let you 65 | know that the following mirrors are all up to date. 66 | 67 | {message} 68 | -- 69 | This automated message is sent to you by http://www.pypi-mirrors.org If you no 70 | longer want to receive these emails, please contact Ken Cochrane (@KenCochrane) on twitter 71 | or reply to this email. 72 | ''' 73 | 74 | email_body = email_template.format(message=message) 75 | 76 | send_email(email_body, email_to, email_from) 77 | 78 | 79 | def send_email(email_body, email_to, email_from): 80 | """ Send an email using the configuration provided """ 81 | email_host = CONFIG.get('email_host') 82 | email_port = CONFIG.get('email_port') 83 | email_user = CONFIG.get('email_user') 84 | email_password = CONFIG.get('email_password') 85 | email_bcc = CONFIG.get('email_bcc') 86 | 87 | if EMAIL_OVERRIDE: 88 | print 'Over-riding email with {0}.'.format(EMAIL_OVERRIDE) 89 | email = EMAIL_OVERRIDE 90 | else: 91 | email = email_to 92 | 93 | print("email to {0} , bcc: {1}; from {2}".format(email, email_bcc, email_from)) 94 | smtp = smtplib.SMTP(email_host, email_port) 95 | smtp.starttls() 96 | smtp.login(email_user, email_password) 97 | smtp.sendmail(email_from, [email, email_bcc], email_body) 98 | smtp.quit() 99 | 100 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import redis 2 | import socket 3 | from urlparse import urlparse 4 | import requests 5 | import lxml.html 6 | 7 | try: 8 | import cPickle as pickle 9 | except ImportError: 10 | import pickle 11 | 12 | from config import load_config 13 | from iploc import get_city 14 | 15 | CONFIG = load_config() 16 | 17 | def get_connection(): 18 | """ Get the connection to Redis""" 19 | return redis.StrictRedis(host=CONFIG.get('host'), 20 | port=int(CONFIG.get('port')), 21 | db=CONFIG.get('db'), 22 | password=CONFIG.get('password')) 23 | 24 | def find_number_of_packages(mirror): 25 | """ Find the number of packages in a mirror """ 26 | html = lxml.html.fromstring(requests.get("http://{0}/simple/".format(mirror)).content) 27 | return len(html.xpath("//a")) 28 | 29 | def ping_ip2loc(ip): 30 | """ get the location info for the ip 31 | you need to register for an API key here. http://ipinfodb.com/register.php 32 | 33 | and set it as an environment variable called 34 | PYPI_MIRRORS_API_KEY 35 | 36 | """ 37 | api_key = CONFIG.get('ip_api_key') 38 | if not api_key: 39 | return None 40 | return get_city(api_key, ip) 41 | 42 | def get_location_for_mirror(mirror): 43 | """ get the location for the mirror """ 44 | conn = get_connection() 45 | loc_key = cache_key('IPLOC', mirror) 46 | value = conn.get(loc_key) 47 | if value: 48 | return pickle.loads(value) 49 | 50 | # if we have a mirror name like mirror.domain.suffix/blah it won't work 51 | try: 52 | hostname = urlparse("http://{0}".format(mirror)).netloc 53 | except Exception as exc: 54 | # if error, just default to mirror that works most of the time 55 | print("Error getting location for {0} \n {1}".format(mirror, exc)) 56 | hostname = mirror 57 | 58 | ip = socket.gethostbyname(hostname) 59 | location = ping_ip2loc(ip) 60 | if location: 61 | conn.setex(loc_key, 86400, pickle.dumps(location)) # 1 day cache 62 | return location 63 | # if we get here, no good, return None 64 | return None 65 | 66 | def store_page_data(data, time_now): 67 | """ Store the data in the cache for later use.""" 68 | conn = get_connection() 69 | context = {'data': data, 'date_now': time_now} 70 | conn.set('PAGE_DATA', pickle.dumps(context)) 71 | 72 | def get_page_data(): 73 | """ Get the page data from the cache """ 74 | conn = get_connection() 75 | data = conn.get('PAGE_DATA') 76 | if data: 77 | return pickle.loads(data) 78 | return {} 79 | 80 | def store_json_data(data): 81 | """ Store the data in the cache for later use.""" 82 | conn = get_connection() 83 | conn.set('JSON_DATA', data) 84 | 85 | def get_json_data(): 86 | """ Get the json data from the cache """ 87 | conn = get_connection() 88 | data = conn.get('JSON_DATA') 89 | if not data: 90 | return {} 91 | return data 92 | 93 | def get_total_seconds(delta): 94 | """ need this since timedelta.total_seconds() 95 | isn't available in python 2.6.x""" 96 | if delta: 97 | return delta.seconds + (delta.days * 24 * 3600) 98 | return 0 99 | 100 | 101 | def cache_key(token, value): 102 | """ build a cache key """ 103 | return "{0}_{1}".format(token, value) 104 | 105 | 106 | def location_name(location): 107 | """ build out the location name given the location data """ 108 | if not location: 109 | return "N/A" 110 | city = location.get('cityName', None) 111 | region = location.get('regionName', None) 112 | country = location.get('countryName', None) 113 | country_code = location.get('countryCode', None) 114 | 115 | # clear out the -'s 116 | if city and city == '-': 117 | city = None 118 | if region and region == '-': 119 | region = None 120 | 121 | # If we have everything return everything but only use country_code 122 | if city and region and country_code: 123 | return "{0}, {1} {2}".format(city, region, country_code) 124 | 125 | # if we just have country, then only return country 126 | if not city and not region and country: 127 | return country 128 | 129 | # whatever else we have build it out by dynamically 130 | name = "" 131 | if city: 132 | name += city 133 | if city and region: 134 | name += ", " 135 | if region: 136 | name += region + " " 137 | if country: 138 | name += country 139 | return name 140 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | PyPI Mirror Status 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 29 | 30 | 31 | Fork me on GitHub 32 | 33 |
34 |
35 |
36 | 40 |
41 | 42 | 45 |
46 | 47 |
48 |
49 | 50 | 51 | 52 | 53 | {% for item in data %} 54 | 55 | 56 | 57 | 58 | 61 | 64 | 74 | {% endfor %} 75 | 76 | 77 | 78 |
MirrorLocation# of PackagesLast updateAgeResponse Time (ms)*Status
{{item.mirror}}{{item.location}}{{item.num_packages}}{{item.last_update}}{{item.time_diff_human}} 59 | {% if item.age_list %}{% endif %} 60 | {{item.response_time}} 62 | {% if item.resp_list %} {% endif %} 63 | {% if item.status == 'Green' %} 65 | Fresh 66 | {% elif item.status == "Yellow" %} 67 | Aging 68 | {% elif item.status == "Red" %} 69 | Old 70 | {% else %} 71 | N/A 72 | {% endif %} 73 |
* Response time from Virginia, US
79 |
80 |
81 |
82 |
83 |

Mirror Statuses

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
AgeStatus
age < 1 hourFresh
1 hour < age < 1 dayAging
age > 1 day Old
92 |
93 | 94 |
95 |

Using a mirror

96 |

Single Usage:
97 |

pip install -i http://<mirror>/simple <package>
98 |

Global settings:
99 | Add ~/.pip/pip.conf that includes: 100 |

101 | [global]
102 | index-url = http://<mirror>/simple
103 |

104 |
105 |
106 |
107 |
108 |
109 | Data also available in JSON format
110 | Page last updated at {{date_now}}
111 | Built by: @KenCochrane 112 |
113 | Built with: 114 | pypi-mirrors, Bootstrap, jQuery Sparklines, Redis and Flask 115 |
116 |
117 |
118 |
119 | 120 |
121 | 122 | 123 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /mirrorlib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # -*- coding: utf-8 -*- 4 | # Open Source Initiative OSI - The MIT License (MIT):Licensing 5 | # 6 | # The MIT License (MIT) 7 | # Copyright (c) 2012 Ken Cochrane (KenCochrane@gmail.com) 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | 27 | """ This is a simple library that you can use to find out the status of 28 | PyPI mirrors. It is based on the information that is found at 29 | http://www.python.org/dev/peps/pep-0381/ and 30 | http://pypi.python.org/mirrors 31 | 32 | Updated for PEP 449 33 | http://www.python.org/dev/peps/pep-0449/ 34 | 35 | """ 36 | import datetime 37 | import urllib2 38 | import time 39 | import operator 40 | 41 | MIRROR_URL_FORMAT = "{0}://{1}/last-modified" 42 | MASTER_URL_FORMAT = "https://{0}/daytime" 43 | MASTER_SERVER = "pypi.python.org" 44 | 45 | STATUSES = {'GREEN':'Green', 46 | 'YELLOW':'Yellow', 47 | 'RED':'Red'} 48 | 49 | 50 | def ping_mirror(mirror_url): 51 | """ Given a mirror_url it will ping it and return the contents and 52 | the response time """ 53 | try: 54 | start = time.time() 55 | res = urllib2.urlopen(mirror_url) 56 | stop = time.time() 57 | response_time = round((stop - start) * 1000, 2) 58 | return res.read().strip(), response_time 59 | except Exception: 60 | return None, None 61 | 62 | 63 | def parse_date(date_str): 64 | """ parse the date the get back from the mirror """ 65 | 66 | if len(date_str) == 17: 67 | # Used on official mirrors 68 | date_fmt = '%Y%m%dT%H:%M:%S' 69 | else: 70 | # Canonical ISO-8601 format (compliant with PEP 381) 71 | date_fmt = '%Y-%m-%dT%H:%M:%S' 72 | return datetime.datetime.strptime(date_str, date_fmt) 73 | 74 | 75 | def humanize_date_difference(now, other_date=None, offset=None, sign="ago"): 76 | """ This function prints the difference between two python datetime objects 77 | in a more human readable form 78 | """ 79 | 80 | if other_date: 81 | dt = abs(now - other_date) 82 | delta_d, offset = dt.days, dt.seconds 83 | if now < other_date: 84 | sign = "ahead" 85 | elif offset: 86 | delta_d, offset = divmod(offset, 60 * 60 * 24) 87 | else: 88 | raise ValueError("Must supply other_date or offset (from now)") 89 | 90 | offset, delta_s = divmod(offset, 60) 91 | delta_h, delta_m = divmod(offset, 60) 92 | 93 | if delta_d: 94 | fmt = "{d:d} days, {h:d} hours, {m:d} minutes {ago}" 95 | elif delta_h: 96 | fmt = "{h:d} hours, {m:d} minutes {ago}" 97 | elif delta_m: 98 | fmt = "{m:d} minutes, {s:d} seconds {ago}" 99 | else: 100 | fmt = "{s:d} seconds {ago}" 101 | return fmt.format(d=delta_d, h=delta_h, m=delta_m, s=delta_s, ago=sign) 102 | 103 | 104 | def mirror_status_desc(how_old): 105 | """ Get the status description of the mirror """ 106 | 107 | if how_old < datetime.timedelta(minutes=60): 108 | return STATUSES.get('GREEN') 109 | elif how_old < datetime.timedelta(days=1): 110 | return STATUSES.get('YELLOW') 111 | else: 112 | return STATUSES.get('RED') 113 | 114 | 115 | def ping_master_pypi_server(master_url_format=MASTER_URL_FORMAT): 116 | """ Ping the master Pypi server, it is a little different 117 | then the other servers. """ 118 | # a.pypi.python.org is the master server treat it differently 119 | m_url = master_url_format.format(MASTER_SERVER) 120 | res, res_time = ping_mirror(m_url) 121 | return MASTER_SERVER, res, res_time 122 | 123 | 124 | def mirror_statuses(mirror_url_format=MIRROR_URL_FORMAT, 125 | mirrors=None, 126 | ping_master_mirror=True): 127 | """ get the data we need from the mirrors and return a list of 128 | dictionaries with information about each mirror 129 | 130 | ``mirror_url_format`` - Change the url format from the standard one 131 | 132 | ``mirrors`` - provided the list if mirrors to check. 133 | The list needs to contain a tuple, (protocal, domain) for example: 134 | [('http, 'pypi.example.com'), ('https', 'pypi2.example.com')] 135 | 136 | ``ping_master_mirror`` - Do you want to include the status of the master 137 | server in the results. Defaults to True. 138 | 139 | """ 140 | if not mirrors: 141 | return [] 142 | 143 | # scan the mirrors and collect data 144 | ping_results = [] 145 | for protocol, ml in mirrors: 146 | m_url = mirror_url_format.format(protocol, ml) 147 | res, res_time = ping_mirror(m_url) 148 | ping_results.append((ml, res, res_time)) 149 | 150 | if ping_master_mirror: 151 | # pypi.python.org is the master server treat it differently 152 | master_results = ping_master_pypi_server() 153 | ping_results.insert(0, master_results) 154 | 155 | now = datetime.datetime.utcnow() 156 | results = [] 157 | for ml, res, res_time in ping_results: 158 | if res: 159 | last_update = parse_date(res) 160 | time_diff = abs(now - last_update) 161 | status = mirror_status_desc(time_diff) 162 | time_diff_human = humanize_date_difference(now, last_update) 163 | results.append({'mirror': ml, 164 | 'last_update': last_update, 165 | 'time_now': now, 166 | 'time_diff': time_diff, 167 | 'time_diff_human': time_diff_human, 168 | 'response_time': res_time, 169 | 'status': status} 170 | ) 171 | else: 172 | results.append({'mirror': ml, 173 | 'last_update': "Unavailable", 174 | 'time_now': now, 175 | 'time_diff_human': "Unavailable", 176 | 'time_diff': 'Unavailable', 177 | 'response_time': "Unavailable", 178 | 'status': 'Unavailable'} 179 | ) 180 | return results 181 | 182 | 183 | def is_master_alive(): 184 | """ Check if the Master server is alive """ 185 | server, response, res_time = ping_master_pypi_server() 186 | if response is None: 187 | return False 188 | return True 189 | 190 | 191 | def find_out_of_date_mirrors(mirrors=None): 192 | """ Find the mirrors that are out of date """ 193 | results = mirror_statuses(mirrors=mirrors) 194 | bad_mirrors = [] 195 | for r in results: 196 | if r.get('status') == STATUSES.get('RED'): 197 | bad_mirrors.append(r) 198 | return bad_mirrors 199 | 200 | 201 | def __find_mirror_sort(sort_field, mirrors=None, reverse=False): 202 | """ Find the first mirror that is sorted by sort_field """ 203 | results = mirror_statuses(mirrors=mirrors, ping_master_mirror=False) 204 | new_list = sorted(results, key=operator.itemgetter(sort_field), reverse=reverse) 205 | return new_list[0] 206 | 207 | 208 | def find_fastest_mirror(mirrors=None): 209 | """ Find the fastest mirror (via response time), might not be up to date """ 210 | return __find_mirror_sort('response_time', mirrors=mirrors) 211 | 212 | 213 | def find_freshest_mirror(mirrors=None): 214 | """ Find the freshest mirror (via last updated) """ 215 | return __find_mirror_sort('time_diff', mirrors=mirrors) -------------------------------------------------------------------------------- /static/js/jquery.sparkline.min.js: -------------------------------------------------------------------------------- 1 | /* jquery.sparkline 1.6 - http://omnipotent.net/jquery.sparkline/ 2 | ** Licensed under the New BSD License - see above site for details */ 3 | 4 | (function($){var defaults={common:{type:'line',lineColor:'#00f',fillColor:'#cdf',defaultPixelsPerValue:3,width:'auto',height:'auto',composite:false,tagValuesAttribute:'values',tagOptionsPrefix:'spark',enableTagOptions:false},line:{spotColor:'#f80',spotRadius:1.5,minSpotColor:'#f80',maxSpotColor:'#f80',lineWidth:1,normalRangeMin:undefined,normalRangeMax:undefined,normalRangeColor:'#ccc',drawNormalOnTop:false,chartRangeMin:undefined,chartRangeMax:undefined,chartRangeMinX:undefined,chartRangeMaxX:undefined},bar:{barColor:'#00f',negBarColor:'#f44',zeroColor:undefined,nullColor:undefined,zeroAxis:undefined,barWidth:4,barSpacing:1,chartRangeMax:undefined,chartRangeMin:undefined,chartRangeClip:false,colorMap:undefined},tristate:{barWidth:4,barSpacing:1,posBarColor:'#6f6',negBarColor:'#f44',zeroBarColor:'#999',colorMap:{}},discrete:{lineHeight:'auto',thresholdColor:undefined,thresholdValue:0,chartRangeMax:undefined,chartRangeMin:undefined,chartRangeClip:false},bullet:{targetColor:'red',targetWidth:3,performanceColor:'blue',rangeColors:['#D3DAFE','#A8B6FF','#7F94FF'],base:undefined},pie:{sliceColors:['#f00','#0f0','#00f']},box:{raw:false,boxLineColor:'black',boxFillColor:'#cdf',whiskerColor:'black',outlierLineColor:'#333',outlierFillColor:'white',medianColor:'red',showOutliers:true,outlierIQR:1.5,spotRadius:1.5,target:undefined,targetColor:'#4a2',chartRangeMax:undefined,chartRangeMin:undefined}};var VCanvas_base,VCanvas_canvas,VCanvas_vml;$.fn.simpledraw=function(width,height,use_existing){if(use_existing&&this[0].VCanvas){return this[0].VCanvas;} 5 | if(width===undefined){width=$(this).innerWidth();} 6 | if(height===undefined){height=$(this).innerHeight();} 7 | if($.browser.hasCanvas){return new VCanvas_canvas(width,height,this);}else if($.browser.msie){return new VCanvas_vml(width,height,this);}else{return false;}};var pending=[];$.fn.sparkline=function(uservalues,userOptions){return this.each(function(){var options=new $.fn.sparkline.options(this,userOptions);var render=function(){var values,width,height;if(uservalues==='html'||uservalues===undefined){var vals=this.getAttribute(options.get('tagValuesAttribute'));if(vals===undefined||vals===null){vals=$(this).html();} 8 | values=vals.replace(/(^\s*\s*$)|\s+/g,'').split(',');}else{values=uservalues;} 9 | width=options.get('width')=='auto'?values.length*options.get('defaultPixelsPerValue'):options.get('width');if(options.get('height')=='auto'){if(!options.get('composite')||!this.VCanvas){var tmp=document.createElement('span');tmp.innerHTML='a';$(this).html(tmp);height=$(tmp).innerHeight();$(tmp).remove();}}else{height=options.get('height');} 10 | $.fn.sparkline[options.get('type')].call(this,values,options,width,height);};if(($(this).html()&&$(this).is(':hidden'))||($.fn.jquery<"1.3.0"&&$(this).parents().is(':hidden'))||!$(this).parents('body').length){pending.push([this,render]);}else{render.call(this);}});};$.fn.sparkline.defaults=defaults;$.sparkline_display_visible=function(){for(var i=pending.length-1;i>=0;i--){var el=pending[i][0];if($(el).is(':visible')&&!$(el).parents().is(':hidden')){pending[i][1].call(el);pending.splice(i,1);}}};var UNSET_OPTION={};var normalizeValue=function(val){switch(val){case'undefined':val=undefined;break;case'null':val=null;break;case'true':val=true;break;case'false':val=false;break;default:var nf=parseFloat(val);if(val==nf){val=nf;}} 11 | return val;};$.fn.sparkline.options=function(tag,userOptions){var extendedOptions;this.userOptions=userOptions=userOptions||{};this.tag=tag;this.tagValCache={};var defaults=$.fn.sparkline.defaults;var base=defaults.common;this.tagOptionsPrefix=userOptions.enableTagOptions&&(userOptions.tagOptionsPrefix||base.tagOptionsPrefix);var tagOptionType=this.getTagSetting('type');if(tagOptionType===UNSET_OPTION){extendedOptions=defaults[userOptions.type||base.type];}else{extendedOptions=defaults[tagOptionType];} 12 | this.mergedOptions=$.extend({},base,extendedOptions,userOptions);};$.fn.sparkline.options.prototype.getTagSetting=function(key){var val,i,prefix=this.tagOptionsPrefix;if(prefix===false||prefix===undefined){return UNSET_OPTION;} 13 | if(this.tagValCache.hasOwnProperty(key)){val=this.tagValCache.key;}else{val=this.tag.getAttribute(prefix+key);if(val===undefined||val===null){val=UNSET_OPTION;}else if(val.substr(0,1)=='['){val=val.substr(1,val.length-2).split(',');for(i=val.length;i--;){val[i]=normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g,''));}}else if(val.substr(0,1)=='{'){var pairs=val.substr(1,val.length-2).split(',');val={};for(i=pairs.length;i--;){var keyval=pairs[i].split(':',2);val[keyval[0].replace(/(^\s*)|(\s*$)/g,'')]=normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g,''));}}else{val=normalizeValue(val);} 14 | this.tagValCache.key=val;} 15 | return val;};$.fn.sparkline.options.prototype.get=function(key){var tagOption=this.getTagSetting(key);if(tagOption!==UNSET_OPTION){return tagOption;} 16 | return this.mergedOptions[key];};$.fn.sparkline.line=function(values,options,width,height){var xvalues=[],yvalues=[],yminmax=[];for(var i=0;imaxy){maxy=normalRangeMax;}} 20 | if(options.get('chartRangeMin')!==undefined&&(options.get('chartRangeClip')||options.get('chartRangeMin')maxy)){maxy=options.get('chartRangeMax');} 22 | if(options.get('chartRangeMinX')!==undefined&&(options.get('chartRangeClipX')||options.get('chartRangeMinX')maxx)){maxx=options.get('chartRangeMaxX');} 24 | var rangex=maxx-minx===0?1:maxx-minx;var rangey=maxy-miny===0?1:maxy-miny;var vl=yvalues.length-1;if(vl<1){this.innerHTML='';return;} 25 | var target=$(this).simpledraw(width,height,options.get('composite'));if(target){var canvas_width=target.pixel_width;var canvas_height=target.pixel_height;var canvas_top=0;var canvas_left=0;var spotRadius=options.get('spotRadius');if(spotRadius&&(canvas_width<(spotRadius*4)||canvas_height<(spotRadius*4))){spotRadius=0;} 26 | if(spotRadius){if(options.get('minSpotColor')||(options.get('spotColor')&&yvalues[vl]==miny)){canvas_height-=Math.ceil(spotRadius);} 27 | if(options.get('maxSpotColor')||(options.get('spotColor')&&yvalues[vl]==maxy)){canvas_height-=Math.ceil(spotRadius);canvas_top+=Math.ceil(spotRadius);} 28 | if(options.get('minSpotColor')||options.get('maxSpotColor')&&(yvalues[0]==miny||yvalues[0]==maxy)){canvas_left+=Math.ceil(spotRadius);canvas_width-=Math.ceil(spotRadius);} 29 | if(options.get('spotColor')||(options.get('minSpotColor')||options.get('maxSpotColor')&&(yvalues[vl]==miny||yvalues[vl]==maxy))){canvas_width-=Math.ceil(spotRadius);}} 30 | canvas_height--;var drawNormalRange=function(){if(normalRangeMin!==undefined){var ytop=canvas_top+Math.round(canvas_height-(canvas_height*((normalRangeMax-miny)/rangey)));var height=Math.round((canvas_height*(normalRangeMax-normalRangeMin))/rangey);target.drawRect(canvas_left,ytop,canvas_width,height,undefined,options.get('normalRangeColor'));}};if(!options.get('drawNormalOnTop')){drawNormalRange();} 31 | var path=[];var paths=[path];var x,y,vlen=yvalues.length;for(i=0;imaxy){y=maxy;} 33 | if(!path.length){path.push([canvas_left+Math.round((x-minx)*(canvas_width/rangex)),canvas_top+canvas_height]);} 34 | path.push([canvas_left+Math.round((x-minx)*(canvas_width/rangex)),canvas_top+Math.round(canvas_height-(canvas_height*((y-miny)/rangey)))]);}} 35 | var lineshapes=[];var fillshapes=[];var plen=paths.length;for(i=0;i2){path[0]=[path[0][0],path[1][1]];} 38 | lineshapes.push(path);} 39 | plen=fillshapes.length;for(i=0;imax)){max=options.get('chartRangeMax');} 47 | var zeroAxis=options.get('zeroAxis');if(zeroAxis===undefined){zeroAxis=min<0;} 48 | var range=max-min===0?1:max-min;var colorMapByIndex,colorMapByValue;if($.isArray(options.get('colorMap'))){colorMapByIndex=options.get('colorMap');colorMapByValue=null;}else{colorMapByIndex=null;colorMapByValue=options.get('colorMap');} 49 | var target=$(this).simpledraw(width,height,options.get('composite'));if(target){var color,canvas_height=target.pixel_height,yzero=min<0&&zeroAxis?canvas_height-Math.round(canvas_height*(Math.abs(min)/range))-1:canvas_height-1;for(i=values.length;i--;){var x=i*(options.get('barWidth')+options.get('barSpacing')),y,val=values[i];if(val===null){if(options.get('nullColor')){color=options.get('nullColor');val=(zeroAxis&&min<0)?0:min;height=1;y=(zeroAxis&&min<0)?yzero:canvas_height-height;}else{continue;}}else{if(valmax){val=max;} 51 | color=(val<0)?options.get('negBarColor'):options.get('barColor');if(zeroAxis&&min<0){height=Math.round(canvas_height*((Math.abs(val)/range)))+1;y=(val<0)?yzero:yzero-height;}else{height=Math.round(canvas_height*((val-min)/range))+1;y=canvas_height-height;} 52 | if(val===0&&options.get('zeroColor')!==undefined){color=options.get('zeroColor');} 53 | if(colorMapByValue&&colorMapByValue[val]){color=colorMapByValue[val];}else if(colorMapByIndex&&colorMapByIndex.length>i){color=colorMapByIndex[i];} 54 | if(color===null){continue;}} 55 | target.drawRect(x,y,options.get('barWidth')-1,height-1,color,color);}}else{this.innerHTML='';}};$.fn.sparkline.tristate=function(values,options,width,height){values=$.map(values,Number);width=(values.length*options.get('barWidth'))+((values.length-1)*options.get('barSpacing'));var colorMapByIndex,colorMapByValue;if($.isArray(options.get('colorMap'))){colorMapByIndex=options.get('colorMap');colorMapByValue=null;}else{colorMapByIndex=null;colorMapByValue=options.get('colorMap');} 56 | var target=$(this).simpledraw(width,height,options.get('composite'));if(target){var canvas_height=target.pixel_height,half_height=Math.round(canvas_height/2);for(var i=values.length;i--;){var x=i*(options.get('barWidth')+options.get('barSpacing')),y,color;if(values[i]<0){y=half_height;height=half_height-1;color=options.get('negBarColor');}else if(values[i]>0){y=0;height=half_height-1;color=options.get('posBarColor');}else{y=half_height-1;height=2;color=options.get('zeroBarColor');} 57 | if(colorMapByValue&&colorMapByValue[values[i]]){color=colorMapByValue[values[i]];}else if(colorMapByIndex&&colorMapByIndex.length>i){color=colorMapByIndex[i];} 58 | if(color===null){continue;} 59 | target.drawRect(x,y,options.get('barWidth')-1,height-1,color,color);}}else{this.innerHTML='';}};$.fn.sparkline.discrete=function(values,options,width,height){values=$.map(values,Number);width=options.get('width')=='auto'?values.length*2:width;var interval=Math.floor(width/values.length);var target=$(this).simpledraw(width,height,options.get('composite'));if(target){var canvas_height=target.pixel_height,line_height=options.get('lineHeight')=='auto'?Math.round(canvas_height*0.3):options.get('lineHeight'),pheight=canvas_height-line_height,min=Math.min.apply(Math,values),max=Math.max.apply(Math,values);if(options.get('chartRangeMin')!==undefined&&(options.get('chartRangeClip')||options.get('chartRangeMin')max)){max=options.get('chartRangeMax');} 61 | var range=max-min;for(var i=values.length;i--;){var val=values[i];if(valmax){val=max;} 63 | var x=(i*interval),ytop=Math.round(pheight-pheight*((val-min)/range));target.drawLine(x,ytop,x,ytop+line_height,(options.get('thresholdColor')&&val1){var canvas_width=target.pixel_width-Math.ceil(options.get('targetWidth')/2),canvas_height=target.pixel_height,min=Math.min.apply(Math,values),max=Math.max.apply(Math,values);if(options.get('base')===undefined){min=min<0?min:0;}else{min=options.get('base');} 64 | var range=max-min;for(var i=2,vlen=values.length;i1){var canvas_width=target.pixel_width,canvas_height=target.pixel_height,radius=Math.floor(Math.min(canvas_width,canvas_height)/2),total=0,next=0,circle=2*Math.PI;for(var i=values.length;i--;){total+=values[i];} 66 | if(options.get('offset')){next+=(2*Math.PI)*(options.get('offset')/360);} 67 | var vlen=values.length;for(i=0;i0){end=next+(circle*(values[i]/total));} 68 | target.drawPieSlice(radius,radius,radius,start,end,undefined,options.get('sliceColors')[i%options.get('sliceColors').length]);next=end;}}};var quartile=function(values,q){if(q==2){var vl2=Math.floor(values.length/2);return values.length%2?values[vl2]:(values[vl2]+values[vl2+1])/2;}else{var vl4=Math.floor(values.length/4);return values.length%2?(values[vl4*q]+values[vl4*q+1])/2:values[vl4*q];}};$.fn.sparkline.box=function(values,options,width,height){values=$.map(values,Number);width=options.get('width')=='auto'?'4.0em':width;var minvalue=options.get('chartRangeMin')===undefined?Math.min.apply(Math,values):options.get('chartRangeMin'),maxvalue=options.get('chartRangeMax')===undefined?Math.max.apply(Math,values):options.get('chartRangeMax'),target=$(this).simpledraw(width,height,options.get('composite')),vlen=values.length,lwhisker,loutlier,q1,q2,q3,rwhisker,routlier;if(target&&values.length>1){var canvas_width=target.pixel_width,canvas_height=target.pixel_height;if(options.get('raw')){if(options.get('showOutliers')&&values.length>5){loutlier=values[0];lwhisker=values[1];q1=values[2];q2=values[3];q3=values[4];rwhisker=values[5];routlier=values[6];}else{lwhisker=values[0];q1=values[1];q2=values[2];q3=values[3];rwhisker=values[4];}}else{values.sort(function(a,b){return a-b;});q1=quartile(values,1);q2=quartile(values,2);q3=quartile(values,3);var iqr=q3-q1;if(options.get('showOutliers')){lwhisker=undefined;rwhisker=undefined;for(var i=0;iq1-(iqr*options.get('outlierIQR'))){lwhisker=values[i];} 69 | if(values[i]rwhisker){target.drawCircle((routlier-minvalue)*unitsize+canvas_left,canvas_height/2,options.get('spotRadius'),options.get('outlierLineColor'),options.get('outlierFillColor'));}} 73 | target.drawRect(Math.round((q1-minvalue)*unitsize+canvas_left),Math.round(canvas_height*0.1),Math.round((q3-q1)*unitsize),Math.round(canvas_height*0.8),options.get('boxLineColor'),options.get('boxFillColor'));target.drawLine(Math.round((lwhisker-minvalue)*unitsize+canvas_left),Math.round(canvas_height/2),Math.round((q1-minvalue)*unitsize+canvas_left),Math.round(canvas_height/2),options.get('lineColor'));target.drawLine(Math.round((lwhisker-minvalue)*unitsize+canvas_left),Math.round(canvas_height/4),Math.round((lwhisker-minvalue)*unitsize+canvas_left),Math.round(canvas_height-canvas_height/4),options.get('whiskerColor'));target.drawLine(Math.round((rwhisker-minvalue)*unitsize+canvas_left),Math.round(canvas_height/2),Math.round((q3-minvalue)*unitsize+canvas_left),Math.round(canvas_height/2),options.get('lineColor'));target.drawLine(Math.round((rwhisker-minvalue)*unitsize+canvas_left),Math.round(canvas_height/4),Math.round((rwhisker-minvalue)*unitsize+canvas_left),Math.round(canvas_height-canvas_height/4),options.get('whiskerColor'));target.drawLine(Math.round((q2-minvalue)*unitsize+canvas_left),Math.round(canvas_height*0.1),Math.round((q2-minvalue)*unitsize+canvas_left),Math.round(canvas_height*0.9),options.get('medianColor'));if(options.get('target')){var size=Math.ceil(options.get('spotRadius'));target.drawLine(Math.round((options.get('target')-minvalue)*unitsize+canvas_left),Math.round((canvas_height/2)-size),Math.round((options.get('target')-minvalue)*unitsize+canvas_left),Math.round((canvas_height/2)+size),options.get('targetColor'));target.drawLine(Math.round((options.get('target')-minvalue)*unitsize+canvas_left-size),Math.round(canvas_height/2),Math.round((options.get('target')-minvalue)*unitsize+canvas_left+size),Math.round(canvas_height/2),options.get('targetColor'));}}else{this.innerHTML='';}};if($.browser.msie&&!document.namespaces.v){document.namespaces.add('v','urn:schemas-microsoft-com:vml','#default#VML');} 74 | if($.browser.hasCanvas===undefined){var t=document.createElement('canvas');$.browser.hasCanvas=t.getContext!==undefined;} 75 | VCanvas_base=function(width,height,target){};VCanvas_base.prototype={init:function(width,height,target){this.width=width;this.height=height;this.target=target;if(target[0]){target=target[0];} 76 | target.VCanvas=this;},drawShape:function(path,lineColor,fillColor,lineWidth){alert('drawShape not implemented');},drawLine:function(x1,y1,x2,y2,lineColor,lineWidth){return this.drawShape([[x1,y1],[x2,y2]],lineColor,lineWidth);},drawCircle:function(x,y,radius,lineColor,fillColor){alert('drawCircle not implemented');},drawPieSlice:function(x,y,radius,startAngle,endAngle,lineColor,fillColor){alert('drawPieSlice not implemented');},drawRect:function(x,y,width,height,lineColor,fillColor){alert('drawRect not implemented');},getElement:function(){return this.canvas;},_insert:function(el,target){$(target).html(el);}};VCanvas_canvas=function(width,height,target){return this.init(width,height,target);};VCanvas_canvas.prototype=$.extend(new VCanvas_base(),{_super:VCanvas_base.prototype,init:function(width,height,target){this._super.init(width,height,target);this.canvas=document.createElement('canvas');if(target[0]){target=target[0];} 77 | target.VCanvas=this;$(this.canvas).css({display:'inline-block',width:width,height:height,verticalAlign:'top'});this._insert(this.canvas,target);this.pixel_height=$(this.canvas).height();this.pixel_width=$(this.canvas).width();this.canvas.width=this.pixel_width;this.canvas.height=this.pixel_height;$(this.canvas).css({width:this.pixel_width,height:this.pixel_height});},_getContext:function(lineColor,fillColor,lineWidth){var context=this.canvas.getContext('2d');if(lineColor!==undefined){context.strokeStyle=lineColor;} 78 | context.lineWidth=lineWidth===undefined?1:lineWidth;if(fillColor!==undefined){context.fillStyle=fillColor;} 79 | return context;},drawShape:function(path,lineColor,fillColor,lineWidth){var context=this._getContext(lineColor,fillColor,lineWidth);context.beginPath();context.moveTo(path[0][0]+0.5,path[0][1]+0.5);for(var i=1,plen=path.length;i';this.canvas.insertAdjacentHTML('beforeEnd',groupel);this.group=$(this.canvas).children()[0];},drawShape:function(path,lineColor,fillColor,lineWidth){var vpath=[];for(var i=0,plen=path.length;i'+' ';this.group.insertAdjacentHTML('beforeEnd',vel);},drawCircle:function(x,y,radius,lineColor,fillColor){x-=radius+1;y-=radius+1;var stroke=lineColor===undefined?' stroked="false" ':' strokeWeight="1" strokeColor="'+lineColor+'" ';var fill=fillColor===undefined?' filled="false"':' fillColor="'+fillColor+'" filled="true" ';var vel='';this.group.insertAdjacentHTML('beforeEnd',vel);},drawPieSlice:function(x,y,radius,startAngle,endAngle,lineColor,fillColor){if(startAngle==endAngle){return;} 90 | if((endAngle-startAngle)==(2*Math.PI)){startAngle=0.0;endAngle=(2*Math.PI);} 91 | var startx=x+Math.round(Math.cos(startAngle)*radius);var starty=y+Math.round(Math.sin(startAngle)*radius);var endx=x+Math.round(Math.cos(endAngle)*radius);var endy=y+Math.round(Math.sin(endAngle)*radius);if(startx==endx&&starty==endy&&(endAngle-startAngle)'+' ';this.group.insertAdjacentHTML('beforeEnd',vel);},drawRect:function(x,y,width,height,lineColor,fillColor){return this.drawShape([[x,y],[x,y+height],[x+width,y+height],[x+width,y],[x,y]],lineColor,fillColor);}});})(jQuery); -------------------------------------------------------------------------------- /static/css/bootstrap.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v2.0.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | .clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";} 11 | .clearfix:after{clear:both;} 12 | .hide-text{overflow:hidden;text-indent:100%;white-space:nowrap;} 13 | .input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} 14 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} 15 | audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} 16 | audio:not([controls]){display:none;} 17 | html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} 18 | a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} 19 | a:hover,a:active{outline:0;} 20 | sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;} 21 | sup{top:-0.5em;} 22 | sub{bottom:-0.25em;} 23 | img{height:auto;border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;} 24 | button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;} 25 | button,input{*overflow:visible;line-height:normal;} 26 | button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;} 27 | button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} 28 | input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} 29 | input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;} 30 | textarea{overflow:auto;vertical-align:top;} 31 | body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;} 32 | a{color:#0088cc;text-decoration:none;} 33 | a:hover{color:#005580;text-decoration:underline;} 34 | .row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";} 35 | .row:after{clear:both;} 36 | [class*="span"]{float:left;margin-left:20px;} 37 | .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} 38 | .span12{width:940px;} 39 | .span11{width:860px;} 40 | .span10{width:780px;} 41 | .span9{width:700px;} 42 | .span8{width:620px;} 43 | .span7{width:540px;} 44 | .span6{width:460px;} 45 | .span5{width:380px;} 46 | .span4{width:300px;} 47 | .span3{width:220px;} 48 | .span2{width:140px;} 49 | .span1{width:60px;} 50 | .offset12{margin-left:980px;} 51 | .offset11{margin-left:900px;} 52 | .offset10{margin-left:820px;} 53 | .offset9{margin-left:740px;} 54 | .offset8{margin-left:660px;} 55 | .offset7{margin-left:580px;} 56 | .offset6{margin-left:500px;} 57 | .offset5{margin-left:420px;} 58 | .offset4{margin-left:340px;} 59 | .offset3{margin-left:260px;} 60 | .offset2{margin-left:180px;} 61 | .offset1{margin-left:100px;} 62 | .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";} 63 | .row-fluid:after{clear:both;} 64 | .row-fluid>[class*="span"]{float:left;margin-left:2.127659574%;} 65 | .row-fluid>[class*="span"]:first-child{margin-left:0;} 66 | .row-fluid > .span12{width:99.99999998999999%;} 67 | .row-fluid > .span11{width:91.489361693%;} 68 | .row-fluid > .span10{width:82.97872339599999%;} 69 | .row-fluid > .span9{width:74.468085099%;} 70 | .row-fluid > .span8{width:65.95744680199999%;} 71 | .row-fluid > .span7{width:57.446808505%;} 72 | .row-fluid > .span6{width:48.93617020799999%;} 73 | .row-fluid > .span5{width:40.425531911%;} 74 | .row-fluid > .span4{width:31.914893614%;} 75 | .row-fluid > .span3{width:23.404255317%;} 76 | .row-fluid > .span2{width:14.89361702%;} 77 | .row-fluid > .span1{width:6.382978723%;} 78 | .container{margin-left:auto;margin-right:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";} 79 | .container:after{clear:both;} 80 | .container-fluid{padding-left:20px;padding-right:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";} 81 | .container-fluid:after{clear:both;} 82 | p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;}p small{font-size:11px;color:#999999;} 83 | .lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;} 84 | h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999999;} 85 | h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;} 86 | h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;} 87 | h3{line-height:27px;font-size:18px;}h3 small{font-size:14px;} 88 | h4,h5,h6{line-height:18px;} 89 | h4{font-size:14px;}h4 small{font-size:12px;} 90 | h5{font-size:12px;} 91 | h6{font-size:11px;color:#999999;text-transform:uppercase;} 92 | .page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;} 93 | .page-header h1{line-height:1;} 94 | ul,ol{padding:0;margin:0 0 9px 25px;} 95 | ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} 96 | ul{list-style:disc;} 97 | ol{list-style:decimal;} 98 | li{line-height:18px;} 99 | ul.unstyled,ol.unstyled{margin-left:0;list-style:none;} 100 | dl{margin-bottom:18px;} 101 | dt,dd{line-height:18px;} 102 | dt{font-weight:bold;line-height:17px;} 103 | dd{margin-left:9px;} 104 | .dl-horizontal dt{float:left;clear:left;width:120px;text-align:right;} 105 | .dl-horizontal dd{margin-left:130px;} 106 | hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;} 107 | strong{font-weight:bold;} 108 | em{font-style:italic;} 109 | .muted{color:#999999;} 110 | abbr[title]{border-bottom:1px dotted #ddd;cursor:help;} 111 | abbr.initialism{font-size:90%;text-transform:uppercase;} 112 | blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;} 113 | blockquote small{display:block;line-height:18px;color:#999999;}blockquote small:before{content:'\2014 \00A0';} 114 | blockquote.pull-right{float:right;padding-left:0;padding-right:15px;border-left:0;border-right:5px solid #eeeeee;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;} 115 | q:before,q:after,blockquote:before,blockquote:after{content:"";} 116 | address{display:block;margin-bottom:18px;line-height:18px;font-style:normal;} 117 | small{font-size:100%;} 118 | cite{font-style:normal;} 119 | code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 120 | code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;} 121 | pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;white-space:pre;white-space:pre-wrap;word-break:break-all;word-wrap:break-word;}pre.prettyprint{margin-bottom:18px;} 122 | pre code{padding:0;color:inherit;background-color:transparent;border:0;} 123 | .pre-scrollable{max-height:340px;overflow-y:scroll;} 124 | .label{padding:1px 4px 2px;font-size:10.998px;font-weight:bold;line-height:13px;color:#ffffff;vertical-align:middle;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 125 | .label:hover{color:#ffffff;text-decoration:none;} 126 | .label-important{background-color:#b94a48;} 127 | .label-important:hover{background-color:#953b39;} 128 | .label-warning{background-color:#f89406;} 129 | .label-warning:hover{background-color:#c67605;} 130 | .label-success{background-color:#468847;} 131 | .label-success:hover{background-color:#356635;} 132 | .label-info{background-color:#3a87ad;} 133 | .label-info:hover{background-color:#2d6987;} 134 | .label-inverse{background-color:#333333;} 135 | .label-inverse:hover{background-color:#1a1a1a;} 136 | .badge{padding:1px 9px 2px;font-size:12.025px;font-weight:bold;white-space:nowrap;color:#ffffff;background-color:#999999;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;} 137 | .badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;} 138 | .badge-error{background-color:#b94a48;} 139 | .badge-error:hover{background-color:#953b39;} 140 | .badge-warning{background-color:#f89406;} 141 | .badge-warning:hover{background-color:#c67605;} 142 | .badge-success{background-color:#468847;} 143 | .badge-success:hover{background-color:#356635;} 144 | .badge-info{background-color:#3a87ad;} 145 | .badge-info:hover{background-color:#2d6987;} 146 | .badge-inverse{background-color:#333333;} 147 | .badge-inverse:hover{background-color:#1a1a1a;} 148 | table{max-width:100%;border-collapse:collapse;border-spacing:0;background-color:transparent;} 149 | .table{width:100%;margin-bottom:18px;}.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;} 150 | .table th{font-weight:bold;} 151 | .table thead th{vertical-align:bottom;} 152 | .table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;} 153 | .table tbody+tbody{border-top:2px solid #dddddd;} 154 | .table-condensed th,.table-condensed td{padding:4px 5px;} 155 | .table-bordered{border:1px solid #dddddd;border-left:0;border-collapse:separate;*border-collapse:collapsed;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;} 156 | .table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;} 157 | .table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;} 158 | .table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;} 159 | .table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;} 160 | .table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} 161 | .table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} 162 | .table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;} 163 | table .span1{float:none;width:44px;margin-left:0;} 164 | table .span2{float:none;width:124px;margin-left:0;} 165 | table .span3{float:none;width:204px;margin-left:0;} 166 | table .span4{float:none;width:284px;margin-left:0;} 167 | table .span5{float:none;width:364px;margin-left:0;} 168 | table .span6{float:none;width:444px;margin-left:0;} 169 | table .span7{float:none;width:524px;margin-left:0;} 170 | table .span8{float:none;width:604px;margin-left:0;} 171 | table .span9{float:none;width:684px;margin-left:0;} 172 | table .span10{float:none;width:764px;margin-left:0;} 173 | table .span11{float:none;width:844px;margin-left:0;} 174 | table .span12{float:none;width:924px;margin-left:0;} 175 | table .span13{float:none;width:1004px;margin-left:0;} 176 | table .span14{float:none;width:1084px;margin-left:0;} 177 | table .span15{float:none;width:1164px;margin-left:0;} 178 | table .span16{float:none;width:1244px;margin-left:0;} 179 | table .span17{float:none;width:1324px;margin-left:0;} 180 | table .span18{float:none;width:1404px;margin-left:0;} 181 | table .span19{float:none;width:1484px;margin-left:0;} 182 | table .span20{float:none;width:1564px;margin-left:0;} 183 | table .span21{float:none;width:1644px;margin-left:0;} 184 | table .span22{float:none;width:1724px;margin-left:0;} 185 | table .span23{float:none;width:1804px;margin-left:0;} 186 | table .span24{float:none;width:1884px;margin-left:0;} 187 | [class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;*margin-right:.3em;}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;} 188 | .icon-white{background-image:url("../img/glyphicons-halflings-white.png");} 189 | .icon-glass{background-position:0 0;} 190 | .icon-music{background-position:-24px 0;} 191 | .icon-search{background-position:-48px 0;} 192 | .icon-envelope{background-position:-72px 0;} 193 | .icon-heart{background-position:-96px 0;} 194 | .icon-star{background-position:-120px 0;} 195 | .icon-star-empty{background-position:-144px 0;} 196 | .icon-user{background-position:-168px 0;} 197 | .icon-film{background-position:-192px 0;} 198 | .icon-th-large{background-position:-216px 0;} 199 | .icon-th{background-position:-240px 0;} 200 | .icon-th-list{background-position:-264px 0;} 201 | .icon-ok{background-position:-288px 0;} 202 | .icon-remove{background-position:-312px 0;} 203 | .icon-zoom-in{background-position:-336px 0;} 204 | .icon-zoom-out{background-position:-360px 0;} 205 | .icon-off{background-position:-384px 0;} 206 | .icon-signal{background-position:-408px 0;} 207 | .icon-cog{background-position:-432px 0;} 208 | .icon-trash{background-position:-456px 0;} 209 | .icon-home{background-position:0 -24px;} 210 | .icon-file{background-position:-24px -24px;} 211 | .icon-time{background-position:-48px -24px;} 212 | .icon-road{background-position:-72px -24px;} 213 | .icon-download-alt{background-position:-96px -24px;} 214 | .icon-download{background-position:-120px -24px;} 215 | .icon-upload{background-position:-144px -24px;} 216 | .icon-inbox{background-position:-168px -24px;} 217 | .icon-play-circle{background-position:-192px -24px;} 218 | .icon-repeat{background-position:-216px -24px;} 219 | .icon-refresh{background-position:-240px -24px;} 220 | .icon-list-alt{background-position:-264px -24px;} 221 | .icon-lock{background-position:-287px -24px;} 222 | .icon-flag{background-position:-312px -24px;} 223 | .icon-headphones{background-position:-336px -24px;} 224 | .icon-volume-off{background-position:-360px -24px;} 225 | .icon-volume-down{background-position:-384px -24px;} 226 | .icon-volume-up{background-position:-408px -24px;} 227 | .icon-qrcode{background-position:-432px -24px;} 228 | .icon-barcode{background-position:-456px -24px;} 229 | .icon-tag{background-position:0 -48px;} 230 | .icon-tags{background-position:-25px -48px;} 231 | .icon-book{background-position:-48px -48px;} 232 | .icon-bookmark{background-position:-72px -48px;} 233 | .icon-print{background-position:-96px -48px;} 234 | .icon-camera{background-position:-120px -48px;} 235 | .icon-font{background-position:-144px -48px;} 236 | .icon-bold{background-position:-167px -48px;} 237 | .icon-italic{background-position:-192px -48px;} 238 | .icon-text-height{background-position:-216px -48px;} 239 | .icon-text-width{background-position:-240px -48px;} 240 | .icon-align-left{background-position:-264px -48px;} 241 | .icon-align-center{background-position:-288px -48px;} 242 | .icon-align-right{background-position:-312px -48px;} 243 | .icon-align-justify{background-position:-336px -48px;} 244 | .icon-list{background-position:-360px -48px;} 245 | .icon-indent-left{background-position:-384px -48px;} 246 | .icon-indent-right{background-position:-408px -48px;} 247 | .icon-facetime-video{background-position:-432px -48px;} 248 | .icon-picture{background-position:-456px -48px;} 249 | .icon-pencil{background-position:0 -72px;} 250 | .icon-map-marker{background-position:-24px -72px;} 251 | .icon-adjust{background-position:-48px -72px;} 252 | .icon-tint{background-position:-72px -72px;} 253 | .icon-edit{background-position:-96px -72px;} 254 | .icon-share{background-position:-120px -72px;} 255 | .icon-check{background-position:-144px -72px;} 256 | .icon-move{background-position:-168px -72px;} 257 | .icon-step-backward{background-position:-192px -72px;} 258 | .icon-fast-backward{background-position:-216px -72px;} 259 | .icon-backward{background-position:-240px -72px;} 260 | .icon-play{background-position:-264px -72px;} 261 | .icon-pause{background-position:-288px -72px;} 262 | .icon-stop{background-position:-312px -72px;} 263 | .icon-forward{background-position:-336px -72px;} 264 | .icon-fast-forward{background-position:-360px -72px;} 265 | .icon-step-forward{background-position:-384px -72px;} 266 | .icon-eject{background-position:-408px -72px;} 267 | .icon-chevron-left{background-position:-432px -72px;} 268 | .icon-chevron-right{background-position:-456px -72px;} 269 | .icon-plus-sign{background-position:0 -96px;} 270 | .icon-minus-sign{background-position:-24px -96px;} 271 | .icon-remove-sign{background-position:-48px -96px;} 272 | .icon-ok-sign{background-position:-72px -96px;} 273 | .icon-question-sign{background-position:-96px -96px;} 274 | .icon-info-sign{background-position:-120px -96px;} 275 | .icon-screenshot{background-position:-144px -96px;} 276 | .icon-remove-circle{background-position:-168px -96px;} 277 | .icon-ok-circle{background-position:-192px -96px;} 278 | .icon-ban-circle{background-position:-216px -96px;} 279 | .icon-arrow-left{background-position:-240px -96px;} 280 | .icon-arrow-right{background-position:-264px -96px;} 281 | .icon-arrow-up{background-position:-289px -96px;} 282 | .icon-arrow-down{background-position:-312px -96px;} 283 | .icon-share-alt{background-position:-336px -96px;} 284 | .icon-resize-full{background-position:-360px -96px;} 285 | .icon-resize-small{background-position:-384px -96px;} 286 | .icon-plus{background-position:-408px -96px;} 287 | .icon-minus{background-position:-433px -96px;} 288 | .icon-asterisk{background-position:-456px -96px;} 289 | .icon-exclamation-sign{background-position:0 -120px;} 290 | .icon-gift{background-position:-24px -120px;} 291 | .icon-leaf{background-position:-48px -120px;} 292 | .icon-fire{background-position:-72px -120px;} 293 | .icon-eye-open{background-position:-96px -120px;} 294 | .icon-eye-close{background-position:-120px -120px;} 295 | .icon-warning-sign{background-position:-144px -120px;} 296 | .icon-plane{background-position:-168px -120px;} 297 | .icon-calendar{background-position:-192px -120px;} 298 | .icon-random{background-position:-216px -120px;} 299 | .icon-comment{background-position:-240px -120px;} 300 | .icon-magnet{background-position:-264px -120px;} 301 | .icon-chevron-up{background-position:-288px -120px;} 302 | .icon-chevron-down{background-position:-313px -119px;} 303 | .icon-retweet{background-position:-336px -120px;} 304 | .icon-shopping-cart{background-position:-360px -120px;} 305 | .icon-folder-close{background-position:-384px -120px;} 306 | .icon-folder-open{background-position:-408px -120px;} 307 | .icon-resize-vertical{background-position:-432px -119px;} 308 | .icon-resize-horizontal{background-position:-456px -118px;} 309 | .btn-group{position:relative;*zoom:1;*margin-left:.3em;}.btn-group:before,.btn-group:after{display:table;content:"";} 310 | .btn-group:after{clear:both;} 311 | .btn-group:first-child{*margin-left:0;} 312 | .btn-group+.btn-group{margin-left:5px;} 313 | .btn-toolbar{margin-top:9px;margin-bottom:9px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;} 314 | .btn-group .btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} 315 | .btn-group .btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} 316 | .btn-group .btn:last-child,.btn-group .dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} 317 | .btn-group .btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;} 318 | .btn-group .btn.large:last-child,.btn-group .large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;} 319 | .btn-group .btn:hover,.btn-group .btn:focus,.btn-group .btn:active,.btn-group .btn.active{z-index:2;} 320 | .btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;} 321 | .btn-group .dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);*padding-top:3px;*padding-bottom:3px;} 322 | .btn-group .btn-mini.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:1px;*padding-bottom:1px;} 323 | .btn-group .btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px;} 324 | .btn-group .btn-large.dropdown-toggle{padding-left:12px;padding-right:12px;} 325 | .btn-group.open{*z-index:1000;}.btn-group.open .dropdown-menu{display:block;margin-top:1px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} 326 | .btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);} 327 | .btn .caret{margin-top:7px;margin-left:0;} 328 | .btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100);} 329 | .btn-mini .caret{margin-top:5px;} 330 | .btn-small .caret{margin-top:6px;} 331 | .btn-large .caret{margin-top:6px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} 332 | .btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);} 333 | .nav{margin-left:0;margin-bottom:18px;list-style:none;} 334 | .nav>li>a{display:block;} 335 | .nav>li>a:hover{text-decoration:none;background-color:#eeeeee;} 336 | .nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;} 337 | .nav li+.nav-header{margin-top:9px;} 338 | .nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;} 339 | .nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} 340 | .nav-list>li>a{padding:3px 15px;} 341 | .nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;} 342 | .nav-list [class^="icon-"]{margin-right:2px;} 343 | .nav-list .divider{height:1px;margin:8px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;} 344 | .nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";} 345 | .nav-tabs:after,.nav-pills:after{clear:both;} 346 | .nav-tabs>li,.nav-pills>li{float:left;} 347 | .nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;} 348 | .nav-tabs{border-bottom:1px solid #ddd;} 349 | .nav-tabs>li{margin-bottom:-1px;} 350 | .nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;} 351 | .nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;} 352 | .nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} 353 | .nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#0088cc;} 354 | .nav-stacked>li{float:none;} 355 | .nav-stacked>li>a{margin-right:0;} 356 | .nav-tabs.nav-stacked{border-bottom:0;} 357 | .nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} 358 | .nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} 359 | .nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} 360 | .nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;} 361 | .nav-pills.nav-stacked>li>a{margin-bottom:3px;} 362 | .nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;} 363 | .nav-tabs .dropdown-menu,.nav-pills .dropdown-menu{margin-top:1px;border-width:1px;} 364 | .nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} 365 | .nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{border-top-color:#0088cc;border-bottom-color:#0088cc;margin-top:6px;} 366 | .nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580;} 367 | .nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;} 368 | .nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;} 369 | .nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;} 370 | .nav .open .caret,.nav .open.active .caret,.nav .open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);} 371 | .tabs-stacked .open>a:hover{border-color:#999999;} 372 | .tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";} 373 | .tabbable:after{clear:both;} 374 | .tab-content{display:table;width:100%;} 375 | .tabs-below .nav-tabs,.tabs-right .nav-tabs,.tabs-left .nav-tabs{border-bottom:0;} 376 | .tab-content>.tab-pane,.pill-content>.pill-pane{display:none;} 377 | .tab-content>.active,.pill-content>.active{display:block;} 378 | .tabs-below .nav-tabs{border-top:1px solid #ddd;} 379 | .tabs-below .nav-tabs>li{margin-top:-1px;margin-bottom:0;} 380 | .tabs-below .nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below .nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;} 381 | .tabs-below .nav-tabs .active>a,.tabs-below .nav-tabs .active>a:hover{border-color:transparent #ddd #ddd #ddd;} 382 | .tabs-left .nav-tabs>li,.tabs-right .nav-tabs>li{float:none;} 383 | .tabs-left .nav-tabs>li>a,.tabs-right .nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;} 384 | .tabs-left .nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;} 385 | .tabs-left .nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} 386 | .tabs-left .nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;} 387 | .tabs-left .nav-tabs .active>a,.tabs-left .nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;} 388 | .tabs-right .nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;} 389 | .tabs-right .nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} 390 | .tabs-right .nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;} 391 | .tabs-right .nav-tabs .active>a,.tabs-right .nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;} 392 | .navbar{*position:relative;*z-index:2;overflow:visible;margin-bottom:18px;} 393 | .navbar-inner{padding-left:20px;padding-right:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} 394 | .navbar .container{width:auto;} 395 | .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);}.btn-navbar:hover,.btn-navbar:active,.btn-navbar.active,.btn-navbar.disabled,.btn-navbar[disabled]{background-color:#222222;} 396 | .btn-navbar:active,.btn-navbar.active{background-color:#080808 \9;} 397 | .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);} 398 | .btn-navbar .icon-bar+.icon-bar{margin-top:3px;} 399 | .nav-collapse.collapse{height:auto;} 400 | .navbar{color:#999999;}.navbar .brand:hover{text-decoration:none;} 401 | .navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#ffffff;} 402 | .navbar .navbar-text{margin-bottom:0;line-height:40px;} 403 | .navbar .btn,.navbar .btn-group{margin-top:5px;} 404 | .navbar .btn-group .btn{margin-top:0;} 405 | .navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";} 406 | .navbar-form:after{clear:both;} 407 | .navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;} 408 | .navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;} 409 | .navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} 410 | .navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;} 411 | .navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;background-color:#626262;border:1px solid #151515;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query:-moz-placeholder{color:#cccccc;} 412 | .navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;} 413 | .navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;} 414 | .navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;} 415 | .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} 416 | .navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} 417 | .navbar-fixed-top{top:0;} 418 | .navbar-fixed-bottom{bottom:0;} 419 | .navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;} 420 | .navbar .nav.pull-right{float:right;} 421 | .navbar .nav>li{display:block;float:left;} 422 | .navbar .nav>li>a{float:none;padding:10px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} 423 | .navbar .nav>li>a:hover{background-color:transparent;color:#ffffff;text-decoration:none;} 424 | .navbar .nav .active>a,.navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#222222;} 425 | .navbar .divider-vertical{height:40px;width:1px;margin:0 9px;overflow:hidden;background-color:#222222;border-right:1px solid #333333;} 426 | .navbar .nav.pull-right{margin-left:10px;margin-right:0;} 427 | .navbar .dropdown-menu{margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;} 428 | .navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;} 429 | .navbar-fixed-bottom .dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;} 430 | .navbar-fixed-bottom .dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;} 431 | .navbar .nav .dropdown-toggle .caret,.navbar .nav .open.dropdown .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} 432 | .navbar .nav .active .caret{opacity:1;filter:alpha(opacity=100);} 433 | .navbar .nav .open>.dropdown-toggle,.navbar .nav .active>.dropdown-toggle,.navbar .nav .open.active>.dropdown-toggle{background-color:transparent;} 434 | .navbar .nav .active>.dropdown-toggle:hover{color:#ffffff;} 435 | .navbar .nav.pull-right .dropdown-menu,.navbar .nav .dropdown-menu.pull-right{left:auto;right:0;}.navbar .nav.pull-right .dropdown-menu:before,.navbar .nav .dropdown-menu.pull-right:before{left:auto;right:12px;} 436 | .navbar .nav.pull-right .dropdown-menu:after,.navbar .nav .dropdown-menu.pull-right:after{left:auto;right:13px;} 437 | .alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#c09853;} 438 | .alert-heading{color:inherit;} 439 | .alert .close{position:relative;top:-2px;right:-21px;line-height:18px;} 440 | .alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;} 441 | .alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;} 442 | .alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;} 443 | .alert-block{padding-top:14px;padding-bottom:14px;} 444 | .alert-block>p,.alert-block>ul{margin-bottom:0;} 445 | .alert-block p+p{margin-top:5px;} 446 | .hero-unit{padding:60px;margin-bottom:30px;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;} 447 | .hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit;} 448 | --------------------------------------------------------------------------------