diff --git a/.gitignore b/.gitignore index 4d91f46..ed84e8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# Github Pages +# GitHub Pages _site .sass-cache .jekyll-metadata @@ -15,3 +15,7 @@ _site # Output of the go coverage tool, specifically when used with LiteIDE *.out + +# Project specific +gui/data/ +gui/bin/ diff --git a/README.md b/README.md index 686f0a7..d880eb2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,19 @@ Also if you are developing your own client application or integrating one into y Getting Boulder up and running has quite a learning curve though and that is where **LabCA** comes in. It is a self-contained installation with a nice web GUI built on top of Boulder so you can quickly start using it. All regular management tasks can be done from the web interface. It is best installed in a Virtual Machine and uses Debian Linux as a base. -### NOTE +## Installation + +```sh +curl -sSL https://raw.githubusercontent.com/hakwerk/labca/master/install | bash +``` + +## Updating + +```sh +~labca/labca/install +``` + +## NOTE Although LabCA tries to be as robust as possible, use it at your own risk. If you depend on it, make sure that you know what you are doing! # License diff --git a/acme_tiny.py b/acme_tiny.py new file mode 100644 index 0000000..f919937 --- /dev/null +++ b/acme_tiny.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python +# Copyright Daniel Roesler, under MIT license, see LICENSE at github.com/diafygi/acme-tiny +import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging +try: + from urllib.request import urlopen, Request # Python 3 +except ImportError: + from urllib2 import urlopen, Request # Python 2 + +DEFAULT_CA = "http://LABCA_FQDN" # DEPRECATED! USE DEFAULT_DIRECTORY_URL INSTEAD +DEFAULT_DIRECTORY_URL = "http://LABCA_FQDN/directory" + +LOGGER = logging.getLogger(__name__) +LOGGER.addHandler(logging.StreamHandler()) +LOGGER.setLevel(logging.INFO) + +def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA, disable_check=False, directory_url=DEFAULT_DIRECTORY_URL, contact=None): + directory, acct_headers, alg, jwk = None, None, None, None # global variables + + # helper functions - base64 encode for jose spec + def _b64(b): + return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "") + + # helper function - run external commands + def _cmd(cmd_list, stdin=None, cmd_input=None, err_msg="Command Line Error"): + proc = subprocess.Popen(cmd_list, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate(cmd_input) + if proc.returncode != 0: + raise IOError("{0}\n{1}".format(err_msg, err)) + return out + + # helper function - make request and automatically parse json response + def _do_request(url, data=None, err_msg="Error", depth=0): + try: + resp = urlopen(Request(url, data=data, headers={"Content-Type": "application/jose+json", "User-Agent": "acme-tiny"})) + resp_data, code, headers = resp.read().decode("utf8"), resp.getcode(), resp.headers + resp_data = json.loads(resp_data) # try to parse json results + except ValueError: + pass # ignore json parsing errors + except IOError as e: + resp_data = e.read().decode("utf8") if hasattr(e, "read") else str(e) + code, headers = getattr(e, "code", None), {} + if depth < 100 and code == 400 and json.loads(resp_data)['type'] == "urn:ietf:params:acme:error:badNonce": + raise IndexError(resp_data) # allow 100 retrys for bad nonces + if code not in [200, 201, 204]: + raise ValueError("{0}:\nUrl: {1}\nData: {2}\nResponse Code: {3}\nResponse: {4}".format(err_msg, url, data, code, resp_data)) + return resp_data, code, headers + + # helper function - make signed requests + def _send_signed_request(url, payload, err_msg, depth=0): + payload64 = _b64(json.dumps(payload).encode('utf8')) + new_nonce = _do_request(directory['newNonce'])[2]['Replay-Nonce'] + protected = {"url": url, "alg": alg, "nonce": new_nonce} + protected.update({"jwk": jwk} if acct_headers is None else {"kid": acct_headers['Location']}) + protected64 = _b64(json.dumps(protected).encode('utf8')) + protected_input = "{0}.{1}".format(protected64, payload64).encode('utf8') + out = _cmd(["openssl", "dgst", "-sha256", "-sign", account_key], stdin=subprocess.PIPE, cmd_input=protected_input, err_msg="OpenSSL Error") + data = json.dumps({"protected": protected64, "payload": payload64, "signature": _b64(out)}) + try: + return _do_request(url, data=data.encode('utf8'), err_msg=err_msg, depth=depth) + except IndexError: # retry bad nonces (they raise IndexError) + return _send_signed_request(url, payload, err_msg, depth=(depth + 1)) + + # helper function - poll until complete + def _poll_until_not(url, pending_statuses, err_msg): + while True: + result, _, _ = _do_request(url, err_msg=err_msg) + if result['status'] in pending_statuses: + time.sleep(2) + continue + return result + + # parse account key to get public key + log.info("Parsing account key...") + out = _cmd(["openssl", "rsa", "-in", account_key, "-noout", "-text"], err_msg="OpenSSL Error") + pub_pattern = r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)" + pub_hex, pub_exp = re.search(pub_pattern, out.decode('utf8'), re.MULTILINE|re.DOTALL).groups() + pub_exp = "{0:x}".format(int(pub_exp)) + pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp + alg = "RS256" + jwk = { + "e": _b64(binascii.unhexlify(pub_exp.encode("utf-8"))), + "kty": "RSA", + "n": _b64(binascii.unhexlify(re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))), + } + accountkey_json = json.dumps(jwk, sort_keys=True, separators=(',', ':')) + thumbprint = _b64(hashlib.sha256(accountkey_json.encode('utf8')).digest()) + + # find domains + log.info("Parsing CSR...") + out = _cmd(["openssl", "req", "-in", csr, "-noout", "-text"], err_msg="Error loading {0}".format(csr)) + domains = set([]) + common_name = re.search(r"Subject:.*? CN\s?=\s?([^\s,;/]+)", out.decode('utf8')) + if common_name is not None: + domains.add(common_name.group(1)) + subject_alt_names = re.search(r"X509v3 Subject Alternative Name: \n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE|re.DOTALL) + if subject_alt_names is not None: + for san in subject_alt_names.group(1).split(", "): + if san.startswith("DNS:"): + domains.add(san[4:]) + log.info("Found domains: {0}".format(", ".join(domains))) + + # get the ACME directory of urls + log.info("Getting directory...") + directory_url = CA + "/directory" if CA != DEFAULT_CA else directory_url # backwards compatibility with deprecated CA kwarg + directory, _, _ = _do_request(directory_url, err_msg="Error getting directory") + log.info("Directory found!") + + # create account, update contact details (if any), and set the global key identifier + log.info("Registering account...") + reg_payload = {"termsOfServiceAgreed": True} + account, code, acct_headers = _send_signed_request(directory['newAccount'], reg_payload, "Error registering") + log.info("Registered!" if code == 201 else "Already registered!") + if contact is not None: + account, _, _ = _send_signed_request(acct_headers['Location'], {"contact": contact}, "Error updating contact details") + log.info("Updated contact details:\n{0}".format("\n".join(account['contact']))) + + # create a new order + log.info("Creating new order...") + order_payload = {"identifiers": [{"type": "dns", "value": d} for d in domains]} + order, _, order_headers = _send_signed_request(directory['newOrder'], order_payload, "Error creating new order") + log.info("Order created!") + + # get the authorizations that need to be completed + for auth_url in order['authorizations']: + authorization, _, _ = _do_request(auth_url, err_msg="Error getting challenges") + domain = authorization['identifier']['value'] + log.info("Verifying {0}...".format(domain)) + + # find the http-01 challenge and write the challenge file + challenge = [c for c in authorization['challenges'] if c['type'] == "http-01"][0] + token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token']) + keyauthorization = "{0}.{1}".format(token, thumbprint) + wellknown_path = os.path.join(acme_dir, token) + with open(wellknown_path, "w") as wellknown_file: + wellknown_file.write(keyauthorization) + + # check that the file is in place + try: + wellknown_url = "http://{0}/.well-known/acme-challenge/{1}".format(domain, token) + assert(disable_check or _do_request(wellknown_url)[0] == keyauthorization) + except (AssertionError, ValueError) as e: + os.remove(wellknown_path) + raise ValueError("Wrote file to {0}, but couldn't download {1}: {2}".format(wellknown_path, wellknown_url, e)) + + # say the challenge is done + _send_signed_request(challenge['url'], {}, "Error submitting challenges: {0}".format(domain)) + authorization = _poll_until_not(auth_url, ["pending"], "Error checking challenge status for {0}".format(domain)) + if authorization['status'] != "valid": + raise ValueError("Challenge did not pass for {0}: {1}".format(domain, authorization)) + log.info("{0} verified!".format(domain)) + + # finalize the order with the csr + log.info("Signing certificate...") + csr_der = _cmd(["openssl", "req", "-in", csr, "-outform", "DER"], err_msg="DER Export Error") + _send_signed_request(order['finalize'], {"csr": _b64(csr_der)}, "Error finalizing order") + + # poll the order to monitor when it's done + order = _poll_until_not(order_headers['Location'], ["pending", "processing"], "Error checking order status") + if order['status'] != "valid": + raise ValueError("Order failed: {0}".format(order)) + + # download the certificate + certificate_pem, _, _ = _do_request(order['certificate'], err_msg="Certificate download failed") + log.info("Certificate signed!") + return certificate_pem + +def main(argv=None): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent("""\ + This script automates the process of getting a signed TLS certificate from Let's Encrypt using + the ACME protocol. It will need to be run on your server and have access to your private + account key, so PLEASE READ THROUGH IT! It's only ~200 lines, so it won't take long. + + Example Usage: + python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /usr/share/nginx/html/.well-known/acme-challenge/ > signed_chain.crt + + Example Crontab Renewal (once per month): + 0 0 1 * * python /path/to/acme_tiny.py --account-key /path/to/account.key --csr /path/to/domain.csr --acme-dir /usr/share/nginx/html/.well-known/acme-challenge/ > /path/to/signed_chain.crt 2>> /var/log/acme_tiny.log + """) + ) + parser.add_argument("--account-key", required=True, help="path to your Let's Encrypt account private key") + parser.add_argument("--csr", required=True, help="path to your certificate signing request") + parser.add_argument("--acme-dir", required=True, help="path to the .well-known/acme-challenge/ directory") + parser.add_argument("--quiet", action="store_const", const=logging.ERROR, help="suppress output except for errors") + parser.add_argument("--disable-check", default=False, action="store_true", help="disable checking if the challenge file is hosted correctly before telling the CA") + parser.add_argument("--directory-url", default=DEFAULT_DIRECTORY_URL, help="certificate authority directory url, default is Let's Encrypt") + parser.add_argument("--ca", default=DEFAULT_CA, help="DEPRECATED! USE --directory-url INSTEAD!") + parser.add_argument("--contact", metavar="CONTACT", default=None, nargs="*", help="Contact details (e.g. mailto:aaa@bbb.com) for your account-key") + + args = parser.parse_args(argv) + LOGGER.setLevel(args.quiet or LOGGER.level) + signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, log=LOGGER, CA=args.ca, disable_check=args.disable_check, directory_url=args.directory_url, contact=args.contact) + sys.stdout.write(signed_crt) + +if __name__ == "__main__": # pragma: no cover + main(sys.argv[1:]) + + + +## echo "172.17.0.1 tessie.hakwerk.local" >> /etc/hosts +## openssl genrsa 4096 > account.key +## openssl genrsa 4096 > domain.key +## openssl req -new -sha256 -key domain.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:tessie.hakwerk.local")) > domain.csr +## python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /var/www/html/.well-known/acme-challenge/ > domain_chain.crt +## cp domain_chain.crt ~/boulder/test/ + +## docker exec -ti boulder_boulder_1 /bin/bash +## bin/checkocsp test/domain_chain.crt diff --git a/backup b/backup new file mode 100755 index 0000000..d2c30c7 --- /dev/null +++ b/backup @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -e + +NOW=$(date +%y%m%d%H%M%S) +CRON="" +if [ "$1" == "cron" ]; then + CRON="${1}_" +fi +BASE=${HOSTNAME}_${CRON}${NOW} +TMPDIR=/tmp/$BASE +mkdir -p $TMPDIR +mkdir -p /home/labca/backup + +cd /home/labca/boulder +docker-compose exec -T bmysql mysqldump boulder_sa_integration >$TMPDIR/boulder_sa_integration.sql + +cp -p /etc/nginx/ssl/*key* /etc/nginx/ssl/*cert.pem /etc/nginx/ssl/*.csr $TMPDIR/ + +cp -rp /home/labca/admin/data $TMPDIR/ + + +cd /tmp +tar czf /home/labca/backup/$BASE.tgz $BASE +rm -rf $TMPDIR + +# housekeeping +find /home/labca/backup -name "*_cron_*.tgz" -mtime +31 -exec rm -rf {} \; + +if [ "$1" != "cron" ]; then + echo /home/labca/backup/$BASE.tgz +fi diff --git a/commander b/commander new file mode 100755 index 0000000..414c252 --- /dev/null +++ b/commander @@ -0,0 +1,213 @@ +#!/usr/bin/env bash + +set -e + +LOGFILE=/home/labca/logs/commander.log + +err_report() { + echo "ERROR! On line $1 in commander script" +} + +trap 'err_report $LINENO' INT TERM ERR + +dn=$(dirname $0) +source "$dn/utils.sh" + +function wait_server() { + local url="$1" + + local status=0 + local cnt=0 + while [ $cnt -lt 20 ] && [ "$status" != "200" ]; do + status=$(curl -o /dev/null -sSL --head --write-out '%{http_code}\n' $url) + let cnt=$cnt+1 + if [ $cnt -lt 10 ] && [ "$status" != "200" ]; then + sleep 3 + fi + done +} + + +read txt +case $txt in +"trust-store") + cp /etc/nginx/ssl/labca_cert.pem /usr/local/share/ca-certificates/labca_cert.crt + cp ~labca/admin/data/root-ca.pem /usr/local/share/ca-certificates/root-ca.crt + update-ca-certificates &>>$LOGFILE + ;; +"docker-restart") + cd /home/labca/boulder + docker-compose stop &>>$LOGFILE + wait_down $PS_MYSQL &>>$LOGFILE + wait_down $PS_BHSM &>>$LOGFILE + wait_down $PS_LABCA &>>$LOGFILE + wait_down $PS_BOULDER &>>$LOGFILE + docker-compose rm -f bhsm &>>$LOGFILE + docker-compose up -d &>>$LOGFILE + wait_up $PS_MYSQL &>>$LOGFILE + wait_up $PS_BHSM &>>$LOGFILE + wait_up $PS_LABCA &>>$LOGFILE + wait_up $PS_BOULDER $PS_BOULDER_COUNT &>>$LOGFILE + ;; +"acme-request") + cd /etc/nginx/ssl + [ -e account.key ] || openssl genrsa 4096 > account.key + [ -e labca_key.pem ] || openssl genrsa 4096 > labca_key.pem + san=$(openssl x509 -noout -text -in labca_cert.pem | grep DNS:) + openssl req -new -sha256 -key labca_key.pem -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=$san")) > domain.csr + chown -R www-data:www-data * + url=$(grep 'DEFAULT_DIRECTORY_URL =' /home/labca/acme_tiny.py | sed -e 's/.*=[ ]*//' | sed -e 's/\"//g') + wait_server $url + /home/labca/labca/renew + ln -sf /home/labca/labca/cron_d /etc/cron.d/labca + ln -sf /home/labca/labca/logrotate_d /etc/logrotate.d/labca + ;; +"nginx-remove-redirect") + perl -i -p0e 's/\n # BEGIN temporary redirect\n location = \/ \{\n return 302 \/admin\/;\n }\n # END temporary redirect\n//igs' /etc/nginx/sites-available/labca + ;; +"nginx-reload") + service nginx reload + ;; +"nginx-restart") + service nginx restart + ;; +"log-cert") + tail -200 /etc/nginx/ssl/acme_tiny.log + exit 0 + ;; +"log-boulder") + cd /home/labca/boulder + docker-compose logs -f --no-color --tail=50 boulder + ;; +"log-audit") + cd /home/labca/boulder + docker-compose logs --no-color boulder | grep "\[AUDIT\]" | grep -v "grpc: parseServiceConfig error unmarshaling due to unexpected end of JSON input" | tail -50 + docker-compose logs -f --no-color --tail=0 boulder + ;; +"log-activity") + cd /home/labca/boulder + echo "GMT" + docker-compose logs --no-color boulder | grep "\[AUDIT\]" | grep -v "grpc: parseServiceConfig error unmarshaling due to unexpected end of JSON input" | tail -15 + exit 0 + ;; +"log-labca") + cd /home/labca/boulder + docker-compose logs -f --no-color --tail=50 labca + ;; +"log-web") + tail -f -n 50 /var/log/nginx/access.log + ;; +"log-weberr") + tail -200 /var/log/nginx/error.log + exit 0 + ;; +"log-components") + timezone=$(cat /etc/timezone) + nginx=$(ps -eo lstart,args | grep nginx | grep master | grep -v grep | cut -c 5-24) + svc=$(ps -eo lstart,args | grep tcpserver | grep sudo | grep -v grep | cut -c 5-24) + boulder=$(ps -eo lstart,args | grep bin/boulder-wfe2 | grep -v grep | cut -c 5-24) + labca=$(ps -eo lstart,args | grep -- "-host-port 3000" | grep -v grep | cut -c 5-24) + echo "$timezone|$nginx|$svc|$boulder|$labca" + exit 0 + ;; +"log-stats") + timezone=$(cat /etc/timezone) + uptime=$(uptime -s) + procs=$(ps -ef --no-headers | wc -l) + total=$(free -b --si | grep 'Mem:' | perl -p0e 's/.*?\s+(\d+)\s+.*/$1/') + avail=$(free -b --si | grep 'Mem:' | perl -p0e 's/.*\s+(\d+)$/$1/') + let used=$total-$avail + echo "$timezone|$uptime|$procs|$used|$avail" + exit 0 + ;; +"revoke-cert") + read serial + read reasonCode + cd /home/labca/boulder + docker-compose exec -T boulder bin/admin-revoker serial-revoke --config labca/config/admin-revoker.json $serial $reasonCode 2>&1 + ;; +"test-email") + read recipient + cd /home/labca/boulder + docker-compose exec -T boulder bin/mail-tester --config labca/config/expiration-mailer.json $recipient 2>&1 + ;; +"boulder-start") + cd /home/labca/boulder + docker-compose up -d bmysql + docker-compose up -d bhsm + docker-compose up -d boulder + wait_up $PS_MYSQL &>>$LOGFILE + wait_up $PS_BHSM &>>$LOGFILE + wait_up $PS_BOULDER $PS_BOULDER_COUNT &>>$LOGFILE + ;; +"boulder-stop") + cd /home/labca/boulder + docker-compose stop boulder + docker-compose stop bhsm + docker-compose stop bmysql + wait_down $PS_MYSQL &>>$LOGFILE + wait_down $PS_BHSM &>>$LOGFILE + wait_down $PS_BOULDER &>>$LOGFILE + ;; +"boulder-restart") + cd /home/labca/boulder + docker-compose stop boulder + docker-compose stop bhsm + docker-compose stop bmysql + wait_down $PS_MYSQL &>>$LOGFILE + wait_down $PS_BHSM &>>$LOGFILE + wait_down $PS_BOULDER &>>$LOGFILE + docker-compose up -d bmysql + docker-compose up -d bhsm + docker-compose up -d boulder + wait_up $PS_MYSQL &>>$LOGFILE + wait_up $PS_BHSM &>>$LOGFILE + wait_up $PS_BOULDER $PS_BOULDER_COUNT &>>$LOGFILE + ;; +"labca-restart") + cd /home/labca/boulder + docker-compose stop labca + wait_down $PS_LABCA &>>$LOGFILE + docker-compose up -d labca + wait_up $PS_LABCA &>>$LOGFILE + ;; +"svc-restart") + service labca stop + wait_down $PS_SERVICE &>>$LOGFILE + service labca start + wait_up $PS_SERVICE &>>$LOGFILE + ;; +"log-backups") + ls -1tr /home/labca/backup + exit 0 + ;; +"log-server-backup") + /home/labca/labca/backup + exit 0 + ;; +"backup-delete") + read backup + rm -f /home/labca/backup/$backup + ;; +"backup-restore") + read backup + /home/labca/labca/restore $backup + ;; +"server-restart") + reboot + ;; +"server-shutdown") + halt + ;; +*) + echo "Unknown command '$txt'. ERROR!" + exit 1 + ;; +esac + +echo "ok" + + +# TODO: +# upgrade the labca stuff + diff --git a/config_expiration-mailer.patch b/config_expiration-mailer.patch new file mode 100644 index 0000000..1222328 --- /dev/null +++ b/config_expiration-mailer.patch @@ -0,0 +1,27 @@ +diff --git a/test/config/expiration-mailer.json b/test/config/expiration-mailer.json +index 86e8a43..7988b1b 100644 +--- a/test/config/expiration-mailer.json ++++ b/test/config/expiration-mailer.json +@@ -12,6 +12,11 @@ + "nagCheckInterval": "24h", + "emailTemplate": "labca/example-expiration-template", + "debugAddr": ":8008", ++ "dnsTries": 3, ++ "dnsResolvers": [ ++ "127.0.0.1:8053", ++ "127.0.0.1:8054" ++ ], + "tls": { + "caCertFile": "labca/grpc-creds/minica.pem", + "certFile": "labca/grpc-creds/expiration-mailer.boulder/cert.pem", +@@ -28,5 +33,10 @@ + "syslog": { + "stdoutlevel": 6, + "sysloglevel": 4 ++ }, ++ ++ "common": { ++ "dnsTimeout": "3s", ++ "dnsAllowLoopbackAddresses": true + } + } diff --git a/cron_d b/cron_d new file mode 100644 index 0000000..f8f281e --- /dev/null +++ b/cron_d @@ -0,0 +1,7 @@ +# /etc/cron.d/labca: crontab entries for the LabCA application +SHELL=/bin/bash +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +1 6 * * Mon root /home/labca/labca/backup cron +1 7 * * * root /home/labca/labca/mailer +5 7 * * * root /home/labca/labca/smartrenew diff --git a/docker-compose.patch b/docker-compose.patch new file mode 100644 index 0000000..cac092a --- /dev/null +++ b/docker-compose.patch @@ -0,0 +1,71 @@ +diff --git a/docker-compose.yml b/docker-compose.yml +index b0e2c1f1..be6edbda 100644 +--- a/docker-compose.yml ++++ b/docker-compose.yml +@@ -6,9 +6,10 @@ services: + environment: + FAKE_DNS: 127.0.0.1 + PKCS11_PROXY_SOCKET: tcp://boulder-hsm:5657 +- BOULDER_CONFIG_DIR: test/config ++ BOULDER_CONFIG_DIR: labca/config + volumes: + - .:/go/src/github.com/letsencrypt/boulder ++ - /home/labca/boulder_labca:/go/src/github.com/letsencrypt/boulder/labca + - ./.gocache:/root/.cache/go-build + networks: + bluenet: +@@ -47,8 +48,9 @@ services: + depends_on: + - bhsm + - bmysql +- entrypoint: test/entrypoint.sh ++ entrypoint: labca/entrypoint.sh + working_dir: /go/src/github.com/letsencrypt/boulder ++ restart: always + bhsm: + # To minimize fetching this should be the same version used above + image: letsencrypt/boulder-tools-go${TRAVIS_GO_VERSION:-1.11.1}:2018-10-18 +@@ -61,8 +63,11 @@ services: + bluenet: + aliases: + - boulder-hsm ++ restart: always + bmysql: + image: mariadb:10.3 ++ volumes: ++ - dbdata:/var/lib/mysql + networks: + bluenet: + aliases: +@@ -72,16 +77,27 @@ services: + command: mysqld --bind-address=0.0.0.0 + logging: + driver: none +- netaccess: ++ restart: always ++ labca: + image: letsencrypt/boulder-tools-go${TRAVIS_GO_VERSION:-1.11.1}:2018-10-18 + networks: + - bluenet + volumes: +- - .:/go/src/github.com/letsencrypt/boulder +- working_dir: /go/src/github.com/letsencrypt/boulder +- entrypoint: test/entrypoint-netaccess.sh ++ - /home/labca/admin:/go/src/labca ++ - ./.gocache:/root/.cache/go-build ++ - /var/www/html:/wwwstatic ++ - .:/boulder ++ - /home/labca/boulder_labca:/boulder/labca ++ ports: ++ - 3000:3000 + depends_on: + - bmysql ++ working_dir: /go/src/labca ++ command: ./setup.sh ++ restart: always ++ ++volumes: ++ dbdata: + + networks: + bluenet: diff --git a/expiration-mailer_main.patch b/expiration-mailer_main.patch new file mode 100644 index 0000000..743ae2b --- /dev/null +++ b/expiration-mailer_main.patch @@ -0,0 +1,82 @@ +diff --git a/cmd/expiration-mailer/main.go b/cmd/expiration-mailer/main.go +index de4af4a0..b58405ef 100644 +--- a/cmd/expiration-mailer/main.go ++++ b/cmd/expiration-mailer/main.go +@@ -21,6 +21,7 @@ import ( + "github.com/jmhodges/clock" + "gopkg.in/go-gorp/gorp.v2" + ++ "github.com/letsencrypt/boulder/bdns" + "github.com/letsencrypt/boulder/cmd" + "github.com/letsencrypt/boulder/core" + "github.com/letsencrypt/boulder/features" +@@ -35,7 +36,7 @@ import ( + + const ( + defaultNagCheckInterval = 24 * time.Hour +- defaultExpirationSubject = "Let's Encrypt certificate expiration notice for domain {{.ExpirationSubject}}" ++ defaultExpirationSubject = "LabCA certificate expiration notice for domain {{.ExpirationSubject}}" + ) + + type regStore interface { +@@ -376,6 +377,9 @@ type config struct { + TLS cmd.TLSConfig + SAService *cmd.GRPCClientConfig + ++ DNSTries int ++ DNSResolvers []string ++ + // Path to a file containing a list of trusted root certificates for use + // during the SMTP connection (as opposed to the gRPC connections). + SMTPTrustedRootFile string +@@ -384,6 +388,12 @@ type config struct { + } + + Syslog cmd.SyslogConfig ++ ++ Common struct { ++ DNSResolver string ++ DNSTimeout string ++ DNSAllowLoopbackAddresses bool ++ } + } + + func initStats(scope metrics.Scope) mailerStats { +@@ -485,6 +495,29 @@ func main() { + cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA") + sac := bgrpc.NewStorageAuthorityClient(sapb.NewStorageAuthorityClient(conn)) + ++ dnsTimeout, err := time.ParseDuration(c.Common.DNSTimeout) ++ cmd.FailOnError(err, "Couldn't parse DNS timeout") ++ dnsTries := c.Mailer.DNSTries ++ if dnsTries < 1 { ++ dnsTries = 1 ++ } ++ var resolver bdns.DNSClient ++ if len(c.Common.DNSResolver) != 0 { ++ c.Mailer.DNSResolvers = append(c.Mailer.DNSResolvers, c.Common.DNSResolver) ++ } ++ if !c.Common.DNSAllowLoopbackAddresses { ++ r := bdns.NewDNSClientImpl( ++ dnsTimeout, ++ c.Mailer.DNSResolvers, ++ scope, ++ clk, ++ dnsTries) ++ resolver = r ++ } else { ++ r := bdns.NewTestDNSClientImpl(dnsTimeout, c.Mailer.DNSResolvers, scope, clk, dnsTries) ++ resolver = r ++ } ++ + var smtpRoots *x509.CertPool + if c.Mailer.SMTPTrustedRootFile != "" { + pem, err := ioutil.ReadFile(c.Mailer.SMTPTrustedRootFile) +@@ -520,6 +553,7 @@ func main() { + c.Mailer.Username, + smtpPassword, + smtpRoots, ++ resolver, + *fromAddress, + logger, + scope, diff --git a/gui/acme.go b/gui/acme.go new file mode 100644 index 0000000..eee6ac9 --- /dev/null +++ b/gui/acme.go @@ -0,0 +1,733 @@ +package main + +import ( + "database/sql" + "html/template" + "net" + "net/http" + "strconv" +) + +type BaseList struct { + Title string + TableClass string + Header []template.HTML +} + +type Account struct { + Id int + Status string + Contact string + Agreement string + InitialIp net.IP + CreatedAt string +} + +type AccountList struct { + BaseList + Rows []Account +} + +func GetAccounts(w http.ResponseWriter, r *http.Request) (AccountList, error){ + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountList{}, err + } + + defer db.Close() + + rows, err := db.Query("SELECT id, status, contact, agreement, initialIP, createdAt FROM registrations") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountList{}, err + } + + Accounts := AccountList{ + BaseList: BaseList{ + Title: "Accounts", + TableClass: "accounts_list", + Header: []template.HTML{"ID", "Status", "Contact", "Agreement", "Initial IP", "Created"}, + }, + Rows: []Account{}, + } + + for rows.Next() { + row := Account{} + err = rows.Scan(&row.Id, &row.Status, &row.Contact, &row.Agreement, &row.InitialIp, &row.CreatedAt) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountList{}, err + } + Accounts.Rows = append(Accounts.Rows, row) + } + + return Accounts, nil +} + +type Order struct { + Id int + RegistrationId int + Expires string + CertSerial string + BeganProc bool + Created string +} + +type OrderList struct { + BaseList + Rows []Order +} + +type NameValue struct { + Name string + Value string +} + +type BaseShow struct { + Title string + TableClass string + Rows []NameValue + Links []NameValHtml + Extra []template.HTML +} + +type AccountShow struct { + BaseShow + Related []CertificateList + Related2 []OrderList +} + +func GetAccount(w http.ResponseWriter, r *http.Request, id int) (AccountShow, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountShow{}, err + } + + defer db.Close() + + rows, err := db.Query("SELECT c.id, c.registrationID, c.serial, CASE WHEN cs.notAfter < NOW() THEN 'expired' ELSE cs.status END AS status, c.issued, c.expires FROM certificates c JOIN certificateStatus cs ON cs.id = c.id WHERE registrationID=?", strconv.Itoa(id)) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountShow{}, err + } + + Certificates := CertificateList{ + BaseList: BaseList{ + Title: "Certificates", + TableClass: "rel_certificates_list", + Header: []template.HTML{"ID", "Account ID", "Serial", "Status", "Issued", "Expires"}, + }, + Rows: []Certificate{}, + } + + for rows.Next() { + row := Certificate{} + err = rows.Scan(&row.Id, &row.RegistrationId, &row.Serial, &row.Status, &row.Issued, &row.Expires) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountShow{}, err + } + Certificates.Rows = append(Certificates.Rows, row) + } + + rows, err = db.Query("SELECT id, registrationID, expires, certificateSerial, beganProcessing, created FROM orders WHERE registrationID=?", strconv.Itoa(id)) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountShow{}, err + } + + Orders := OrderList{ + BaseList: BaseList{ + Title: "Orders", + TableClass: "rel_orders_list", + Header: []template.HTML{"ID", "Account ID", "Expires", "Certificate Serial", "Began Processing?", "Created"}, + }, + Rows: []Order{}, + } + + for rows.Next() { + row := Order{} + err = rows.Scan(&row.Id, &row.RegistrationId, &row.Expires, &row.CertSerial, &row.BeganProc, &row.Created) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountShow{}, err + } + Orders.Rows = append(Orders.Rows, row) + } + + rows, err = db.Query("SELECT id, status, contact, agreement, initialIP, createdAt FROM registrations WHERE id=?", strconv.Itoa(id)) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountShow{}, err + } + + AccountDetails := AccountShow{ + BaseShow: BaseShow{ + Title: "Account", + TableClass: "account_show", + Rows: []NameValue{}, + }, + Related: []CertificateList{Certificates}, + Related2: []OrderList{Orders}, + } + + for rows.Next() { + row := Account{} + err = rows.Scan(&row.Id, &row.Status, &row.Contact, &row.Agreement, &row.InitialIp, &row.CreatedAt) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AccountShow{}, err + } + AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)}) + AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Status", row.Status}) + AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Contact", row.Contact}) + AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Agreement", row.Agreement}) + AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Initial IP", row.InitialIp.String()}) + AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Created At", row.CreatedAt}) + } + + return AccountDetails, nil +} + +func GetOrders(w http.ResponseWriter, r *http.Request) (OrderList, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return OrderList{}, err + } + + defer db.Close() + + rows, err := db.Query("SELECT id, registrationID, expires, certificateSerial, beganProcessing, created FROM orders") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return OrderList{}, err + } + + Orders := OrderList{ + BaseList: BaseList{ + Title: "Orders", + TableClass: "orders_list", + Header: []template.HTML{"ID", "Account ID", "Expires", "Certificate Serial", "Began Processing?", "Created"}, + }, + Rows: []Order{}, + } + + for rows.Next() { + row := Order{} + err = rows.Scan(&row.Id, &row.RegistrationId, &row.Expires, &row.CertSerial, &row.BeganProc, &row.Created) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return OrderList{}, err + } + Orders.Rows = append(Orders.Rows, row) + } + + return Orders, nil +} + +type Auth struct { + Id string + Identifier string + RegistrationId int + Status string + Expires string + Combinations string +} + +type AuthList struct { + BaseList + Rows []Auth +} + +type OrderShow struct { + BaseShow + Related []AuthList + Related2 []BaseList +} + +func GetOrder(w http.ResponseWriter, r *http.Request, id int) (OrderShow, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return OrderShow{}, err + } + + defer db.Close() + + partial := "SELECT id, identifier, registrationID, status, expires, combinations FROM " + where := " WHERE id IN (SELECT authzID FROM orderToAuthz WHERE orderID=?)" + rows, err := db.Query(partial+"authz"+where+" UNION "+partial+"pendingAuthorizations"+where, strconv.Itoa(id), strconv.Itoa(id)) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return OrderShow{}, err + } + + Authz := AuthList{ + BaseList: BaseList{ + Title: "Authorizations", + TableClass: "rel_authz_list", + Header: []template.HTML{"ID", "Identifier", "Account ID", "Status", "Expires", "Combinations"}, + }, + Rows: []Auth{}, + } + + for rows.Next() { + row := Auth{} + err = rows.Scan(&row.Id, &row.Identifier, &row.RegistrationId, &row.Status, &row.Expires, &row.Combinations) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return OrderShow{}, err + } + Authz.Rows = append(Authz.Rows, row) + } + + rows, err = db.Query("SELECT id, registrationID, expires, certificateSerial, beganProcessing, created FROM orders WHERE id=?", strconv.Itoa(id)) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return OrderShow{}, err + } + + OrderDetails := OrderShow{ + BaseShow: BaseShow{ + Title: "Order", + TableClass: "order_show", + Rows: []NameValue{}, + Links: []NameValHtml{}, + }, + Related: []AuthList{Authz}, + } + + for rows.Next() { + row := Order{} + err = rows.Scan(&row.Id, &row.RegistrationId, &row.Expires, &row.CertSerial, &row.BeganProc, &row.Created) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return OrderShow{}, err + } + OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)}) + OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"Expires", row.Expires}) + v := "false" + if row.BeganProc { + v = "true" + } + OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"Began Processing?", v}) + OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"Created", row.Created}) + + OrderDetails.Links = append(OrderDetails.Links, NameValHtml{"Certificate", template.HTML("" + row.CertSerial + "")}) + OrderDetails.Links = append(OrderDetails.Links, NameValHtml{"Account", template.HTML("" + strconv.Itoa(row.RegistrationId) + "")}) + } + + return OrderDetails, nil +} + +func GetAuthz(w http.ResponseWriter, r *http.Request) (AuthList, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AuthList{}, err + } + + defer db.Close() + + rows, err := db.Query("SELECT id, identifier, registrationID, status, expires, combinations FROM authz UNION SELECT id, identifier, registrationID, status, expires, combinations FROM pendingAuthorizations") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AuthList{}, err + } + + Authz := AuthList{ + BaseList: BaseList{ + Title: "Authorizations", + TableClass: "authz_list", + Header: []template.HTML{"ID", "Identifier", "Account ID", "Status", "Expires", "Combinations"}, + }, + Rows: []Auth{}, + } + + for rows.Next() { + row := Auth{} + err = rows.Scan(&row.Id, &row.Identifier, &row.RegistrationId, &row.Status, &row.Expires, &row.Combinations) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AuthList{}, err + } + Authz.Rows = append(Authz.Rows, row) + } + + return Authz, nil +} + +type Challenge struct { + Id int + AuthId string + Type string + Status string + Validated string + Token string + KeyAuth string +} + +type ChallengeList struct { + BaseList + Rows []Challenge +} + +type NameValHtml struct { + Name string + Value template.HTML +} + +type AuthShow struct { + BaseShow + Related []ChallengeList + Related2 []BaseList +} + +func GetAuth(w http.ResponseWriter, r *http.Request, id string) (AuthShow, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AuthShow{}, err + } + + defer db.Close() + + rows, err := db.Query("SELECT id, authorizationID, type, status, validated, token, keyAuthorization FROM challenges WHERE authorizationID=?", id) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AuthShow{}, err + } + + Challenges := ChallengeList{ + BaseList: BaseList{ + Title: "Challenges", + TableClass: "rel_challenges_list", + Header: []template.HTML{"ID", "Authorization ID", "Type", "Status", "Validated", "Token", "Key Authorization"}, + }, + Rows: []Challenge{}, + } + + for rows.Next() { + row := Challenge{} + err = rows.Scan(&row.Id, &row.AuthId, &row.Type, &row.Status, &row.Validated, &row.Token, &row.KeyAuth) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AuthShow{}, err + } + Challenges.Rows = append(Challenges.Rows, row) + } + + partial := "SELECT id, identifier, registrationID, status, expires, combinations FROM " + where := " WHERE id IN (SELECT authzID FROM orderToAuthz WHERE id=?)" + rows, err = db.Query(partial+"authz"+where+" UNION "+partial+"pendingAuthorizations"+where, id, id) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AuthShow{}, err + } + + AuthDetails := AuthShow{ + BaseShow: BaseShow{ + Title: "Authorization", + TableClass: "auth_show", + Rows: []NameValue{}, + Links: []NameValHtml{}, + }, + Related: []ChallengeList{Challenges}, + } + + for rows.Next() { + row := Auth{} + err = rows.Scan(&row.Id, &row.Identifier, &row.RegistrationId, &row.Status, &row.Expires, &row.Combinations) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return AuthShow{}, err + } + AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"ID", row.Id}) + AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Identifier", row.Identifier}) + AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Status", row.Status}) + AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Expires", row.Expires}) + AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Combinations", row.Combinations}) + + Link := NameValHtml{"Account", template.HTML("" + strconv.Itoa(row.RegistrationId) + "")} + AuthDetails.Links = append(AuthDetails.Links, Link) + } + + return AuthDetails, nil +} + +func GetChallenges(w http.ResponseWriter, r *http.Request) (ChallengeList, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return ChallengeList{}, err + } + + defer db.Close() + + rows, err := db.Query("SELECT id, authorizationID, type, status, validated, token, keyAuthorization FROM challenges") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return ChallengeList{}, err + } + + Challenges := ChallengeList{ + BaseList: BaseList{ + Title: "Challenges", + TableClass: "challenges_list", + Header: []template.HTML{"ID", "Authorization ID", "Type", "Status", "Validated", "Token", "Key Authorization"}, + }, + Rows: []Challenge{}, + } + + for rows.Next() { + row := Challenge{} + err = rows.Scan(&row.Id, &row.AuthId, &row.Type, &row.Status, &row.Validated, &row.Token, &row.KeyAuth) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return ChallengeList{}, err + } + Challenges.Rows = append(Challenges.Rows, row) + } + + return Challenges, nil +} + +type ChallengeShow struct { + BaseShow + Related []ChallengeList + Related2 []BaseList +} + +func GetChallenge(w http.ResponseWriter, r *http.Request, id int) (ChallengeShow, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return ChallengeShow{}, err + } + + defer db.Close() + + rows, err := db.Query("SELECT id, authorizationID, type, status, validated, token, keyAuthorization FROM challenges WHERE id=?", id) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return ChallengeShow{}, err + } + + ChallengeDetails := ChallengeShow{ + BaseShow: BaseShow{ + Title: "Challenge", + TableClass: "challenge_show", + Rows: []NameValue{}, + Links: []NameValHtml{}, + }, + Related: []ChallengeList{}, + } + + for rows.Next() { + row := Challenge{} + err = rows.Scan(&row.Id, &row.AuthId, &row.Type, &row.Status, &row.Validated, &row.Token, &row.KeyAuth) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return ChallengeShow{}, err + } + ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)}) + ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Type", row.Type}) + ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Status", row.Status}) + ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Validated", row.Validated}) + ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Token", row.Token}) + ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"KeyAuth", row.KeyAuth}) + + Link := NameValHtml{"Authorization", template.HTML("" + row.AuthId + "")} + ChallengeDetails.Links = append(ChallengeDetails.Links, Link) + } + + return ChallengeDetails, nil +} + +type Certificate struct { + Id int + RegistrationId int + Serial string + Status string + Issued string + Expires string +} + +type CertificateList struct { + BaseList + Rows []Certificate +} + +func GetCertificates(w http.ResponseWriter, r *http.Request) (CertificateList, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return CertificateList{}, err + } + + defer db.Close() + + where := "" + if r.URL.Query().Get("active") != "" { + where = " WHERE cs.revokedDate='0000-00-00 00:00:00' AND cs.notAfter >= NOW()"; + } else if r.URL.Query().Get("expired") != "" { + where = " WHERE cs.revokedDate='0000-00-00 00:00:00' AND cs.notAfter < NOW()"; + } else if r.URL.Query().Get("revoked") != "" { + where = " WHERE cs.revokedDate<>'0000-00-00 00:00:00'"; + } + + rows, err := db.Query("SELECT c.id, c.registrationID, c.serial, CASE WHEN cs.notAfter < NOW() THEN 'expired' ELSE cs.status END AS status, c.issued, c.expires FROM certificates c JOIN certificateStatus cs ON cs.id = c.id" + where) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return CertificateList{}, err + } + + Certificates := CertificateList{ + BaseList: BaseList{ + Title: "Certificates", + TableClass: "certificates_list", + Header: []template.HTML{"ID", "Account ID", "Serial", "Status", "Issued", "Expires"}, + }, + Rows: []Certificate{}, + } + + for rows.Next() { + row := Certificate{} + err = rows.Scan(&row.Id, &row.RegistrationId, &row.Serial, &row.Status, &row.Issued, &row.Expires) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return CertificateList{}, err + } + Certificates.Rows = append(Certificates.Rows, row) + } + + return Certificates, nil +} + +type CertificateShow struct { + BaseShow + Related []BaseList + Related2 []BaseList +} + +type CertificateExtra struct { + Id int + RegistrationId int + Serial string + Digest string + Issued string + Expires string + SubscriberApproved bool + Status string + OCSPLastUpdate string + Revoked string + RevokedReason int + LastNagSent string + NotAfter string + IsExpired bool +} + +func GetCertificate(w http.ResponseWriter, r *http.Request, id int, serial string) (CertificateShow, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return CertificateShow{}, err + } + + defer db.Close() + + var rows *sql.Rows + selectWhere := "SELECT c.id, c.registrationID, c.serial, c.digest, c.issued, c.expires, cs.subscriberApproved, CASE WHEN cs.notAfter < NOW() THEN 'expired' ELSE cs.status END AS status, cs.ocspLastUpdated, cs.revokedDate, cs.revokedReason, cs.lastExpirationNagSent, cs.notAfter, cs.isExpired FROM certificates c JOIN certificateStatus cs ON cs.id = c.id WHERE " + + if serial != "" { + rows, err = db.Query(selectWhere+"c.serial=?", serial) + } else { + rows, err = db.Query(selectWhere+"c.id=?", id) + } + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return CertificateShow{}, err + } + + CertificateDetails := CertificateShow{ + BaseShow: BaseShow{ + Title: "Certificate", + TableClass: "certificate_show", + Rows: []NameValue{}, + Links: []NameValHtml{}, + }, + } + + for rows.Next() { + row := CertificateExtra{} + err = rows.Scan(&row.Id, &row.RegistrationId, &row.Serial, &row.Digest, &row.Issued, &row.Expires, &row.SubscriberApproved, &row.Status, &row.OCSPLastUpdate, &row.Revoked, &row.RevokedReason, &row.LastNagSent, &row.NotAfter, &row.IsExpired) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return CertificateShow{}, err + } + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)}) + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Serial", row.Serial}) + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Digest", row.Digest}) + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Issued", row.Issued}) + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Expires", row.Expires}) + v := "false" + if row.SubscriberApproved { + v = "true" + } + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Subscriber Approved", v}) + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Status", row.Status}) + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"OCSP Last Update", row.OCSPLastUpdate}) + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Revoked", row.Revoked}) + reasonText := "" + switch row.RevokedReason { + case 0: + if row.Revoked != "0000-00-00 00:00:00" { + reasonText = " - Unspecified" + } + case 1: + reasonText = " - Key Compromise" + case 2: + reasonText = " - CA Compromise" + case 3: + reasonText = " - Affiliation Changed" + case 4: + reasonText = " - Superseded" + case 5: + reasonText = " - Cessation Of Operation" + case 6: + reasonText = " - Certificate Hold" + case 8: + reasonText = " - Remove From CRL" + case 9: + reasonText = " - Privilege Withdrawn" + case 10: + reasonText = " - AA Compromise" + } + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Revoked Reason", strconv.Itoa(row.RevokedReason) + reasonText}) + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Last Expiration Nag Sent", row.LastNagSent}) + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Not After", row.NotAfter}) + v = "false" + if row.IsExpired { + v = "true" + } + CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Is Expired", v}) + + Link := NameValHtml{"Account", template.HTML("" + strconv.Itoa(row.RegistrationId) + "")} + CertificateDetails.Links = append(CertificateDetails.Links, Link) + + if row.Revoked == "0000-00-00 00:00:00" { + revokeHtml, err := tmpls.RenderSingle("views/revoke-partial.tmpl", struct{ Serial string }{Serial: row.Serial}) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return CertificateShow{}, err + } + CertificateDetails.Extra = append(CertificateDetails.Extra, template.HTML(revokeHtml)) + } + } + + return CertificateDetails, nil +} + diff --git a/gui/apply b/gui/apply new file mode 100755 index 0000000..789c65f --- /dev/null +++ b/gui/apply @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -e + +PKI_PWD=$(pwd) +export PKI_ROOT_CERT_BASE="$PKI_PWD/$PKI_ROOT_CERT_BASE" +export PKI_INT_CERT_BASE="$PKI_PWD/$PKI_INT_CERT_BASE" + +cd /wwwstatic + +$PKI_PWD/apply-nginx + +cp $PKI_ROOT_CERT_BASE.crl crl/ +cp $PKI_ROOT_CERT_BASE.pem certs/ +cp $PKI_ROOT_CERT_BASE.der certs/ +cp $PKI_INT_CERT_BASE.pem certs/ +cp $PKI_INT_CERT_BASE.der certs/ + +chown -R www-data:www-data . + + +cd /boulder/labca +$PKI_PWD/apply-boulder diff --git a/gui/apply-boulder b/gui/apply-boulder new file mode 100755 index 0000000..da2358d --- /dev/null +++ b/gui/apply-boulder @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -e + +perl -i -p0e "s/(\"dnsResolvers\": \[\n).*?(\s+\],)/\1 \"$PKI_DNS\"\2/igs" config/ra.json +perl -i -p0e "s/(\"dnsResolvers\": \[\n).*?(\s+\],)/\1 \"$PKI_DNS\"\2/igs" config/va.json +perl -i -p0e "s/(\"dnsResolvers\": \[\n).*?(\s+\],)/\1 \"$PKI_DNS\"\2/igs" config/expiration-mailer.json +sed -i -e "s/\"issuerDomain\": \".*\"/\"issuerDomain\": \"$PKI_DOMAIN\"/" config/va.json +sed -i -e "s/\"directoryCAAIdentity\": \".*\"/\"directoryCAAIdentity\": \"$PKI_DOMAIN\"/" config/wfe.json +sed -i -e "s/\"directoryCAAIdentity\": \".*\"/\"directoryCAAIdentity\": \"$PKI_DOMAIN\"/" config/wfe2.json + +[ -e ../test/hostname-policy.json ] && cp ../test/hostname-policy.json ./ || true +[ -e ../boulder/test/hostname-policy.json ] && cp ../boulder/test/hostname-policy.json ./ || true +if [ "$PKI_DOMAIN_MODE" == "lockdown" ]; then + sed -i -e "s/ \]$/ \],\n \"Lockdown\": \[\n \"$PKI_LOCKDOWN_DOMAINS\"\n&/" hostname-policy.json +fi +if [ "$PKI_DOMAIN_MODE" == "whitelist" ]; then + sed -i -e "s/ \]$/ \],\n \"Whitelist\": \[\n \"$PKI_WHITELIST_DOMAINS\"\n&/" hostname-policy.json +fi + +sed -i -e "s/\"server\": \".*\"/\"server\": \"$PKI_EMAIL_SERVER\"/" config/expiration-mailer.json +sed -i -e "s/\"port\": \".*\"/\"port\": \"$PKI_EMAIL_PORT\"/" config/expiration-mailer.json +sed -i -e "s/\"username\": \".*\"/\"username\": \"$PKI_EMAIL_USER\"/" config/expiration-mailer.json +sed -i -e "s/\"from\": \".*\"/\"from\": \"$PKI_EMAIL_FROM\"/" config/expiration-mailer.json + +if [ "$PKI_EMAIL_PASS" != "" ]; then + sed -i -e "s/.*/$PKI_EMAIL_PASS/" secrets/smtp_password +fi + +rm -f test-ca.key +rm -f test-ca.key.der +rm -f test-ca.pem +rm -f test-ca.der +rm -f test-root.key +rm -f test-root.key.der +rm -f test-root.pem + +cp -p $PKI_INT_CERT_BASE.key test-ca.key +cp -p $PKI_INT_CERT_BASE.key.der test-ca.key.der +cp -p $PKI_INT_CERT_BASE.pem test-ca.pem +cp -p $PKI_ROOT_CERT_BASE.key test-root.key +cp -p $PKI_ROOT_CERT_BASE.key.der test-root.key.der +cp -p $PKI_ROOT_CERT_BASE.pem test-root.pem + +chown -R `ls -l rate-limit-policies.yml | cut -d" " -f 3,4 | sed 's/ /:/g'` . diff --git a/gui/apply-nginx b/gui/apply-nginx new file mode 100755 index 0000000..78b161d --- /dev/null +++ b/gui/apply-nginx @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -e + +PKI_ROOT_DN=$(openssl x509 -noout -in $PKI_ROOT_CERT_BASE.pem -subject | sed -e "s/subject= //") +sed -i -e "s|\[PKI_ROOT_DN\]|$PKI_ROOT_DN|g" certs/index.html +PKI_ROOT_VALIDITY="$(openssl x509 -noout -in $PKI_ROOT_CERT_BASE.pem -startdate | sed -e "s/.*=/Not Before: /")
$(openssl x509 -noout -in $PKI_ROOT_CERT_BASE.pem -enddate | sed -e "s/.*=/Not After: /")" +sed -i -e "s|\[PKI_ROOT_VALIDITY\]|$PKI_ROOT_VALIDITY|g" certs/index.html +PKI_INT_DN=$(openssl x509 -noout -in $PKI_INT_CERT_BASE.pem -subject | sed -e "s/subject= //") +sed -i -e "s|\[PKI_INT_DN\]|$PKI_INT_DN|g" certs/index.html +PKI_INT_VALIDITY="$(openssl x509 -noout -in $PKI_INT_CERT_BASE.pem -startdate | sed -e "s/.*=/Not Before: /")
$(openssl x509 -noout -in $PKI_INT_CERT_BASE.pem -enddate | sed -e "s/.*=/Not After: /")" +sed -i -e "s|\[PKI_INT_VALIDITY\]|$PKI_INT_VALIDITY|g" certs/index.html + +sed -i -e "s|\[PKI_COMPANY_NAME\]|$PKI_DEFAULT_O|g" cps/index.html +sed -i -e "s|\[PKI_ROOT_DN\]|$PKI_ROOT_DN|g" cps/index.html +PKI_ROOT_FINGERPRINT="$(openssl x509 -noout -in $PKI_ROOT_CERT_BASE.pem -fingerprint | sed -e "s/.*=//" | sed -e "s/.\{21\}/&\\\n/g")" +sed -i -e "s|\[PKI_ROOT_FINGERPRINT\]|$PKI_ROOT_FINGERPRINT|g" cps/index.html +sed -i -e "s|\[PKI_ROOT_VALIDITY\]|$PKI_ROOT_VALIDITY|g" cps/index.html + +sed -i -e "s|\[PKI_COMPANY_NAME\]|$PKI_DEFAULT_O|g" terms/v1.html + +chown -R www-data:www-data . diff --git a/gui/certificate.go b/gui/certificate.go new file mode 100644 index 0000000..e92b4e4 --- /dev/null +++ b/gui/certificate.go @@ -0,0 +1,485 @@ +package main + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "os" + "os/exec" + "path/filepath" + "runtime/debug" + "strings" +) + +type CertificateInfo struct { + IsRoot bool + KeyTypes map[string]string + KeyType string + CreateType string + + Country string + Organization string + OrgUnit string + CommonName string + + ImportFile multipart.File + ImportHandler *multipart.FileHeader + ImportPwd string + + Key string + Passphrase string + Certificate string + + RequestBase string + Errors map[string]string +} + +func (ci *CertificateInfo) Initialize() { + ci.Errors = make(map[string]string) + + ci.KeyTypes = make(map[string]string) + ci.KeyTypes["rsa4096"] = "RSA-4096" + ci.KeyTypes["rsa3072"] = "RSA-3072" + ci.KeyTypes["rsa2048"] = "RSA-2048" + ci.KeyTypes["ecdsa384"] = "ECDSA-384" + ci.KeyTypes["ecdsa256"] = "ECDSA-256" + + ci.KeyType = "rsa4096" +} + +func (ci *CertificateInfo) Validate() bool { + ci.Errors = make(map[string]string) + + if ci.CreateType == "generate" { + if strings.TrimSpace(ci.KeyType) == "" || strings.TrimSpace(ci.KeyTypes[ci.KeyType]) == "" { + ci.Errors["KeyType"] = "Please select a key type/size" + } + if strings.TrimSpace(ci.Country) == "" || len(ci.Country) < 2 { + ci.Errors["Country"] = "Please enter a valid 2-character country code" + } + if strings.TrimSpace(ci.Organization) == "" { + ci.Errors["Organization"] = "Please enter an organization name" + } + if strings.TrimSpace(ci.CommonName) == "" { + ci.Errors["CommonName"] = "Please enter a common name" + } + } + + if (ci.CreateType == "import") && (ci.ImportHandler != nil) { + ext := ci.ImportHandler.Filename[len(ci.ImportHandler.Filename)-4:] + if (ci.ImportHandler.Size == 0) || (ext != ".zip" && ext != ".pfx") { + ci.Errors["Import"] = "Please provide a bundle (.pfx or .zip) with a key and certificate" + } + } + + if ci.CreateType == "upload" { + if strings.TrimSpace(ci.Key) == "" { + ci.Errors["Key"] = "Please provide a PEM-encoded key" + } + if strings.TrimSpace(ci.Certificate) == "" { + ci.Errors["Certificate"] = "Please provide a PEM-encoded certificate" + } + } + + return len(ci.Errors) == 0 +} + +func reportError(err error) error { + lines := strings.Split(string(debug.Stack()), "\n") + if len(lines) >= 5 { + lines = append(lines[:0], lines[5:]...) + } + + stop := len(lines) + for i := 0; i < len(lines); i++ { + if strings.Index(lines[i], ".ServeHTTP(") >= 0 { + stop = i + break + } + } + lines = lines[:stop] + lines = append(lines, "...") + + fmt.Println(strings.Join(lines, "\n")) + + return errors.New("Error (" + err.Error() + ")! See LabCA logs for details") +} + +func preCreateTasks(path string) error { + if _, err := exe_cmd("touch " + path + "index.txt"); err != nil { + return reportError(err) + } + if _, err := exe_cmd("touch " + path + "index.txt.attr"); err != nil { + return reportError(err) + } + + if _, err := os.Stat(path + "serial"); os.IsNotExist(err) { + if err := ioutil.WriteFile(path+"serial", []byte("1000\n"), 0644); err != nil { + return err + } + } + if _, err := os.Stat(path + "crlnumber"); os.IsNotExist(err) { + if err = ioutil.WriteFile(path+"crlnumber", []byte("1000\n"), 0644); err != nil { + return err + } + } + + if _, err := exe_cmd("mkdir -p " + path + "certs"); err != nil { + return reportError(err) + } + + return nil +} + +func (ci *CertificateInfo) Create(path string, certBase string) error { + if err := preCreateTasks(path); err != nil { + return err + } + + tmpDir, err := ioutil.TempDir("", "labca") + if err != nil { + return err + } + + defer os.RemoveAll(tmpDir) + + var tmpKey string + var tmpCert string + if ci.IsRoot { + tmpKey = filepath.Join(tmpDir, "root-ca.key") + tmpCert = filepath.Join(tmpDir, "root-ca.pem") + } else { + tmpKey = filepath.Join(tmpDir, "ca-int.key") + tmpCert = filepath.Join(tmpDir, "ca-int.pem") + } + + if ci.CreateType == "generate" { + // 1. Generate key + createCmd := "genrsa -aes256 -passout pass:foobar" + keySize := " 4096" + if strings.HasPrefix(ci.KeyType, "ecdsa") { + keySize = "" + createCmd = "ecparam -genkey -name " + if ci.KeyType == "ecdsa256" { + createCmd = createCmd + "prime256v1" + } + if ci.KeyType == "ecdsa384" { + createCmd = createCmd + "secp384r1" + } + } else { + if strings.HasSuffix(ci.KeyType, "3072") { + keySize = " 3072" + } + if strings.HasSuffix(ci.KeyType, "2048") { + keySize = " 2048" + } + } + + if _, err := exe_cmd("openssl " + createCmd + " -out " + path + certBase + ".key" + keySize); err != nil { + return reportError(err) + } + if _, err := exe_cmd("openssl pkey -in " + path + certBase + ".key -passin pass:foobar -out " + path + certBase + ".tmp"); err != nil { + return reportError(err) + } + if _, err = exe_cmd("mv " + path + certBase + ".tmp " + path + certBase + ".key"); err != nil { + return reportError(err) + } + + _, _ = exe_cmd("sleep 1") + + // 2. Generate certificate + subject := "/C=" + ci.Country + "/O=" + ci.Organization + if ci.OrgUnit != "" { + subject = subject + "/OU=" + ci.OrgUnit + } + subject = subject + "/CN=" + ci.CommonName + subject = strings.Replace(subject, " ", "\\\\", -1) + + if ci.IsRoot { + if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -days 3650 -new -x509 -extensions v3_ca -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".pem"); err != nil { + return reportError(err) + } + } else { + if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -new -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".csr"); err != nil { + return reportError(err) + } + if _, err := exe_cmd("openssl ca -config " + path + "../openssl.cnf -extensions v3_intermediate_ca -days 3600 -md sha384 -notext -batch -in " + path + certBase + ".csr -out " + path + certBase + ".pem"); err != nil { + return reportError(err) + } + } + + } else if ci.CreateType == "import" { + tmpFile := filepath.Join(tmpDir, ci.ImportHandler.Filename) + + f, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + return err + } + + defer f.Close() + + io.Copy(f, ci.ImportFile) + + contentType := ci.ImportHandler.Header.Get("Content-Type") + if contentType == "application/x-pkcs12" { + if ci.IsRoot { + if strings.Index(ci.ImportHandler.Filename, "labca_root") != 0 { + fmt.Printf("WARNING: importing root from .pfx file but name is %s\n", ci.ImportHandler.Filename) + } + } else { + if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 { + fmt.Printf("WARNING: importing issuer from .pfx file but name is %s\n", ci.ImportHandler.Filename) + } + } + + pwd := "pass:dummy" + if ci.ImportPwd != "" { + pwd = "pass:" + strings.Replace(ci.ImportPwd, " ", "\\\\", -1) + } + + if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nocerts -nodes -out " + tmpKey); err != nil { + if strings.Index(string(out), "invalid password") >= 0 { + return errors.New("Incorrect password!") + } else { + return reportError(err) + } + } + if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nokeys -out " + tmpCert); err != nil { + if strings.Index(string(out), "invalid password") >= 0 { + return errors.New("Incorrect password!") + } else { + return reportError(err) + } + } + } else if contentType == "application/zip" { + if ci.IsRoot { + if (strings.Index(ci.ImportHandler.Filename, "labca_root") != 0) && (strings.Index(ci.ImportHandler.Filename, "labca_certificates") != 0) { + fmt.Printf("WARNING: importing root from .zip file but name is %s\n", ci.ImportHandler.Filename) + } + } else { + if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 { + fmt.Printf("WARNING: importing issuer from .zip file but name is %s\n", ci.ImportHandler.Filename) + } + } + + cmd := "unzip -j" + if ci.ImportPwd != "" { + cmd = cmd + " -P " + strings.Replace(ci.ImportPwd, " ", "\\\\", -1) + } else { + cmd = cmd + " -P dummy" + } + cmd = cmd + " " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -d " + tmpDir + + if _, err := exe_cmd(cmd); err != nil { + if err.Error() == "exit status 82" { + return errors.New("Incorrect password!") + } else { + return reportError(err) + } + } + } else { + return errors.New("Content Type '" + contentType + "' not supported!") + } + + } else if ci.CreateType == "upload" { + if err := ioutil.WriteFile(tmpKey, []byte(ci.Key), 0644); err != nil { + return err + } + + pwd := "pass:dummy" + if ci.Passphrase != "" { + pwd = "pass:" + strings.Replace(ci.Passphrase, " ", "\\\\", -1) + } + + if out, err := exe_cmd("openssl pkey -passin " + pwd + " -in " + tmpKey + " -out " + tmpKey + "-out"); err != nil { + if strings.Index(string(out), ":bad decrypt:") >= 0 { + return errors.New("Incorrect password!") + } else { + return reportError(err) + } + } else { + if _, err = exe_cmd("mv " + tmpKey + "-out " + tmpKey); err != nil { + return reportError(err) + } + } + + if err := ioutil.WriteFile(tmpCert, []byte(ci.Certificate), 0644); err != nil { + return err + } + + } else { + return fmt.Errorf("Unknown CreateType!") + } + + // This is shared between pfx/zip upload and pem text upload + if ci.CreateType != "generate" { + var rootCert string + var rootKey string + var issuerCert string + var issuerKey string + + if ci.IsRoot { + rootCert = filepath.Join(tmpDir, "root-ca.pem") + rootKey = filepath.Join(tmpDir, "root-ca.key") + + if _, err := os.Stat(rootCert); os.IsNotExist(err) { + return errors.New("File does not contain root-ca.pem!") + } + if _, err := os.Stat(rootKey); os.IsNotExist(err) { + return errors.New("File does not contain root-ca.key!") + } + } + + issuerCert = filepath.Join(tmpDir, "ca-int.pem") + issuerKey = filepath.Join(tmpDir, "ca-int.key") + + if _, err := os.Stat(issuerCert); os.IsNotExist(err) { + if ci.IsRoot { + issuerCert = "" + } else { + return errors.New("File does not contain ca-int.pem!") + } + } + if _, err := os.Stat(issuerKey); os.IsNotExist(err) { + if ci.IsRoot { + issuerKey = "" + } else { + return errors.New("File does not contain ca-int.key!") + } + } + + var rootSubject string + if (rootCert != "") && (rootKey != "") { + r, err := exe_cmd("openssl x509 -noout -subject -in " + rootCert) + if err != nil { + return reportError(err) + } else { + rootSubject = string(r[0 : len(r)-1]) + fmt.Printf("Import root with subject '%s'\n", rootSubject) + } + + r, err = exe_cmd("openssl pkey -noout -in " + rootKey) + if err != nil { + return reportError(err) + } else { + fmt.Println("Import root key") + } + } + + if (issuerCert != "") && (issuerKey != "") { + if ci.IsRoot { + if err := preCreateTasks(path + "issuer/"); err != nil { + return err + } + } + + r, err := exe_cmd("openssl x509 -noout -subject -in " + issuerCert) + if err != nil { + return reportError(err) + } else { + fmt.Printf("Import issuer with subject '%s'\n", string(r[0:len(r)-1])) + } + + r, err = exe_cmd("openssl x509 -noout -issuer -in " + issuerCert) + if err != nil { + return reportError(err) + } else { + issuerIssuer := string(r[0 : len(r)-1]) + fmt.Printf("Issuer certificate issued by CA '%s'\n", issuerIssuer) + + if rootSubject == "" { + r, err := exe_cmd("openssl x509 -noout -subject -in data/root-ca.pem") + if err != nil { + return reportError(err) + } else { + rootSubject = string(r[0 : len(r)-1]) + } + } + + issuerIssuer = strings.Replace(issuerIssuer, "issuer=", "", -1) + rootSubject = strings.Replace(rootSubject, "subject=", "", -1) + if issuerIssuer != rootSubject { + return errors.New("Issuer not issued by our Root CA!") + } + } + + r, err = exe_cmd("openssl pkey -noout -in " + issuerKey) + if err != nil { + return reportError(err) + } else { + fmt.Println("Import issuer key") + } + } + + // All is good now, move files to their permanent location... + if rootCert != "" { + if _, err = exe_cmd("mv " + rootCert + " " + path); err != nil { + return reportError(err) + } + } + if rootKey != "" { + if _, err = exe_cmd("mv " + rootKey + " " + path); err != nil { + return reportError(err) + } + } + if issuerCert != "" { + if _, err = exe_cmd("mv " + issuerCert + " data/issuer/"); err != nil { + return reportError(err) + } + } + if issuerKey != "" { + if _, err = exe_cmd("mv " + issuerKey + " data/issuer/"); err != nil { + return reportError(err) + } + } + + if (issuerCert != "") && (issuerKey != "") && ci.IsRoot { + if err := postCreateTasks(path+"issuer/", "ca-int"); err != nil { + return err + } + } + } + + if err := postCreateTasks(path, certBase); err != nil { + return err + } + + if ci.IsRoot { + if _, err := exe_cmd("openssl ca -config " + path + "openssl.cnf -gencrl -keyfile " + path + certBase + ".key -cert " + path + certBase + ".pem -out " + path + certBase + ".crl"); err != nil { + return reportError(err) + } + } + + return nil +} + +func postCreateTasks(path string, certBase string) error { + if _, err := exe_cmd("openssl pkey -in " + path + certBase + ".key -out " + path + certBase + ".key.der -outform der"); err != nil { + return reportError(err) + } + + if _, err := exe_cmd("openssl x509 -in " + path + certBase + ".pem -out " + path + certBase + ".der -outform DER"); err != nil { + return reportError(err) + } + + return nil +} + +func exe_cmd(cmd string) ([]byte, error) { + parts := strings.Fields(cmd) + for i := 0; i < len(parts); i++ { + parts[i] = strings.Replace(parts[i], "\\\\", " ", -1) + } + head := parts[0] + parts = parts[1:] + + out, err := exec.Command(head, parts...).CombinedOutput() + if err != nil { + fmt.Print(fmt.Sprint(err) + ": " + string(out)) + } else { + //fmt.Println(string(out)) + } + return out, err +} diff --git a/gui/dashboard.go b/gui/dashboard.go new file mode 100644 index 0000000..66cc147 --- /dev/null +++ b/gui/dashboard.go @@ -0,0 +1,359 @@ +package main + +import ( + "database/sql" + "fmt" + "github.com/dustin/go-humanize" + "log" + "net/http" + "regexp" + "strconv" + "strings" + "time" +) + +type Activity struct { + Title string + Message string + Timestamp string + TimestampRel string + Class string +} + +func _parseLine(line string, loc *time.Location) Activity { + var activity Activity + + // Remove ansi colors + b := make([]byte, len(line)) + var bl int + for i := 0; i < len(line); i++ { + c := line[i] + if c >= 32 && c != 127 { + b[bl] = c + bl++ + } + } + line = string(b[:bl]) + line = strings.Replace(line, "[31m", "", -1) + line = strings.Replace(line, "[1m", "", -1) + line = strings.Replace(line, "[0m", "", -1) + + re := regexp.MustCompile("^.*\\|\\s*(\\S)(\\S+) (\\S+) (\\S+) (.*)$") + result := re.FindStringSubmatch(line) + + activity.Class = "" + if result[1] == "W" { + activity.Class = "warning" + } + if result[1] == "E" { + activity.Class = "error" + } + + timestamp, err := time.ParseInLocation("060102150405", result[2], loc) + activity.Timestamp = "" + activity.TimestampRel = "??" + if err == nil { + activity.Timestamp = timestamp.Format("02-Jan-2006 15:04:05 MST") + activity.Timestamp = strings.Replace(activity.Timestamp, "+0000", "GMT", -1) + activity.TimestampRel = humanize.RelTime(timestamp, time.Now(), "", "") + } + + tail := result[3][len(result[3])-2:] + activity.Title = "" + switch tail { + case "ca": + activity.Title = "Certification Agent" + case "ra": + activity.Title = "Registration Agent" + case "sa": + activity.Title = "Storage Agent" + case "va": + activity.Title = "Validation Agent" + } + + message := result[5] + idx := strings.Index(message, ".well-known/acme-challenge") + if idx > -1 { + message = message[0:idx] + } + if strings.Index(message, "Checked CAA records for") > -1 { + message = message[0:strings.Index(message, ",")] + } + if strings.Index(message, "Validation result") > -1 { + message = message[0:17] + } + idx = strings.Index(message, " csr=[") + if idx > -1 { + message = message[0:idx] + } + idx = strings.Index(message, " precertificate=[") + if idx > -1 { + message = message[0:idx] + } + if strings.Index(message, "Certificate request - ") > -1 { + idx = strings.Index(message, " JSON={") + if idx > -1 { + message = message[0:idx] + } + } + activity.Message = message + + return activity +} + +func _parseActivity(data string) []Activity { + var activity []Activity + + lines := strings.Split(data, "\n") + + loc, err := time.LoadLocation(lines[0]) + if err != nil { + log.Printf("Could not determine location: %s\n", err) + loc = time.Local + } + + for i := len(lines) - 2; i >= 1; i-- { + activity = append(activity, _parseLine(lines[i], loc)) + } + + return activity +} + +type Component struct { + Name string + Timestamp string + TimestampRel string + Class string + LogUrl string + LogTitle string + Buttons []map[string]interface{} +} + +func _parseComponents(data string) []Component { + var components []Component + + if data[len(data)-1:] == "\n" { + data = data[0 : len(data)-1] + } + + parts := strings.Split(data, "|") + + loc, err := time.LoadLocation(parts[0]) + if err != nil { + log.Printf("Could not determine location: %s\n", err) + loc = time.Local + } + + nginx, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[1], loc) + nginxReal := "" + nginxNice := "stopped" + nginxClass := "error" + if err == nil { + nginxReal = nginx.Format("02-Jan-2006 15:04:05 MST") + nginxNice = humanize.RelTime(nginx, time.Now(), "", "") + nginxClass = "" + } + + svc, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[2], loc) + svcReal := "" + svcNice := "stopped" + svcClass := "error" + if err == nil { + svcReal = svc.Format("02-Jan-2006 15:04:05 MST") + svcNice = humanize.RelTime(svc, time.Now(), "", "") + svcClass = "" + } + + boulder, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[3], loc) + boulderReal := "" + boulderNice := "stopped" + boulderClass := "error" + if err == nil { + boulderReal = boulder.Format("02-Jan-2006 15:04:05 MST") + boulderNice = humanize.RelTime(boulder, time.Now(), "", "") + boulderClass = "" + } + + labca, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[4], loc) + labcaReal := "" + labcaNice := "stopped" + labcaClass := "error" + if err == nil { + labcaReal = labca.Format("02-Jan-2006 15:04:05 MST") + labcaNice = humanize.RelTime(labca, time.Now(), "", "") + labcaClass = "" + } + + components = append(components, Component{Name: "NGINX Webserver", Timestamp: nginxReal, TimestampRel: nginxNice, Class: nginxClass}) + components = append(components, Component{Name: "Host Service", Timestamp: svcReal, TimestampRel: svcNice, Class: svcClass}) + components = append(components, Component{Name: "Boulder (ACME)", Timestamp: boulderReal, TimestampRel: boulderNice, Class: boulderClass}) + components = append(components, Component{Name: "LabCA Application", Timestamp: labcaReal, TimestampRel: labcaNice, Class: labcaClass}) + + return components +} + +type Stat struct { + Name string + Hint string + Value string + Class string +} + +func _parseStats(data string) []Stat { + var stats []Stat + + if data[len(data)-1:] == "\n" { + data = data[0 : len(data)-1] + } + + parts := strings.Split(data, "|") + + loc, err := time.LoadLocation(parts[0]) + if err != nil { + log.Printf("Could not determine location: %s\n", err) + loc = time.Local + } + + since, err := time.ParseInLocation("2006-01-02 15:04:05", parts[1], loc) + var sinceReal string + sinceNice := "??" + if err == nil { + sinceReal = since.Format("02-Jan-2006 15:04:05 MST") + sinceNice = humanize.RelTime(since, time.Now(), "", "") + } + stats = append(stats, Stat{Name: "System Uptime", Hint: sinceReal, Value: sinceNice}) + + numProcs, err := strconv.Atoi(parts[2]) + if err != nil { + numProcs = 0 + } + stats = append(stats, Stat{Name: "Process Count", Value: strconv.Itoa(numProcs)}) + + memUsed, err := strconv.ParseUint(parts[3], 10, 64) + if err != nil { + memUsed = 0 + } + memAvail, err := strconv.ParseUint(parts[4], 10, 64) + if err != nil { + memAvail = 0 + } + + percMem := float64(0) + if (memUsed + memAvail) > 0 { + percMem = float64(100) * float64(memUsed) / float64(memUsed+memAvail) + } + + usedHuman := humanize.IBytes(memUsed) + availHuman := humanize.IBytes(memAvail) + percHuman := fmt.Sprintf("%s %%", humanize.FtoaWithDigits(percMem, 1)) + + class := "" + if percMem > 75 { + class = "warning" + } + if percMem > 90 { + class = "error" + } + stats = append(stats, Stat{Name: "Memory Usage", Value: percHuman, Class: class}) + stats = append(stats, Stat{Name: "Memory Used", Value: usedHuman}) + class = "" + if memAvail < 250000000 { + class = "warning" + } + if memAvail < 100000000 { + class = "error" + } + stats = append(stats, Stat{Name: "Memory Available", Value: availHuman, Class: class}) + + return stats +} + +func CollectDashboardData(w http.ResponseWriter, r *http.Request) (map[string]interface {}, error) { + db, err := sql.Open(dbType, dbConn) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return nil, err + } + + defer db.Close() + + dashboardData := make(map[string]interface{}) + dashboardData["RequestBase"] = r.Header.Get("X-Request-Base") + + rows, err := db.Query("SELECT count(*) FROM registrations") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return nil, err + } + + var dbres int + if rows.Next() { + err = rows.Scan(&dbres) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return nil, err + } + + dashboardData["NumAccounts"] = dbres + } + + rows, err = db.Query("SELECT count(*) FROM certificateStatus WHERE revokedDate='0000-00-00 00:00:00' AND notAfter >= NOW()") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return nil, err + } + + if rows.Next() { + err = rows.Scan(&dbres) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return nil, err + } + + dashboardData["NumCerts"] = dbres + } + + rows, err = db.Query("SELECT count(*) FROM certificateStatus WHERE revokedDate='0000-00-00 00:00:00' AND notAfter < NOW()") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return nil, err + } + + if rows.Next() { + err = rows.Scan(&dbres) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return nil, err + } + + dashboardData["NumExpired"] = dbres + } + + rows, err = db.Query("SELECT count(*) FROM certificateStatus WHERE revokedDate<>'0000-00-00 00:00:00'") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return nil, err + } + + if rows.Next() { + err = rows.Scan(&dbres) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return nil, err + } + + dashboardData["NumRevoked"] = dbres + } + + activity := getLog(w, r, "activity") + dashboardData["Activity"] = _parseActivity(activity) + + components := getLog(w, r, "components") + dashboardData["Components"] = _parseComponents(components) + + stats := getLog(w, r, "stats") + dashboardData["Stats"] = _parseStats(stats) + + return dashboardData, nil +} + diff --git a/gui/data/issuer/openssl.cnf b/gui/data/issuer/openssl.cnf new file mode 100644 index 0000000..4ba5409 --- /dev/null +++ b/gui/data/issuer/openssl.cnf @@ -0,0 +1,135 @@ +# OpenSSL Intermediary CA configuration file + +[ ca ] +default_ca = CA_default + +[ CA_default ] +# Directory and file locations. +dir = data/issuer +certs = $dir/certs +crl_dir = $dir/crl +new_certs_dir = $dir/certs +database = $dir/index.txt +serial = $dir/serial +RANDFILE = $dir/.rand + +# The root key and root certificate. +private_key = $dir/ca-int.key +certificate = $dir/ca-int.pem + +# For certificate revocation lists. +crlnumber = $dir/crlnumber +crl = $dir/crl/ca-int.crl +crl_extensions = crl_ext +default_crl_days = 180 + +# SHA-1 is deprecated, so use SHA-2 or SHA-3 instead. +#default_md = sha384 +default_md = sha256 + +name_opt = ca_default +cert_opt = ca_default +default_days = 3000 +preserve = no +policy = policy_loose + +[ policy_loose ] +# Allow the intermediate CA to sign a more diverse range of certificates. +# See the POLICY FORMAT section of the `ca` man page. +countryName = optional +organizationName = match +organizationalUnitName = optional +commonName = supplied + +[ req ] +# Options for the `req` tool (`man req`). +default_bits = 2048 +distinguished_name = req_distinguished_name +string_mask = utf8only + +# SHA-1 is deprecated, so use SHA-2 or SHA-3 instead. +default_md = sha384 + +# Extension to add when the -x509 option is used. +x509_extensions = v3_ca + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +0.organizationName = Organization Name +organizationalUnitName = Organizational Unit Name +commonName = Common Name + +# Optionally, specify some defaults. +countryName_default = PKI_DEFAULT_C +0.organizationName_default = PKI_DEFAULT_O +organizationalUnitName_default = PKI_DEFAULT_I_OU +commonName_default = PKI_DEFAULT_I_OU + +[ v3_ca ] +# Extensions for a typical CA (`man x509v3_config`). +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true +keyUsage = critical, digitalSignature, cRLSign, keyCertSign + +[ v3_intermediate_ca ] +# Extensions for a typical intermediate CA (`man x509v3_config`). +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, digitalSignature, cRLSign, keyCertSign +crlDistributionPoints = @crl_info +authorityInfoAccess = @ocsp_info +certificatePolicies=2.23.140.1.2.1,@cps + +[ usr_cert ] +# Extensions for client certificates (`man x509v3_config`). +basicConstraints = CA:FALSE +nsCertType = client, email +nsComment = "OpenSSL Generated Client Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, emailProtection + +[ codesign_req ] +keyUsage = critical,digitalSignature +extendedKeyUsage = critical,codeSigning +subjectKeyIdentifier = hash +basicConstraints = CA:FALSE +authorityInfoAccess = @ocsp_info + +[ server_cert ] +# Extensions for server certificates (`man x509v3_config`). +basicConstraints = CA:FALSE +nsCertType = server +nsComment = "OpenSSL Generated Server Cert" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +crlDistributionPoints = @crl_info +authorityInfoAccess = @ocsp_info + +[ crl_ext ] +# Extension for CRLs (`man x509v3_config`). +authorityKeyIdentifier=keyid:always + +[ ocsp ] +# Extension for OCSP signing certificates (`man ocsp`). +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = critical, digitalSignature +extendedKeyUsage = critical, OCSPSigning + +[crl_info] +URI.0 = http://LABCA_FQDN/crl/ca-int.crl + +[ocsp_info] +caIssuers;URI.0 = http://LABCA_FQDN/cert/ca-int.pem +OCSP;URI.0 = http://LABCA_FQDN/ocsp/ + +[cps] +policyIdentifier = 1.3.6.1.4.1.44947.1.1.1 + CPS.1="http://LABCA_FQDN/cps/"; diff --git a/gui/data/openssl.cnf b/gui/data/openssl.cnf new file mode 100644 index 0000000..6ed919b --- /dev/null +++ b/gui/data/openssl.cnf @@ -0,0 +1,140 @@ +# OpenSSL Root CA configuration file + +[ ca ] +default_ca = CA_default + +[ CA_default ] +# Directory and file locations. +dir = data +certs = $dir/certs +crl_dir = $dir/crl +new_certs_dir = $dir/certs +database = $dir/index.txt +serial = $dir/serial +RANDFILE = $dir/.rand + +# The root key and root certificate. +private_key = $dir/root-ca.key +certificate = $dir/root-ca.pem + +# For certificate revocation lists. +crlnumber = $dir/crlnumber +crl = $dir/crl/root-ca.crl +crl_extensions = crl_ext +default_crl_days = 3650 + +# SHA-1 is deprecated, so use SHA-2 or SHA-3 instead. +#default_md = sha384 +default_md = sha256 + +name_opt = ca_default +cert_opt = ca_default +default_days = 3650 +preserve = no +policy = policy_strict + +[ policy_strict ] +# The root CA should only sign intermediate certificates that match. +# See the POLICY FORMAT section of `man ca`. +countryName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied + +[ policy_loose ] +# Allow the intermediate CA to sign a more diverse range of certificates. +# See the POLICY FORMAT section of the `ca` man page. +countryName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied + +[ req ] +# Options for the `req` tool (`man req`). +default_bits = 2048 +distinguished_name = req_distinguished_name +string_mask = utf8only + +# SHA-1 is deprecated, so use SHA-2 or SHA-3 instead. +default_md = sha384 + +# Extension to add when the -x509 option is used. +x509_extensions = v3_ca + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +0.organizationName = Organization Name +organizationalUnitName = Organizational Unit Name +commonName = Common Name + +# Optionally, specify some defaults. +countryName_default = PKI_DEFAULT_C +0.organizationName_default = PKI_DEFAULT_O +organizationalUnitName_default = PKI_DEFAULT_R_OU +commonName_default = PKI_DEFAULT_R_OU + +[ v3_ca ] +# Extensions for a typical CA (`man x509v3_config`). +subjectKeyIdentifier = hash +basicConstraints = critical, CA:true +keyUsage = critical, cRLSign, keyCertSign +crlDistributionPoints = @crl_info +certificatePolicies=2.23.140.1.2.1,@cps + +[ v3_intermediate_ca ] +# Extensions for a typical intermediate CA (`man x509v3_config`). +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, digitalSignature, cRLSign, keyCertSign +crlDistributionPoints = @crl_info +authorityInfoAccess = @ocsp_info +certificatePolicies=2.23.140.1.2.1,@cps + +[ usr_cert ] +# Extensions for client certificates (`man x509v3_config`). +basicConstraints = CA:FALSE +nsCertType = client, email +nsComment = "OpenSSL Generated Client Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, emailProtection + +[ server_cert ] +# Extensions for server certificates (`man x509v3_config`). +basicConstraints = CA:FALSE +nsCertType = server +nsComment = "OpenSSL Generated Server Cert" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +crlDistributionPoints = @crl_info +authorityInfoAccess = @ocsp_info +#subjectAltName = @alt_names + +[alt_names] +DNS.0 = PKI_DEFAULT_R_OU + +[ crl_ext ] +# Extension for CRLs (`man x509v3_config`). +authorityKeyIdentifier=keyid:always + +[ ocsp ] +# Extension for OCSP signing certificates (`man ocsp`). +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = critical, digitalSignature +extendedKeyUsage = critical, OCSPSigning + +[crl_info] +URI.0 = http://LABCA_FQDN/crl/root-ca.crl + +[ocsp_info] +caIssuers;URI.0 = http://LABCA_FQDN/cert/root-ca.pem + +[cps] +policyIdentifier = 1.3.6.1.4.1.44947.1.1.1 + CPS.1="http://LABCA_FQDN/cps/"; diff --git a/gui/main.go b/gui/main.go new file mode 100644 index 0000000..9cfbe48 --- /dev/null +++ b/gui/main.go @@ -0,0 +1,2274 @@ +package main + +import ( + "bufio" + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "github.com/biz/templates" + _ "github.com/go-sql-driver/mysql" + "github.com/gorilla/mux" + "github.com/gorilla/securecookie" + "github.com/gorilla/sessions" + "github.com/gorilla/websocket" + "github.com/nbutton23/zxcvbn-go" + "github.com/theherk/viper" + "golang.org/x/crypto/bcrypt" + "html/template" + "io" + "io/ioutil" + "log" + "math" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "reflect" + "regexp" + "runtime" + "runtime/debug" + "strconv" + "strings" + "time" +) + +const ( + writeWait = 10 * time.Second + pongWait = 60 * time.Second + pingPeriod = (pongWait * 9) / 10 +) + +var ( + appSession *sessions.Session + restartSecret string + sessionStore *sessions.CookieStore + tmpls *templates.Templates + version string + dbConn string + dbType string + isDev bool + + upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + } +) + +type User struct { + Name string + Email string + Password string + Confirm string + NewPassword string + RequestBase string + Errors map[string]string +} + +func (reg *User) Validate(isNew bool, isChange bool) bool { + reg.Errors = make(map[string]string) + + if strings.TrimSpace(reg.Name) == "" { + reg.Errors["Name"] = "Please enter a user name" + } + + if isNew || isChange { + re := regexp.MustCompile(".+@.+\\..+") + matched := re.Match([]byte(reg.Email)) + if matched == false { + reg.Errors["Email"] = "Please enter a valid email address" + } + } + + blacklist := []string{"labca", "acme", reg.Name} + if x := strings.Index(reg.Email, "@"); x > 0 { + blacklist = append(blacklist, reg.Email[:x]) + d := strings.Split(reg.Email[x+1:], ".") + for i:=0; i= 5 { + lines = append(lines[:0], lines[5:]...) + } + fmt.Print(strings.Join(lines, "\n")) + + render(w, r, "error", map[string]interface{}{"Message": "Some unexpected error occurred!"}) + // TODO: send email eventually with info on the error + } +} + +func rootHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + dashboardData, err := CollectDashboardData(w, r) + if err == nil { + render(w, r, "dashboard", dashboardData) + } +} + +func aboutHandler(w http.ResponseWriter, r *http.Request) { + render(w, r, "about", map[string]interface{}{ + "Title": "About", + }) +} + +func loginHandler(w http.ResponseWriter, r *http.Request) { + if viper.Get("user.password") == nil { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + session := getSession(w, r) + var bounceUrl string + if session.Values["bounce"] == nil { + bounceUrl = "/" + } else { + bounceUrl = session.Values["bounce"].(string) + } + + if session.Values["user"] != nil { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+bounceUrl, http.StatusFound) + return + } + + if r.Method == "GET" { + reg := &User{ + RequestBase: r.Header.Get("X-Request-Base"), + } + render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true}) + return + } else if r.Method == "POST" { + if err := r.ParseForm(); err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + + reg := &User{ + Name: r.Form.Get("username"), + Password: r.Form.Get("password"), + RequestBase: r.Header.Get("X-Request-Base"), + } + + if reg.Validate(false, false) == false { + render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true}) + return + } + + if viper.GetString("user.name") != reg.Name { + reg.Errors["Name"] = "Incorrect username or password" + render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true}) + return + } else { + byteStored := []byte(viper.GetString("user.password")) + err := bcrypt.CompareHashAndPassword(byteStored, []byte(reg.Password)) + if err != nil { + log.Println(err) + reg.Errors["Name"] = "Incorrect username or password" + render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true}) + return + } + } + + session.Values["user"] = reg.Name + session.Save(r, w) + + http.Redirect(w, r, r.Header.Get("X-Request-Base")+bounceUrl, http.StatusFound) + } else { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/login", http.StatusSeeOther) + return + } +} + +func logoutHandler(w http.ResponseWriter, r *http.Request) { + appSession = nil + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound) +} + +func _sendCmdOutput(w http.ResponseWriter, r *http.Request, cmd string) { + parts := strings.Fields(cmd) + for i := 0; i < len(parts); i++ { + parts[i] = strings.Replace(parts[i], "\\\\", " ", -1) + } + head := parts[0] + parts = parts[1:] + + out, err := exec.Command(head, parts...).Output() + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + + buf := bytes.NewBuffer(out) + _, err = buf.WriteTo(w) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } +} + +func _backupHandler(w http.ResponseWriter, r *http.Request) { + res := struct { + Success bool + Message string + }{Success: true} + + action := r.Form.Get("action") + if action == "backup-restore" { + backup := r.Form.Get("backup") + if !_hostCommand(w, r, action, backup) { + res.Success = false + res.Message = "Command failed - see LabCA log for any details" + } + + defer _hostCommand(w, r, "server-restart") + } else if action == "backup-delete" { + backup := r.Form.Get("backup") + if !_hostCommand(w, r, action, backup) { + res.Success = false + res.Message = "Command failed - see LabCA log for any details" + } + } else if action == "backup-now" { + res.Message = getLog(w, r, "server-backup") + if res.Message == "" { + res.Success = false + res.Message = "Command failed - see LabCA log for any details" + } else { + res.Message = filepath.Base(res.Message) + } + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(res) +} + +func _accountUpdateHandler(w http.ResponseWriter, r *http.Request) { + reg := &User{ + Name: r.Form.Get("username"), + Email: r.Form.Get("email"), + NewPassword: r.Form.Get("new-password"), + Confirm: r.Form.Get("confirm"), + Password: r.Form.Get("password"), + } + + res := struct { + Success bool + Errors map[string]string + }{Success: true} + + if reg.Validate(false, true) { + viper.Set("user.name", reg.Name) + viper.Set("user.email", reg.Email) + + if reg.NewPassword != "" { + hash, err := bcrypt.GenerateFromPassword([]byte(reg.NewPassword), bcrypt.MinCost) + if err != nil { + res.Success = false + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + viper.Set("user.password", string(hash)) + + // Forget current session, so user has to login with the new password + appSession = nil + } + + viper.WriteConfig() + + } else { + res.Success = false + res.Errors = reg.Errors + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(res) +} + +func _configUpdateHandler(w http.ResponseWriter, r *http.Request) { + cfg := &SetupConfig{ + Fqdn: r.Form.Get("fqdn"), + Organization: r.Form.Get("organization"), + Dns: r.Form.Get("dns"), + DomainMode: r.Form.Get("domain_mode"), + LockdownDomains: r.Form.Get("lockdown_domains"), + WhitelistDomains: r.Form.Get("whitelist_domains"), + } + + res := struct { + Success bool + Errors map[string]string + }{Success: true} + + if cfg.Validate(true) { + delta := false + + if cfg.Fqdn != viper.GetString("labca.fqdn") { + delta = true + viper.Set("labca.fqdn", cfg.Fqdn) + } + + if cfg.Organization != viper.GetString("labca.organization") { + delta = true + viper.Set("labca.organization", cfg.Organization) + } + + matched, err := regexp.MatchString(":\\d+$", cfg.Dns) + if err == nil && !matched { + cfg.Dns += ":53" + } + + if cfg.Dns != viper.GetString("labca.dns") { + delta = true + viper.Set("labca.dns", cfg.Dns) + } + + domain_mode := cfg.DomainMode + if domain_mode != viper.GetString("labca.domain_mode") { + delta = true + viper.Set("labca.domain_mode", cfg.DomainMode) + } + + if domain_mode == "lockdown" { + if cfg.LockdownDomains != viper.GetString("labca.lockdown") { + delta = true + viper.Set("labca.lockdown", cfg.LockdownDomains) + } + } + if domain_mode == "whitelist" { + if cfg.WhitelistDomains != viper.GetString("labca.whitelist") { + delta = true + viper.Set("labca.whitelist", cfg.WhitelistDomains) + } + } + + if delta { + viper.WriteConfig() + + err := _applyConfig() + if err != nil { + res.Success = false + res.Errors = cfg.Errors + res.Errors["ConfigUpdate"] = "Config apply error: '" + err.Error() + "'" + } + } else { + res.Success = false + res.Errors = cfg.Errors + res.Errors["ConfigUpdate"] = "Nothing changed!" + } + + } else { + res.Success = false + res.Errors = cfg.Errors + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(res) +} + +type EmailConfig struct { + DoEmail bool + Server string + Port string + EmailUser string + EmailPwd []byte + From string + Errors map[string]string +} + +func (cfg *EmailConfig) Validate() bool { + cfg.Errors = make(map[string]string) + + result, err := _encrypt(cfg.EmailPwd) + if err == nil { + cfg.EmailPwd = []byte(result) + } else { + cfg.Errors["EmailPwd"] = "Could not encrypt this password: " + err.Error() + } + + if cfg.DoEmail == false { + return len(cfg.Errors) == 0 + } + + if strings.TrimSpace(cfg.Server) == "" { + cfg.Errors["Server"] = "Please enter the email server address" + } + + if strings.TrimSpace(cfg.Port) == "" { + cfg.Errors["Port"] = "Please enter the email server port number" + } + + p, err := strconv.Atoi(cfg.Port) + if err != nil { + cfg.Errors["Port"] = "Port number must be numeric" + } else if p <= 0 { + cfg.Errors["Port"] = "Port number must be positive" + } else if p > 65535 { + cfg.Errors["Port"] = "Port number too large" + } + + if strings.TrimSpace(cfg.EmailUser) == "" { + cfg.Errors["EmailUser"] = "Please enter the username for authorization to the email server" + } + + res, err := _decrypt(string(cfg.EmailPwd)) + if err != nil { + cfg.Errors["EmailPwd"] = "Could not decrypt this password: " + err.Error() + } + if strings.TrimSpace(string(res)) == "" { + cfg.Errors["EmailPwd"] = "Please enter the password for authorization to the email server" + } + + if strings.TrimSpace(cfg.From) == "" { + cfg.Errors["From"] = "Please enter the from email address" + } + + return len(cfg.Errors) == 0 +} + + +func _emailUpdateHandler(w http.ResponseWriter, r *http.Request) { + cfg := &EmailConfig{ + DoEmail: (r.Form.Get("do_email") == "true"), + Server: r.Form.Get("server"), + Port: r.Form.Get("port"), + EmailUser: r.Form.Get("email_user"), + EmailPwd: []byte(r.Form.Get("email_pwd")), + From: r.Form.Get("from"), + } + + res := struct { + Success bool + Errors map[string]string + }{Success: true} + + if cfg.Validate() { + delta := false + + if cfg.DoEmail != viper.GetBool("labca.email.enable") { + delta = true + viper.Set("labca.email.enable", cfg.DoEmail) + } + + if cfg.Server != viper.GetString("labca.email.server") { + delta = true + viper.Set("labca.email.server", cfg.Server) + } + + if cfg.Port != viper.GetString("labca.email.port") { + delta = true + viper.Set("labca.email.port", cfg.Port) + } + + if cfg.EmailUser != viper.GetString("labca.email.user") { + delta = true + viper.Set("labca.email.user", cfg.EmailUser) + } + + res1, err1 := _decrypt(string(cfg.EmailPwd)) + if err1 != nil && cfg.DoEmail { + log.Println("WARNING: could not decrypt given password: " + err1.Error()) + } + res2, err2 := _decrypt(viper.GetString("labca.email.pass")) + if err2 != nil && cfg.DoEmail && viper.GetString("labca.email.pass") != "" { + log.Println("WARNING: could not decrypt stored password: " + err2.Error()) + } + if string(res1) != string(res2) { + delta = true + viper.Set("labca.email.pass", string(cfg.EmailPwd)) + } + + if cfg.From != viper.GetString("labca.email.from") { + delta = true + viper.Set("labca.email.from", cfg.From) + } + + if delta { + viper.WriteConfig() + + err := _applyConfig() + if err != nil { + res.Success = false + res.Errors = cfg.Errors + res.Errors["EmailUpdate"] = "Config apply error: '" + err.Error() + "'" + } + } else { + res.Success = false + res.Errors = cfg.Errors + res.Errors["EmailUpdate"] = "Nothing changed!" + } + + } else { + res.Success = false + res.Errors = cfg.Errors + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(res) +} + +func _emailSendHandler(w http.ResponseWriter, r *http.Request) { + res := struct { + Success bool + Errors map[string]string + }{Success: true, Errors: make(map[string]string)} + + recipient := viper.GetString("user.email") + if !_hostCommand(w, r, "test-email", recipient) { + res.Success = false + res.Errors["EmailSend"] = "Failed to send email - see logs" + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(res) +} + +func _exportHandler(w http.ResponseWriter, r *http.Request) { + basename := "certificates" + if r.Form.Get("root") != "true" { + basename = "issuer" + } + if r.Form.Get("issuer") != "true" { + basename = "root" + } + + if r.Form.Get("type") == "pfx" { + w.Header().Set("Content-Type", "application/x-pkcs12") + w.Header().Set("Content-Disposition", "attachment; filename=labca_"+basename+".pfx") + + var certBase string + if basename == "root" { + certBase = "data/root-ca" + } else { + certBase = "data/issuer/ca-int" + } + + cmd := "openssl pkcs12 -export -inkey " + certBase + ".key -in " + certBase + ".pem -passout pass:" + r.Form.Get("export-pwd") + + _sendCmdOutput(w, r, cmd) + } + + if r.Form.Get("type") == "zip" { + w.Header().Set("Content-Type", "application/zip") + w.Header().Set("Content-Disposition", "attachment; filename=labca_"+basename+".zip") + + cmd := "zip -j -P " + r.Form.Get("export-pwd") + " - " + var certBase string + if r.Form.Get("root") == "true" { + certBase = "data/root-ca" + cmd = cmd + certBase + ".key " + certBase + ".pem " + } + if r.Form.Get("issuer") == "true" { + certBase = "data/issuer/ca-int" + cmd = cmd + certBase + ".key " + certBase + ".pem " + } + + _sendCmdOutput(w, r, cmd) + } +} + +func _doCmdOutput(w http.ResponseWriter, r *http.Request, cmd string) string { + parts := strings.Fields(cmd) + for i := 0; i < len(parts); i++ { + parts[i] = strings.Replace(parts[i], "\\\\", " ", -1) + } + head := parts[0] + parts = parts[1:] + + out, err := exec.Command(head, parts...).Output() + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return "" + } + + return string(out) +} + +func _encrypt(plaintext []byte) (string, error) { + key := []byte(viper.GetString("keys.enc")) + block, err := aes.NewCipher(key[:32]) + if err != nil { + return "", err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + nonce := make([]byte, gcm.NonceSize()) + _, err = io.ReadFull(rand.Reader, nonce) + if err != nil { + return "", err + } + + return base64.StdEncoding.EncodeToString(gcm.Seal(nonce, nonce, plaintext, nil)), nil +} + +func _decrypt(ciphertext string) ([]byte, error) { + key := []byte(viper.GetString("keys.enc")) + block, err := aes.NewCipher(key[:32]) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + ct, err := base64.StdEncoding.DecodeString(ciphertext) + if err != nil { + return nil, err + } + + if len(ct) < gcm.NonceSize() { + return nil, errors.New("malformed ciphertext") + } + + return gcm.Open(nil, ct[:gcm.NonceSize()], ct[gcm.NonceSize():], nil,) +} + +func manageHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + if r.Method == "POST" { + if err := r.ParseForm(); err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + + action := r.Form.Get("action") + switch action { + case "backup-restore": + case "backup-delete": + case "backup-now": + case "cert-export": + case "nginx-reload": + case "nginx-restart": + case "svc-restart": + case "boulder-start": + case "boulder-stop": + case "boulder-restart": + case "labca-restart": + case "server-restart": + case "server-shutdown": + case "update-account": + case "update-config": + case "update-email": + case "send-email": + default: + errorHandler(w, r, errors.New(fmt.Sprintf("Unknown manage action '%s'", action)), http.StatusBadRequest) + return + } + + if action == "backup-restore" || action == "backup-delete" || action == "backup-now" { + _backupHandler(w, r) + return + } + + if action == "cert-export" { + _exportHandler(w, r) + return + } + + if action == "update-account" { + _accountUpdateHandler(w, r) + return + } + + if action == "update-config" { + _configUpdateHandler(w, r) + return + } + + if action == "update-email" { + _emailUpdateHandler(w, r) + return + } + + if action == "send-email" { + _emailSendHandler(w, r) + return + } + + res := struct { + Success bool + Message string + Timestamp string + TimestampRel string + Class string + }{Success: true} + if !_hostCommand(w, r, action) { + res.Success = false + res.Message = "Command failed - see LabCA log for any details" + } + + if action != "server-restart" && action != "server-shutdown" { + components := _parseComponents(getLog(w, r, "components")) + for i := 0; i < len(components); i++ { + if (components[i].Name == "NGINX Webserver" && (action == "nginx-reload" || action == "nginx-restart")) || + (components[i].Name == "Host Service" && action == "svc-restart") || + (components[i].Name == "Boulder (ACME)" && (action == "boulder-start" || action == "boulder-stop" || action == "boulder-restart")) || + (components[i].Name == "LabCA Application" && action == "labca-restart") { + res.Timestamp = components[i].Timestamp + res.TimestampRel = components[i].TimestampRel + res.Class = components[i].Class + break + } + } + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(res) + + } else { + manageData := make(map[string]interface{}) + manageData["RequestBase"] = r.Header.Get("X-Request-Base") + + components := _parseComponents(getLog(w, r, "components")) + for i := 0; i < len(components); i++ { + if components[i].Name == "NGINX Webserver" { + components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/weberr" + components[i].LogTitle = "Web Error Log" + + btn := make(map[string]interface{}) + btn["Class"] = "btn-info" + btn["Id"] = "nginx-reload" + btn["Title"] = "Reload web server configuration with minimal impact to the users" + btn["Label"] = "Reload" + components[i].Buttons = append(components[i].Buttons, btn) + + btn = make(map[string]interface{}) + btn["Class"] = "btn-warning" + btn["Id"] = "nginx-restart" + btn["Title"] = "Restart the web server with some downtime for the users" + btn["Label"] = "Restart" + components[i].Buttons = append(components[i].Buttons, btn) + } + + if components[i].Name == "Host Service" { + components[i].LogUrl = "" + components[i].LogTitle = "" + + btn := make(map[string]interface{}) + btn["Class"] = "btn-warning" + btn["Id"] = "svc-restart" + btn["Title"] = "Restart the host service" + btn["Label"] = "Restart" + components[i].Buttons = append(components[i].Buttons, btn) + } + + if components[i].Name == "Boulder (ACME)" { + components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/boulder" + components[i].LogTitle = "ACME Log" + + btn := make(map[string]interface{}) + cls := "btn-success" + if components[i].TimestampRel != "stopped" { + cls = cls + " hidden" + } + btn["Class"] = cls + btn["Id"] = "boulder-start" + btn["Title"] = "Start the core ACME application" + btn["Label"] = "Start" + components[i].Buttons = append(components[i].Buttons, btn) + + btn = make(map[string]interface{}) + cls = "btn-warning" + if components[i].TimestampRel == "stopped" { + cls = cls + " hidden" + } + btn["Class"] = cls + btn["Id"] = "boulder-restart" + btn["Title"] = "Stop and restart the core ACME application" + btn["Label"] = "Restart" + components[i].Buttons = append(components[i].Buttons, btn) + + btn = make(map[string]interface{}) + cls = "btn-danger" + if components[i].TimestampRel == "stopped" { + cls = cls + " hidden" + } + btn["Class"] = cls + btn["Id"] = "boulder-stop" + btn["Title"] = "Stop the core ACME application; users can no longer use ACME clients to interact with this instance" + btn["Label"] = "Stop" + components[i].Buttons = append(components[i].Buttons, btn) + } + + if components[i].Name == "LabCA Application" { + components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/labca" + components[i].LogTitle = "LabCA Log" + + btn := make(map[string]interface{}) + btn["Class"] = "btn-warning" + btn["Id"] = "labca-restart" + btn["Title"] = "Stop and restart this LabCA admin application" + btn["Label"] = "Restart" + components[i].Buttons = append(components[i].Buttons, btn) + } + } + manageData["Components"] = components + + stats := _parseStats(getLog(w, r, "stats")) + for _, stat := range stats { + if stat.Name == "System Uptime" { + manageData["ServerTimestamp"] = stat.Hint + manageData["ServerTimestampRel"] = stat.Value + break + } + } + + backupFiles := strings.Split(getLog(w, r, "backups"), "\n") + backupFiles = backupFiles[:len(backupFiles)-1] + manageData["BackupFiles"] = backupFiles + + manageData["RootDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/root-ca.pem") + manageData["IssuerDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/issuer/ca-int.pem") + + manageData["Fqdn"] = viper.GetString("labca.fqdn") + manageData["Organization"] = viper.GetString("labca.organization") + manageData["Dns"] = viper.GetString("labca.dns") + domain_mode := viper.GetString("labca.domain_mode") + manageData["DomainMode"] = domain_mode + if domain_mode == "lockdown" { + manageData["LockdownDomains"] = viper.GetString("labca.lockdown") + } + if domain_mode == "whitelist" { + manageData["WhitelistDomains"] = viper.GetString("labca.whitelist") + } + + manageData["DoEmail"] = viper.GetBool("labca.email.enable") + manageData["Server"] = viper.GetString("labca.email.server") + manageData["Port"] = viper.GetInt("labca.email.port") + manageData["EmailUser"] = viper.GetString("labca.email.user") + manageData["EmailPwd"] = "" + if viper.Get("labca.email.pass") != nil { + pwd := viper.GetString("labca.email.pass") + result, err := _decrypt(pwd) + if err == nil { + manageData["EmailPwd"] = string(result) + } else { + log.Printf("WARNING: could not decrypt email password: %s!\n", err.Error()) + } + } + manageData["From"] = viper.GetString("labca.email.from") + + manageData["Name"] = viper.GetString("user.name") + manageData["Email"] = viper.GetString("user.email") + + render(w, r, "manage", manageData) + } +} + +func logsHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + logType := vars["type"] + + proto := "ws" + if r.Header.Get("X-Forwarded-Proto") == "https" { + proto = "wss" + } + + wsurl := proto + "://" + r.Host + r.Header.Get("X-Request-Base") + "/ws?logType=" + logType + + var name string + var message string + var data string + + switch logType { + case "cert": + name = "Web Certificate Log" + message = "Log file for the certificate renewal for this server." + wsurl = "" + data = getLog(w, r, logType) + case "boulder": + name = "ACME Backend Log" + message = "Live view on the backend ACME application (Boulder) logs." + case "audit": + name = "ACME Audit Log" + message = "Live view on only the audit messages in the backend ACME application (Boulder) logs." + case "labca": + name = "LabCA Log" + message = "Live view on the logs for this LabCA web application." + case "web": + name = "Web Access Log" + message = "Live view on the NGINX web server access log." + case "weberr": + name = "Web Error Log" + message = "Log file for the NGINX web server error log." + wsurl = "" + data = getLog(w, r, logType) + default: + errorHandler(w, r, errors.New(fmt.Sprintf("Unknown log type '%s'", logType)), http.StatusBadRequest) + return + } + + render(w, r, "logs", map[string]interface{}{ + "Name": name, + "Message": message, + "Data": data, + "WsUrl": wsurl, + }) +} + +func getLog(w http.ResponseWriter, r *http.Request, logType string) string { + ip, err := _discoverGateway() + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return "" + } + + conn, err := net.Dial("tcp", ip.String()+":3030") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return "" + } + + defer conn.Close() + + fmt.Fprintf(conn, "log-"+logType+"\n") + reader := bufio.NewReader(conn) + contents, err := ioutil.ReadAll(reader) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return "" + } + + return string(contents) +} + +func wsErrorHandler(err error) { + log.Printf("wsErrorHandler: %v", err) + + pc := make([]uintptr, 15) + n := runtime.Callers(2, pc) + frames := runtime.CallersFrames(pc[:n]) + frame, _ := frames.Next() + fmt.Printf("%s:%d, %s\n", frame.File, frame.Line, frame.Function) + + debug.PrintStack() +} + +func showLog(ws *websocket.Conn, logType string) { + ip, err := _discoverGateway() + if err != nil { + wsErrorHandler(err) + return + } + + conn, err := net.Dial("tcp", ip.String()+":3030") + if err != nil { + wsErrorHandler(err) + return + } + + defer conn.Close() + + fmt.Fprintf(conn, "log-"+logType+"\n") + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + msg := scanner.Text() + if logType != "audit" || strings.Index(msg, "[AUDIT]") > -1 { + ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil { + // Probably "websocket: close sent" + return + } + } + } + if err := scanner.Err(); err != nil { + wsErrorHandler(err) + return + } + + return +} + +func reader(ws *websocket.Conn) { + defer ws.Close() + ws.SetReadLimit(512) + ws.SetReadDeadline(time.Now().Add(pongWait)) + ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + _, _, err := ws.ReadMessage() + if err != nil { + break + } + } +} + +func writer(ws *websocket.Conn, logType string) { + pingTicker := time.NewTicker(pingPeriod) + defer func() { + pingTicker.Stop() + ws.Close() + }() + + go showLog(ws, logType) + + for { + select { + case <-pingTicker.C: + ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + // Probably "websocket: close sent" + return + } + } + } +} + +func wsHandler(w http.ResponseWriter, r *http.Request) { + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + if _, ok := err.(websocket.HandshakeError); !ok { + log.Println(err) + } + return + } + + logType := r.FormValue("logType") + + switch logType { + case "boulder": + case "audit": + case "labca": + case "web": + default: + errorHandler(w, r, errors.New(fmt.Sprintf("Unknown log type '%s'", logType)), http.StatusBadRequest) + return + } + + go writer(ws, logType) + reader(ws) +} + +func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot bool) bool { + path := "data/" + if !isRoot { + path = path + "issuer/" + } + + if _, err := os.Stat(path + certBase + ".pem"); os.IsNotExist(err) { + session := getSession(w, r) + + if r.Method == "GET" { + ci := &CertificateInfo{ + IsRoot: isRoot, + CreateType: "generate", + CommonName: "Root CA", + RequestBase: r.Header.Get("X-Request-Base"), + } + if !isRoot { + ci.CommonName = "CA" + } + ci.Initialize() + + if session.Values["ct"] != nil { + ci.CreateType = session.Values["ct"].(string) + } + if session.Values["kt"] != nil { + ci.KeyType = session.Values["kt"].(string) + } + if session.Values["c"] != nil { + ci.Country = session.Values["c"].(string) + } + if session.Values["o"] != nil { + ci.Organization = session.Values["o"].(string) + } + if session.Values["ou"] != nil { + ci.OrgUnit = session.Values["ou"].(string) + } + if session.Values["cn"] != nil { + ci.CommonName = session.Values["cn"].(string) + ci.CommonName = strings.Replace(ci.CommonName, "Root", "", -1) + ci.CommonName = strings.Replace(ci.CommonName, " ", " ", -1) + } + + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } else if r.Method == "POST" { + if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return false + } + + ci := &CertificateInfo{} + ci.Initialize() + ci.IsRoot = r.Form.Get("cert") == "root" + ci.CreateType = r.Form.Get("createtype") + + if r.Form.Get("keytype") != "" { + ci.KeyType = r.Form.Get("keytype") + } + ci.Country = r.Form.Get("c") + ci.Organization = r.Form.Get("o") + ci.OrgUnit = r.Form.Get("ou") + ci.CommonName = r.Form.Get("cn") + + if ci.CreateType == "import" { + file, handler, err := r.FormFile("import") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return false + } + + defer file.Close() + + ci.ImportFile = file + ci.ImportHandler = handler + ci.ImportPwd = r.Form.Get("import-pwd") + } + + ci.Key = r.Form.Get("key") + ci.Passphrase = r.Form.Get("passphrase") + ci.Certificate = r.Form.Get("certificate") + ci.RequestBase = r.Header.Get("X-Request-Base") + + if ci.Validate() == false { + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + + if err := ci.Create(path, certBase); err != nil { + ci.Errors[strings.Title(ci.CreateType)] = err.Error() + log.Printf("_certCreate: create failed: %v", err) + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + + if viper.Get("labca.organization") == nil { + viper.Set("labca.organization", ci.Organization) + viper.WriteConfig() + } + + session.Values["ct"] = ci.CreateType + session.Values["kt"] = ci.KeyType + session.Values["c"] = ci.Country + session.Values["o"] = ci.Organization + session.Values["ou"] = ci.OrgUnit + session.Values["cn"] = ci.CommonName + session.Save(r, w) + + // Fake the method to GET as we need to continue in the setupHandler() function + r.Method = "GET" + } else { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther) + return false + } + } + + return true +} + +func _parseLinuxIPRouteShow(output []byte) (net.IP, error) { + // Linux '/usr/bin/ip route show' format looks like this: + // default via 192.168.178.1 dev wlp3s0 metric 303 + // 192.168.178.0/24 dev wlp3s0 proto kernel scope link src 192.168.178.76 metric 303 + lines := strings.Split(string(output), "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) >= 3 && fields[0] == "default" { + ip := net.ParseIP(fields[2]) + if ip != nil { + return ip, nil + } + } + } + + return nil, errors.New("no gateway found") +} + +func _discoverGateway() (net.IP, error) { + if isDev { + ip := net.ParseIP("127.0.0.1") + if ip != nil { + return ip, nil + } + } + + routeCmd := exec.Command("ip", "route", "show") + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, err + } + + return _parseLinuxIPRouteShow(output) +} + +func _hostCommand(w http.ResponseWriter, r *http.Request, command string, params ...string) bool { + ip, err := _discoverGateway() + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return false + } + + conn, err := net.Dial("tcp", ip.String()+":3030") + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return false + } + + defer conn.Close() + + fmt.Fprintf(conn, command+"\n") + for _, param := range params { + fmt.Fprintf(conn, param+"\n") + } + + reader := bufio.NewReader(conn) + message, err := ioutil.ReadAll(reader) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return false + } + + if strings.Compare(string(message), "ok\n") == 0 { + return true + } + + tail := message[len(message)-4:] + if strings.Compare(string(tail), "\nok\n") == 0 { + msg := message[0 : len(message)-4] + log.Printf("Message from server: '%s'", msg) + return true + } + + log.Printf("ERROR: Message from server: '%s'", message) + errorHandler(w, r, errors.New(string(message)), http.StatusInternalServerError) + return false +} + +func randToken() string { + b := make([]byte, 8) + rand.Read(b) + return fmt.Sprintf("%x", b) +} + +func _applyConfig() error { + os.Setenv("PKI_ROOT_CERT_BASE", "data/root-ca") + os.Setenv("PKI_INT_CERT_BASE", "data/issuer/ca-int") + os.Setenv("PKI_DEFAULT_O", viper.GetString("labca.organization")) + os.Setenv("PKI_DNS", viper.GetString("labca.dns")) + domain := viper.GetString("labca.fqdn") + os.Setenv("PKI_FQDN", domain) + pos := strings.Index(domain, ".") + if pos > -1 { + pos = pos + 1 + domain = domain[pos:] + } + os.Setenv("PKI_DOMAIN", domain) + os.Setenv("PKI_DOMAIN_MODE", viper.GetString("labca.domain_mode")) + os.Setenv("PKI_LOCKDOWN_DOMAINS", viper.GetString("labca.lockdown")) + os.Setenv("PKI_WHITELIST_DOMAINS", viper.GetString("labca.whitelist")) + if viper.GetBool("labca.email.enable") { + os.Setenv("PKI_EMAIL_SERVER", viper.GetString("labca.email.server")) + os.Setenv("PKI_EMAIL_PORT", viper.GetString("labca.email.port")) + os.Setenv("PKI_EMAIL_USER", viper.GetString("labca.email.user")) + res, err := _decrypt(viper.GetString("labca.email.pass")) + if err != nil { + log.Println("WARNING: could not decrypt stored password: " + err.Error()) + } + os.Setenv("PKI_EMAIL_PASS", string(res)) + os.Setenv("PKI_EMAIL_FROM", viper.GetString("labca.email.from")) + } else { + os.Setenv("PKI_EMAIL_SERVER", "localhost") + os.Setenv("PKI_EMAIL_PORT", "9380") + os.Setenv("PKI_EMAIL_USER", "cert-master@example.com") + os.Setenv("PKI_EMAIL_PASS", "password") + os.Setenv("PKI_EMAIL_FROM", "Expiry bot ") + } + + _, err := exe_cmd("./apply") + if err != nil { + fmt.Println("") + } + return err +} + +func _progress(stage string) int { + max := 20.0 / 100.0 + curr := 1.0 + + if stage == "register" { + return int(math.Round(curr / max)) + } else { + curr += 2.0 + } + + if stage == "setup" { + return int(math.Round(curr / max)) + } else { + curr += 3.0 + } + + if stage == "root-ca" { + return int(math.Round(curr / max)) + } else { + curr += 4.0 + } + + if stage == "ca-int" { + return int(math.Round(curr / max)) + } else { + curr += 3.0 + } + + if stage == "polling" { + return int(math.Round(curr / max)) + } else { + curr += 4.0 + } + + if stage == "wrapup" { + return int(math.Round(curr / max)) + } else { + curr += 3.0 + } + + if stage == "final" { + return int(math.Round(curr / max)) + } else { + return 0 + } +} + +func _helptext(stage string) template.HTML { + if stage == "register" { + return template.HTML(fmt.Sprint("

You need to create an admin account for managing this instance of\n", + "LabCA. There can only be one admin account, but you can configure all its attributes once the\n", + "initial setup has completed.

")) + } else if stage == "setup" { + return template.HTML(fmt.Sprint("

The fully qualified domain name (FQDN) is what end users will use\n", + "to connect to this server. It was provided in the initial setup and is shown here for reference.

\n", + "

Please fill in a DNS server (and optionally port, default is ':53') that will be used to lookup\n", + "the domains for which a certificate is requested.

\n", + "

LabCA is primarily intended for use inside an organization where all domains end in the same\n", + "domain, e.g. '.localdomain'. In lockdown mode only those domains are allowed. In whitelist mode\n", + "those domains are allowed next to all official, internet accessible domains and in standard\n", + "mode only the official domains are allowed.

")) + } else if stage == "root-ca" { + return template.HTML(fmt.Sprint("

This is the top level certificate that will sign the issuer\n", + "certificate(s). You can either generate a fresh Root CA (Certificate Authority) or import an\n", + "existing one, e.g. a backup from another LabCA instance.

\n", + "

If you want to generate a certificate, pick a key type and strength (the higher the number the\n", + "more secure, ECDSA is more modern than RSA), provide at least a country and organization name,\n", + "and the common name. It is recommended that the common name contains the word 'Root' as well\n", + "as your organization name so you can recognize it, and that's why that is automatically filled\n", + "once you leave the organization field.

")) + } else if stage == "ca-int" { + return template.HTML(fmt.Sprint("

This is what end users will see as the issuing certificate. Again,\n", + "you can either generate a fresh certificate or import an existing one, as long as it is signed by\n", + "the Root CA from the previous step.

\n", + "

If you want to generate a certificate, by default the same key type and strength is selected as\n", + "was choosen in the previous step when generating the root, but you may choose a different one. By\n", + "default the common name is the same as the CN for the Root CA, minus the word 'Root'.

")) + } else { + return template.HTML("") + } +} + +func setupHandler(w http.ResponseWriter, r *http.Request) { + if viper.GetBool("config.complete") == true { + render(w, r, "index:manage", map[string]interface{}{"Message": template.HTML("Setup already completed! Go home")}) + return + } + + // 1. Setup admin user + if viper.Get("user.password") == nil { + if r.Method == "GET" { + reg := &User{ + RequestBase: r.Header.Get("X-Request-Base"), + } + render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")}) + return + } else if r.Method == "POST" { + if err := r.ParseForm(); err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + + reg := &User{ + Name: r.Form.Get("username"), + Email: r.Form.Get("email"), + Password: r.Form.Get("password"), + Confirm: r.Form.Get("confirm"), + RequestBase: r.Header.Get("X-Request-Base"), + } + + if reg.Validate(true, false) == false { + render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")}) + return + } + + hash, err := bcrypt.GenerateFromPassword([]byte(reg.Password), bcrypt.MinCost) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + viper.Set("user.name", reg.Name) + viper.Set("user.email", reg.Email) + viper.Set("user.password", string(hash)) + viper.WriteConfig() + + session := getSession(w, r) + session.Values["user"] = reg.Name + session.Save(r, w) + + // Fake the method to GET as we need to continue in the setupHandler() function + r.Method = "GET" + } else { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther) + return + } + } + + // 2. Setup essential configuration + if viper.Get("labca.dns") == nil { + if r.Method == "GET" { + domain := viper.GetString("labca.fqdn") + pos := strings.Index(domain, ".") + if pos > -1 { + pos = pos + 1 + domain = domain[pos:] + } + + cfg := &SetupConfig{ + Fqdn: viper.GetString("labca.fqdn"), + DomainMode: "lockdown", + LockdownDomains: domain, + WhitelistDomains: domain, + RequestBase: r.Header.Get("X-Request-Base"), + } + + render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")}) + return + } else if r.Method == "POST" { + if err := r.ParseForm(); err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + + cfg := &SetupConfig{ + Fqdn: r.Form.Get("fqdn"), + Dns: r.Form.Get("dns"), + DomainMode: r.Form.Get("domain_mode"), + LockdownDomains: r.Form.Get("lockdown_domains"), + WhitelistDomains: r.Form.Get("whitelist_domains"), + RequestBase: r.Header.Get("X-Request-Base"), + } + + if cfg.Validate(false) == false { + render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")}) + return + } + + matched, err := regexp.MatchString(":\\d+$", cfg.Dns) + if err == nil && !matched { + cfg.Dns += ":53" + } + + viper.Set("labca.fqdn", cfg.Fqdn) + viper.Set("labca.dns", cfg.Dns) + viper.Set("labca.domain_mode", cfg.DomainMode) + if cfg.DomainMode == "lockdown" { + viper.Set("labca.lockdown", cfg.LockdownDomains) + } + if cfg.DomainMode == "whitelist" { + viper.Set("labca.whitelist", cfg.WhitelistDomains) + } + viper.WriteConfig() + + // Fake the method to GET as we need to continue in the setupHandler() function + r.Method = "GET" + } else { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther) + return + } + } + + // 3. Setup root CA certificate + if !_certCreate(w, r, "root-ca", true) { + return + } + + // 4. Setup issuer certificate + if !_certCreate(w, r, "ca-int", false) { + return + } + + // 5. Apply configuration / populate with certificate info + err := _applyConfig() + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + + if !viper.GetBool("config.restarted") { + // 6. Trust the new certs + if !_hostCommand(w, r, "trust-store") { + return + } + + // Don't let the retry mechanism generate new restartSecret! + if r.Header.Get("X-Requested-With") == "XMLHttpRequest" { + render(w, r, "index", map[string]interface{}{"Message": "Retry OK"}) + } else { + // 8. Restart application + restartSecret = randToken() + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/wait?restart="+restartSecret, http.StatusFound) + } + return + + } else { + render(w, r, "wrapup:manage", map[string]interface{}{"Progress": _progress("wrapup"), "HelpText": _helptext("wrapup")}) + } +} + +func waitHandler(w http.ResponseWriter, r *http.Request) { + if viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound) + return + } + + render(w, r, "polling:manage", map[string]interface{}{"Progress": _progress("polling"), "HelpText": _helptext("polling")}) +} + +func restartHandler(w http.ResponseWriter, r *http.Request) { + if viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound) + return + } + + if strings.Compare(r.URL.Query().Get("token"), restartSecret) != 0 { + log.Println("WARNING: Restart token ('" + r.URL.Query().Get("token") + "') does not match our secret ('" + restartSecret + "')!") + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + viper.Set("config.restarted", true) + viper.WriteConfig() + + if !_hostCommand(w, r, "docker-restart") { + viper.Set("config.restarted", false) + viper.WriteConfig() + return + } +} + +func finalHandler(w http.ResponseWriter, r *http.Request) { + if viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound) + return + } + + // Don't let the retry mechanism trigger a certificate request and restart! + if r.Header.Get("X-Requested-With") == "XMLHttpRequest" { + render(w, r, "index", map[string]interface{}{"Message": "Retry OK"}) + } else { + // 9. Setup our own web certificate + if !_hostCommand(w, r, "acme-request") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/logs/cert", http.StatusSeeOther) + return + } + + // 10. remove the temporary bit from nginx config + if !_hostCommand(w, r, "nginx-remove-redirect") { + return + } + + // 11. reload nginx + if !_hostCommand(w, r, "nginx-reload") { + return + } + + viper.Set("config.complete", true) + viper.WriteConfig() + + render(w, r, "final:manage", map[string]interface{}{"RequestBase": r.Header.Get("X-Request-Base"), "Progress": _progress("final"), "HelpText": _helptext("final")}) + } +} + +// RangeStructer takes the first argument, which must be a struct, and +// returns the value of each field in a slice. It will return nil +// if there are no arguments or first argument is not a struct +func RangeStructer(args ...interface{}) []interface{} { + if len(args) == 0 { + return nil + } + + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Struct { + return nil + } + + out := make([]interface{}, v.NumField()) + for i := 0; i < v.NumField(); i++ { + switch v.Field(i).Kind() { + case reflect.String: + if v.Field(i).Type().String() == "template.HTML" { + out[i] = template.HTML(v.Field(i).String()) + } else { + out[i] = v.Field(i).String() + } + case reflect.Bool: + out[i] = v.Field(i).Bool() + default: + out[i] = v.Field(i) + } + } + + return out +} + +func accountsHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + Accounts, err := GetAccounts(w, r) + if err == nil { + render(w, r, "list:accounts", map[string]interface{}{"List": Accounts}) + } +} + +func accountHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + errorHandler(w, r, err, http.StatusBadRequest) + return + } + + AccountDetails, err := GetAccount(w, r, id) + if err == nil { + render(w, r, "show:accounts", map[string]interface{}{"Details": AccountDetails}) + } +} + +func ordersHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + Orders, err := GetOrders(w, r) + if err == nil { + render(w, r, "list:orders", map[string]interface{}{"List": Orders}) + } +} + +func orderHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + errorHandler(w, r, err, http.StatusBadRequest) + return + } + + OrderDetails, err := GetOrder(w, r, id) + if err == nil { + render(w, r, "show:orders", map[string]interface{}{"Details": OrderDetails}) + } +} + +func authzHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + Authz, err := GetAuthz(w, r) + if err == nil { + render(w, r, "list:authz", map[string]interface{}{"List": Authz}) + } +} + +func authHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + vars := mux.Vars(r) + id := vars["id"] + + AuthDetails, err := GetAuth(w, r, id) + if err == nil { + render(w, r, "show:authz", map[string]interface{}{"Details": AuthDetails}) + } +} + +func challengesHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + Challenges, err := GetChallenges(w, r) + if err == nil { + render(w, r, "list:challenges", map[string]interface{}{"List": Challenges}) + } +} + +func challengeHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + errorHandler(w, r, err, http.StatusBadRequest) + return + } + + ChallengeDetails, err := GetChallenge(w, r, id) + if err == nil { + render(w, r, "show:challenges", map[string]interface{}{"Details": ChallengeDetails}) + } +} + +func certificatesHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + Certificates, err := GetCertificates(w, r) + if err == nil { + render(w, r, "list:certificates", map[string]interface{}{"List": Certificates}) + } +} + +func certificateHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound) + return + } + + var serial string + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + serial = vars["id"] + } + + CertificateDetails, err := GetCertificate(w, r, id, serial) + if err == nil { + render(w, r, "show:certificates", map[string]interface{}{"Details": CertificateDetails}) + } +} + +func certRevokeHandler(w http.ResponseWriter, r *http.Request) { + if !viper.GetBool("config.complete") { + errorHandler(w, r, errors.New("Method not allowed at this point"), http.StatusMethodNotAllowed) + return + } + + if r.Method == "POST" { + if err := r.ParseForm(); err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + + serial := r.Form.Get("serial") + reason, err := strconv.Atoi(r.Form.Get("reason")) + if err != nil { + errorHandler(w, r, err, http.StatusBadRequest) + return + } + + if !_hostCommand(w, r, "revoke-cert", serial, strconv.Itoa(reason)) { + return + } + } +} + +type navItem struct { + Name string + Icon string + Attrs map[template.HTMLAttr]string + IsActive bool + SubMenu []navItem +} + +func activeNav(active string, uri string, requestBase string) []navItem { + isAcmeActive := (uri == "/accounts" || strings.HasPrefix(uri, "/accounts/") || + uri == "/orders" || strings.HasPrefix(uri, "/orders/") || + uri == "/authz" || strings.HasPrefix(uri, "/authz/") || + uri == "/challenges" || strings.HasPrefix(uri, "/challenges/") || + uri == "/certificates" || strings.HasPrefix(uri, "/certificates/") || + false) + + // create menu items + home := navItem{ + Name: "Dashboard", + Icon: "fa-dashboard", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/", + "title": "Main page with the status of the system", + }, + } + accounts := navItem{ + Name: "Accounts", + Icon: "fa-list-alt", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/accounts", + "title": "ACME Accounts", + }, + } + orders := navItem{ + Name: "Orders", + Icon: "fa-tags", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/orders", + "title": "ACME Orders", + }, + } + authz := navItem{ + Name: "Authorizations", + Icon: "fa-chain", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/authz", + "title": "ACME Authorizations", + }, + } + challenges := navItem{ + Name: "Challenges", + Icon: "fa-exchange", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/challenges", + "title": "ACME Challenges", + }, + } + certificates := navItem{ + Name: "Certificates", + Icon: "fa-lock", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/certificates", + "title": "ACME Certificates", + }, + } + acme := navItem{ + Name: "ACME", + Icon: "fa-sitemap", + Attrs: map[template.HTMLAttr]string{ + "href": "#", + "title": "Automated Certificate Management Environment", + }, + IsActive: isAcmeActive, + SubMenu: []navItem{accounts, certificates, orders, authz, challenges}, + } + cert := navItem{ + Name: "Web Certificate", + Icon: "fa-lock", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/logs/cert", + "title": "Log file for the certificate renewal for this server", + }, + } + boulder := navItem{ + Name: "ACME", + Icon: "fa-search-plus", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/logs/boulder", + "title": "Live view on the backend ACME application logs", + }, + } + audit := navItem{ + Name: "ACME Audit Log", + Icon: "fa-paw", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/logs/audit", + "title": "Live view on only the audit messages in the backend ACME application logs", + }, + } + labca := navItem{ + Name: "LabCA", + Icon: "fa-edit", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/logs/labca", + "title": "Live view on the logs for this LabCA web application", + }, + } + web := navItem{ + Name: "Web Access", + Icon: "fa-globe", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/logs/web", + "title": "Live view on the NGINX web server access log", + }, + } + weberr := navItem{ + Name: "Web Error", + Icon: "fa-times", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/logs/weberr", + "title": "Log file for the NGINX web server error log", + }, + } + logs := navItem{ + Name: "Logs", + Icon: "fa-files-o", + Attrs: map[template.HTMLAttr]string{ + "href": "#", + "title": "Log Files", + }, + SubMenu: []navItem{cert, boulder, audit, labca, web, weberr}, + } + manage := navItem{ + Name: "Manage", + Icon: "fa-wrench", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/manage", + "title": "Manage the system", + }, + } + about := navItem{ + Name: "About", + Icon: "fa-comments", + Attrs: map[template.HTMLAttr]string{ + "href": requestBase + "/about", + "title": "About LabCA", + }, + } + public := navItem{ + Name: "Public Area", + Icon: "fa-home", + Attrs: map[template.HTMLAttr]string{ + "href": "/", + "title": "The non-Admin pages of this LabCA instance", + }, + } + + // set active menu class + switch active { + case "about": + about.Attrs["class"] = "active" + case "accounts": + accounts.Attrs["class"] = "active" + case "orders": + orders.Attrs["class"] = "active" + case "authz": + authz.Attrs["class"] = "active" + case "challenges": + challenges.Attrs["class"] = "active" + case "certificates": + certificates.Attrs["class"] = "active" + case "index": + home.Attrs["class"] = "active" + case "manage": + manage.Attrs["class"] = "active" + case "logs": + logs.Attrs["class"] = "active" + } + + return []navItem{home, acme, logs, manage, about, public} +} + +func render(w http.ResponseWriter, r *http.Request, view string, data map[string]interface{}) { + viewSlice := strings.Split(view, ":") + menu := viewSlice[0] + if len(viewSlice) > 1 { + menu = viewSlice[1] + } + data["Menu"] = activeNav(menu, r.RequestURI, r.Header.Get("X-Request-Base")) + + if version != "" { + data["Version"] = version + } + + b, err := tmpls.Render("base.tmpl", "views/"+viewSlice[0]+".tmpl", data) + if err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return + } + + w.Write(b) +} + +func notFoundHandler(w http.ResponseWriter, r *http.Request) { + errorHandler(w, r, fmt.Errorf("NotFoundHandler for: %s %s", r.Method, r.URL), http.StatusNotFound) +} + +func authorized(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method + " " + r.RequestURI) + + if r.RequestURI == "/login" || strings.Contains(r.RequestURI, "/static/") { + next.ServeHTTP(w, r) + } else { + session := getSession(w, r) + if session.Values["user"] != nil || (r.RequestURI == "/setup" && viper.Get("user.password") == nil) { + next.ServeHTTP(w, r) + } else { + session.Values["bounce"] = r.RequestURI + session.Save(r, w) + http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/login", http.StatusFound) + } + } + }) +} + +func init() { + if os.Getenv("DEVELOPMENT") != "" { + isDev = true + } + + var err error + tmpls, err = templates.New().ParseDir("./templates", "templates/") + if err != nil { + panic(fmt.Errorf("Fatal error templates: %s \n", err)) + } + tmpls.AddFunc("rangeStruct", RangeStructer) + + viper.SetConfigName("config") + viper.AddConfigPath("data") + viper.SetDefault("config.complete", false) + if err := viper.ReadInConfig(); err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + + if viper.Get("keys.auth") == nil { + key := securecookie.GenerateRandomKey(32) + if key == nil { + panic(fmt.Errorf("Fatal error random key\n")) + } + viper.Set("keys.auth", key) + viper.WriteConfig() + } + if viper.Get("keys.enc") == nil { + key := securecookie.GenerateRandomKey(32) + if key == nil { + panic(fmt.Errorf("Fatal error random key\n")) + } + viper.Set("keys.enc", key) + viper.WriteConfig() + } + + if viper.Get("server.addr") == nil { + viper.Set("server.addr", "0.0.0.0") + viper.WriteConfig() + } + + if viper.Get("server.port") == nil { + viper.Set("server.port", 3000) + viper.WriteConfig() + } + + if viper.Get("server.session.maxage") == nil { + viper.Set("server.session.maxage", 3600) // 1 hour + viper.WriteConfig() + } + + if viper.Get("db.conn") == nil { + viper.Set("db.type", "mysql") + viper.Set("db.conn", "root@tcp(boulder-mysql:3306)/boulder_sa_integration") + viper.WriteConfig() + } + dbConn = viper.GetString("db.conn") + dbType = viper.GetString("db.type") + + version = viper.GetString("version") +} + +func main() { + tmpls.Parse() + + sessionStore = sessions.NewCookieStore([]byte(viper.GetString("keys.auth")), []byte(viper.GetString("keys.enc"))) + sessionStore.Options = &sessions.Options{ + Path: "/", + MaxAge: viper.GetInt("server.session.maxage") * 1, + HttpOnly: true, + } + + r := mux.NewRouter() + r.HandleFunc("/", rootHandler).Methods("GET") + r.HandleFunc("/about", aboutHandler).Methods("GET") + r.HandleFunc("/manage", manageHandler).Methods("GET", "POST") + r.HandleFunc("/final", finalHandler).Methods("GET") + r.HandleFunc("/login", loginHandler).Methods("GET", "POST") + r.HandleFunc("/logout", logoutHandler).Methods("GET") + r.HandleFunc("/logs/{type}", logsHandler).Methods("GET") + r.HandleFunc("/restart", restartHandler).Methods("GET") + r.HandleFunc("/setup", setupHandler).Methods("GET", "POST") + r.HandleFunc("/wait", waitHandler).Methods("GET") + r.HandleFunc("/ws", wsHandler).Methods("GET") + + r.HandleFunc("/accounts", accountsHandler).Methods("GET") + r.HandleFunc("/accounts/{id}", accountHandler).Methods("GET") + r.HandleFunc("/orders", ordersHandler).Methods("GET") + r.HandleFunc("/orders/{id}", orderHandler).Methods("GET") + r.HandleFunc("/authz", authzHandler).Methods("GET") + r.HandleFunc("/authz/{id}", authHandler).Methods("GET") + r.HandleFunc("/challenges", challengesHandler).Methods("GET") + r.HandleFunc("/challenges/{id}", challengeHandler).Methods("GET") + r.HandleFunc("/certificates", certificatesHandler).Methods("GET") + r.HandleFunc("/certificates/{id}", certificateHandler).Methods("GET") + r.HandleFunc("/certificates/{id}", certRevokeHandler).Methods("POST") + + r.NotFoundHandler = http.HandlerFunc(notFoundHandler) + if isDev { + r.PathPrefix("/accounts/static/").Handler(http.StripPrefix("/accounts/static/", http.FileServer(http.Dir("../www")))) + r.PathPrefix("/authz/static/").Handler(http.StripPrefix("/authz/static/", http.FileServer(http.Dir("../www")))) + r.PathPrefix("/challenges/static/").Handler(http.StripPrefix("/challenges/static/", http.FileServer(http.Dir("../www")))) + r.PathPrefix("/certificates/static/").Handler(http.StripPrefix("/certificates/static/", http.FileServer(http.Dir("../www")))) + r.PathPrefix("/orders/static/").Handler(http.StripPrefix("/orders/static/", http.FileServer(http.Dir("../www")))) + r.PathPrefix("/logs/static/").Handler(http.StripPrefix("/logs/static/", http.FileServer(http.Dir("../www")))) + r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("../www")))) + } + r.Use(authorized) + + log.Printf("Listening on %s:%d...\n", viper.GetString("server.addr"), viper.GetInt("server.port")) + srv := &http.Server{ + Handler: r, + Addr: viper.GetString("server.addr") + ":" + viper.GetString("server.port"), + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + log.Fatal(srv.ListenAndServe()) +} diff --git a/gui/setup.sh b/gui/setup.sh new file mode 100755 index 0000000..71e299f --- /dev/null +++ b/gui/setup.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +[ -d bin ] || mkdir bin + +[ -e bin/labca ] || set -ev +if [ ! -e bin/labca ]; then + go get github.com/biz/templates + go get github.com/go-sql-driver/mysql + go get github.com/dustin/go-humanize + go get github.com/gorilla/mux + go get github.com/gorilla/securecookie + go get github.com/gorilla/sessions + go get github.com/gorilla/websocket + go get github.com/nbutton23/zxcvbn-go + go get github.com/theherk/viper + go get golang.org/x/crypto/bcrypt + + go build -o bin/labca main.go acme.go certificate.go dashboard.go +fi + +bin/labca diff --git a/gui/templates/base.tmpl b/gui/templates/base.tmpl new file mode 100644 index 0000000..56651f8 --- /dev/null +++ b/gui/templates/base.tmpl @@ -0,0 +1,46 @@ + + + + + + + + + + {{ block "title" . }}{{ if .Title }}{{ .Title }} | {{ end }}LabCA{{ end }} + {{ block "css" . }}{{ template "partials/css.tmpl" . }}{{ end }} + + {{ block "head" . }}{{ .Head }}{{ end }} + + + +
+ {{ block "nav" . }}{{ template "partials/nav.tmpl" . }}{{ end }} + +
+
+
+
+ {{ block "body" . }}{{ .Body }}{{ end }} +
+
+
+ +
+
+ + +
+
+ +
+
+ + {{ block "js" . }}{{ template "partials/js.tmpl" . }}{{ end }} + {{ block "tail" . }}{{ .Tail }}{{ end }} + + diff --git a/gui/templates/partials/css.tmpl b/gui/templates/partials/css.tmpl new file mode 100644 index 0000000..94d9292 --- /dev/null +++ b/gui/templates/partials/css.tmpl @@ -0,0 +1,10 @@ +{{ if .Css }} + {{ range .Css }} + {{ end }} +{{ else }} + + + + + +{{ end }} diff --git a/gui/templates/partials/js.tmpl b/gui/templates/partials/js.tmpl new file mode 100644 index 0000000..7534dff --- /dev/null +++ b/gui/templates/partials/js.tmpl @@ -0,0 +1,10 @@ +{{ if .Js }} + {{ range .Js }} + {{ end }} +{{ else }} + + + + + +{{ end }} diff --git a/gui/templates/partials/nav.tmpl b/gui/templates/partials/nav.tmpl new file mode 100644 index 0000000..5bb1e57 --- /dev/null +++ b/gui/templates/partials/nav.tmpl @@ -0,0 +1,52 @@ +{{ if .Menu }} + + +{{ end }} diff --git a/gui/templates/partials/progress.tmpl b/gui/templates/partials/progress.tmpl new file mode 100644 index 0000000..03376d6 --- /dev/null +++ b/gui/templates/partials/progress.tmpl @@ -0,0 +1,12 @@ + +
+
+
+
+
+
+ {{ if .HelpText }} +
+ {{ .HelpText }} +
+ {{ end }} diff --git a/gui/templates/views/about.tmpl b/gui/templates/views/about.tmpl new file mode 100644 index 0000000..394b8c0 --- /dev/null +++ b/gui/templates/views/about.tmpl @@ -0,0 +1,23 @@ +{{ define "body" }} +

About LabCA

+

More and more websites and applications are served over HTTPS, where all traffic between your browser and the web server is encrypted. With standard HTTP the (form) data is unencrypted and open to eavesdroppers and hackers listening to communications between the user and the website. Therefore the Chrome browser now even warns about unsafe plain HTTP sites to nudge users towards HTTPS.

+ +

To a lesser extent this also applies to internal applications and sites that are not exposed publicly. Just because the users may have a higher level of trust versus users of a public facing website doesn't mean sensitive content shouldn't be protected as much as possible. Lots of hacking and theft occur from within a company's own walls, virtual or real. Also, no user should get used to ignoring any browser warnings (e.g. about self-signed certificates), even for internal sites.

+ +

For the public internet, Let's Encrypt™ has made a big impact by providing free HTTPS certificates in an easy and automated way. There are many clients available to interact with their so called ACME (Automated Certificate Management Environment). They also have a staging environment that allows you to get things right before issuing trusted certificates and reduce the chance of your running up against rate limits.

+ +
+

We want to create a more secure and privacy-respecting Web

+ Let's Encrypt™ +
+ +

One technical requirement however is to have a publicly reachable location where your client application and their server can exchange information. For intranet / company internal applications or for testing clients within your organization this may not always be feasible.

+ +

Luckily they have made the core of their application, called "Boulder", available as open source. It is possible to install Boulder on your own server and use it internally to hand out certificates. As long as all client machines / laptops in your organization trust your root CA certificate, all certificates it signed are trusted automatically and users see a green lock icon in their browsers.

+ +

Also if you are developing your own client application or integrating one into your own application, a local test ACME can be very handy. There is a lot of information on the internet about setting up your own PKI (Public Key Infrastructure) but those are usually not automated.

+ +

Getting Boulder up and running has quite a learning curve though and that is where LabCA comes in. It is a self-contained installation with a nice web GUI built on top of Boulder so you can quickly start using it. All regular management tasks can be done from the web interface. It is best installed in a Virtual Machine and uses Debian Linux as a base.

+ +

NOTE: although LabCA tries to be as robust as possible, use it at your own risk. If you depend on it, make sure that you know what you are doing!

+{{ end }} diff --git a/gui/templates/views/cert.tmpl b/gui/templates/views/cert.tmpl new file mode 100644 index 0000000..2a1d7ce --- /dev/null +++ b/gui/templates/views/cert.tmpl @@ -0,0 +1,154 @@ +{{ define "body" }} +
+
+{{with .CertificateInfo}} +

{{ if .IsRoot }}Root{{ else }}Issuer (2nd level){{ end }} Certificate

+ + + +
+
+
+
+ + +
+ + + {{ with .Errors.KeyType }} + {{ . }} + {{ end }} +
+
+ + + {{ with .Errors.Country }} + {{ . }} + {{ end }} +
+
+ + + {{ with .Errors.Organization }} + {{ . }} + {{ end }} +
+
+ + + {{ with .Errors.OrgUnit }} + {{ . }} + {{ end }} +
+
+ + + {{ with .Errors.CommonName }} + {{ . }} + {{ end }} +
+
+ {{ with .Errors.Generate }} + {{ . }}
+ {{ end }} + +
+
+
+ +
+
+
+ + +

+ Here you can import a certificate that was exported from another LabCA instance, either in the .pfx or in the .zip format.
+ If you have separate key and certificate files, use the + Upload tab. +

+
+ + +
+
+
+
+ +
+
+
+ +
+
+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ {{ with .Errors.Upload }} + {{ . }}
+ {{ end }} + +
+
+
+
+{{end}} +{{ template "partials/progress.tmpl" . }} +{{end}} + +{{ define "tail" }} + +{{end}} diff --git a/gui/templates/views/dashboard.tmpl b/gui/templates/views/dashboard.tmpl new file mode 100644 index 0000000..5f9bc35 --- /dev/null +++ b/gui/templates/views/dashboard.tmpl @@ -0,0 +1,174 @@ +{{ define "body" }} +

LabCA

+
+ + +
+
+
+
+
+
+ +
+
+
{{ .NumAccounts }}
+
Accounts
+
+
+
+ + + +
+
+ + +
+
+
+
+
+ +
+
+
{{ .NumCerts }}
+
Certificates
+
+
+
+ + + +
+
+ +
+
+
+
+
+ +
+
+
{{ .NumExpired }}
+
Expired Certs
+
+
+
+ + + +
+
+ +
+
+
+
+
+ +
+
+
{{ .NumRevoked }}
+
Revoked Certs
+
+
+
+ + + +
+
+
+ +
+
+
+
+ Boulder (ACME) Activity +
+
+
+ {{ range $item := .Activity }} +
+ {{ $item.Message }} + {{ $item.TimestampRel }} +
+ {{ end }} +
+
+ + + +
+
+ +
+
+
+ System Overview +
+
+
+ + + {{ range $item := .Components }} + + + + + {{ end }} + +
{{ $item.Name }} + {{ $item.TimestampRel }} + {{ if $item.Class }}{{ end }} + +
+ + + + {{ range $item := .Stats }} + + + + {{ $item.Value }} + {{ if $item.Class }}{{ end }} + + + + {{ end }} + +
{{ $item.Name }}
+
+
+ + + +
+ +{{ end }} diff --git a/gui/templates/views/error.tmpl b/gui/templates/views/error.tmpl new file mode 100644 index 0000000..53d07a4 --- /dev/null +++ b/gui/templates/views/error.tmpl @@ -0,0 +1,4 @@ +{{ define "body" }} +

OOPS

+

{{ .Message }}

+{{ end }} diff --git a/gui/templates/views/final.tmpl b/gui/templates/views/final.tmpl new file mode 100644 index 0000000..a72fd6e --- /dev/null +++ b/gui/templates/views/final.tmpl @@ -0,0 +1,9 @@ +{{ define "body" }} +
+
+

LabCA

+

Congratulations! You are now done setting up LabCA!

+

You probably need to restart this browser to pickup the new certificate, it is not sufficient to just refresh the page.

+

Then go to the admin/ page or to the public homepage.

+{{ template "partials/progress.tmpl" . }} +{{end}} diff --git a/gui/templates/views/index.tmpl b/gui/templates/views/index.tmpl new file mode 100644 index 0000000..ab739b5 --- /dev/null +++ b/gui/templates/views/index.tmpl @@ -0,0 +1,4 @@ +{{ define "body" }} +

TODO

+ {{ .Message }} +{{ end }} diff --git a/gui/templates/views/list.tmpl b/gui/templates/views/list.tmpl new file mode 100644 index 0000000..0cf1e07 --- /dev/null +++ b/gui/templates/views/list.tmpl @@ -0,0 +1,34 @@ +{{ define "body" }} +{{with .List}} +

LabCA {{ .Title }}

+ + + + + {{ range .Header }} + + {{ end }} + + + + {{ range .Rows }} + + {{ range rangeStruct . }} + + {{ end }} + + {{ end }} + +
{{ . }}
{{ . }}
+{{end}} +{{end}} + +{{ define "head" }} + +{{ end }} + +{{ define "tail" }} + + + +{{ end }} diff --git a/gui/templates/views/login.tmpl b/gui/templates/views/login.tmpl new file mode 100644 index 0000000..5f551e6 --- /dev/null +++ b/gui/templates/views/login.tmpl @@ -0,0 +1,23 @@ +{{ define "body" }} +

Login

+ +{{with .User}} +
+
+ + + {{ with .Errors.Name }} + {{ . }} + {{ end }} +
+
+ + + {{ with .Errors.Password }} + {{ . }} + {{ end }} +
+ +
+{{end}} +{{end}} diff --git a/gui/templates/views/logs.tmpl b/gui/templates/views/logs.tmpl new file mode 100644 index 0000000..7c7eb6e --- /dev/null +++ b/gui/templates/views/logs.tmpl @@ -0,0 +1,39 @@ +{{ define "body" }} +

{{ .Name }}

+ {{ .Message }} +
{{.Data}}
+ {{ if .WsUrl }} +
+ Autoscroll: +
+ {{ end }} +{{ end }} + +{{ define "tail" }} + +{{ end }} diff --git a/gui/templates/views/manage.tmpl b/gui/templates/views/manage.tmpl new file mode 100644 index 0000000..9c63bea --- /dev/null +++ b/gui/templates/views/manage.tmpl @@ -0,0 +1,727 @@ +{{ define "body" }} +

Manage LabCA

+

Here you can manage your LabCA instance. Be careful!

+ + + +
+
+
+
+
+ + + {{ range $item := .Components }} + + + + + + + {{ end }} + + + + + + + +
{{ $item.Name }} + + {{ if ne $item.TimestampRel "stopped" }}Up {{ end }}{{ $item.TimestampRel }} + + {{ if or $item.LogUrl $item.LogTitle}}{{ end }} + {{ range $btn := $item.Buttons }} + + {{ end }} +
ServerUp {{ .ServerTimestampRel }} + +
+ +
+
+
+
+ +
+
+
+
+ + + {{ range .BackupFiles }} + + + + + {{ end }} + +
{{ . }} + + +
+ + +
+
+
+ Backups are automatically created once a week. Here you can manually create extra backups, delete + backups or restore a selected backup. After restoring the server will be automatically restarted.
+ Automatic backups are removed after one month, manual backups are kept until manually deleted. +
+
+
+
+ +
+
+
+
+
+ + Root Certificate (Details ) +
{{ .RootDetails }}
+
+ + Issuer Certificate (Details ) +
{{ .IssuerDetails }}
+
+ +
+
+ .pfx (PKCS#12) file
+ .zip archive (less secure)
+
+ +
+ + + + + + + +
+ + + + +
+
+ +
+
+
+
+ + + + +
+
+ + + +
+
+ + + +
+ +
+
+ + Lockdown to only this domain:
+ + +
+ + Next to all official domains, also allow this domain:
+ + +
+ + Standard - any official domains
+
+
+ + +   Are you sure? This facilitates man-in-the-middle attacks!
+
+
+
+ +
+
+
+
+ + Send emails about upcoming certificate expiration +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+ +
+
+ + + + +
+
+ + + +
+
+ + +
+
+
+
+ + + + +{{end}} + +{{ define "tail" }} + + + + +{{ end }} diff --git a/gui/templates/views/polling.tmpl b/gui/templates/views/polling.tmpl new file mode 100644 index 0000000..a8b6d67 --- /dev/null +++ b/gui/templates/views/polling.tmpl @@ -0,0 +1,13 @@ +{{ define "body" }} +
+
+

Restart

+

+ Please install the root certificate in the Trusted Root Certification Authorities store of this machine now.
+ Windows (.der) format | Linux (.pem) format +

+

Then, restart LabCA

+ +{{ template "partials/progress.tmpl" . }} +{{end}} diff --git a/gui/templates/views/register.tmpl b/gui/templates/views/register.tmpl new file mode 100644 index 0000000..435b340 --- /dev/null +++ b/gui/templates/views/register.tmpl @@ -0,0 +1,58 @@ +{{ define "body" }} +
+
+

Create admin account

+ +{{with .User}} +
+
+ + + {{ with .Errors.Name }} + {{ . }} + {{ end }} +
+
+ + + {{ with .Errors.Email }} + {{ . }} + {{ end }} +
+
+ + + +
+
+
+ {{ with .Errors.Password }} + {{ . }} + {{ end }} +
+
+ + + + {{ with .Errors.Confirm }} + {{ . }} + {{ end }} +
+
+ +
+
+{{end}} +{{ template "partials/progress.tmpl" . }} +{{end}} + +{{ define "tail" }} + + + +{{ end }} diff --git a/gui/templates/views/revoke-partial.tmpl b/gui/templates/views/revoke-partial.tmpl new file mode 100644 index 0000000..1c78b9c --- /dev/null +++ b/gui/templates/views/revoke-partial.tmpl @@ -0,0 +1,33 @@ + + + diff --git a/gui/templates/views/setup.tmpl b/gui/templates/views/setup.tmpl new file mode 100644 index 0000000..62d78c0 --- /dev/null +++ b/gui/templates/views/setup.tmpl @@ -0,0 +1,49 @@ +{{ define "body" }} +
+
+

Basic configuration

+ +{{with .SetupConfig}} +
+
+ + + {{ with .Errors.Fqdn }} + {{ . }} + {{ end }} +
+
+ + + {{ with .Errors.Dns }} + {{ . }} + {{ end }} +
+ +
+
+ {{ with .Errors.DomainMode }} + {{ . }}
+ {{ end }} + Lockdown to only this domain:
+
+ {{ with .Errors.LockdownDomains }} + {{ . }}
+ {{ end }} + + Next to all official domains, also allow this domain:
+
+ {{ with .Errors.WhitelistDomains }} + {{ . }}
+ {{ end }} + + Standard - any official domains

+
+
+ +   Are you sure? This facilitates man-in-the-middle attacks!
+
+
+{{end}} +{{ template "partials/progress.tmpl" . }} +{{end}} diff --git a/gui/templates/views/show.tmpl b/gui/templates/views/show.tmpl new file mode 100644 index 0000000..20250c0 --- /dev/null +++ b/gui/templates/views/show.tmpl @@ -0,0 +1,82 @@ +{{ define "body" }} +{{ with .Details }} +

LabCA {{ .Title }}

+ + + + {{ range .Rows }} + + + + + {{ end }} + {{ range .Links }} + + + + + {{ end }} + +
{{ .Name }}{{ .Value }}
{{ .Name }}{{ .Value }}
+ {{ if .Extra }}{{ range $extra := .Extra }} + {{ $extra }} + {{ end }}{{ end }} +
+ + {{ range .Related }} +

{{ .Title }}

+ + + + {{ range .Header }} + + {{ end }} + + + + {{ range .Rows }} + + {{ range rangeStruct . }} + + {{ end }} + + {{ end }} + + + {{ end }} + + {{ if .Related2 }} + {{ range .Related2 }} +

{{ .Title }}

+ + + + {{ range .Header }} + + {{ end }} + + + + {{ range .Rows }} + + {{ range rangeStruct . }} + + {{ end }} + + {{ end }} + + + {{ end }} + {{ end }} +{{end}} +{{end}} + +{{ define "head" }} + +{{ end }} + +{{ define "tail" }} + + + +{{ end }} diff --git a/gui/templates/views/wrapup.tmpl b/gui/templates/views/wrapup.tmpl new file mode 100644 index 0000000..73480db --- /dev/null +++ b/gui/templates/views/wrapup.tmpl @@ -0,0 +1,8 @@ +{{ define "body" }} +
+
+

Restart

+

Almost there! Now we will request a certificate for this website and restart one more time...
+

+{{ template "partials/progress.tmpl" . }} +{{end}} diff --git a/init_d b/init_d new file mode 100755 index 0000000..4ee99be --- /dev/null +++ b/init_d @@ -0,0 +1,98 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: +# Required-Start: $remote_fs $syslog $network +# Required-Stop: $remote_fs $syslog $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start LabCA commander daemon at boot time +# Description: Enable service provided by LabCA commander daemon. +### END INIT INFO + +cmd="tcpserver $(docker inspect --format "{{ .NetworkSettings.Networks.boulder_bluenet.Gateway }}" boulder_labca_1) 3030 /home/labca/labca/commander" +user="" + +name=`basename $0` +pid_file="/var/run/$name.pid" +stdout_log="/var/log/$name.log" +stderr_log="/var/log/$name.err" + +get_pid() { + cat "$pid_file" +} + +is_running() { + [ -f "$pid_file" ] && ps -p `get_pid` > /dev/null 2>&1 +} + +case "$1" in + start) + if is_running; then + echo "Already started" + else + echo "Starting $name" + if [ -z "$user" ]; then + sudo $cmd >> "$stdout_log" 2>> "$stderr_log" & + else + sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" & + fi + echo $! > "$pid_file" + if ! is_running; then + echo "Unable to start, see $stdout_log and $stderr_log" + exit 1 + fi + fi + ;; + stop) + if is_running; then + echo -n "Stopping $name.." + kill `get_pid` + for i in 1 2 3 4 5 6 7 8 9 10 + # for i in `seq 10` + do + if ! is_running; then + break + fi + + echo -n "." + sleep 1 + done + echo + + if is_running; then + echo "Not stopped; may still be shutting down or shutdown may have failed" + exit 1 + else + echo "Stopped" + if [ -f "$pid_file" ]; then + rm "$pid_file" + fi + fi + else + echo "Not running" + fi + ;; + restart) + $0 stop + if is_running; then + echo "Unable to stop, will not attempt to start" + exit 1 + fi + $0 start + ;; + status) + if is_running; then + echo "Running" + else + echo "Stopped" + exit 1 + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac + +exit 0 + diff --git a/install b/install new file mode 100755 index 0000000..ff8a074 --- /dev/null +++ b/install @@ -0,0 +1,658 @@ +#!/usr/bin/env bash + +# LabCA: a private Certificate Authority for internal lab usage +# (c) 2018 Arjan Hakkesteegt +# +# Install with this command from a Linux machine (only tested with Debian 9): +# curl -sSL https://raw.githubusercontent.com/hakwerk/labca/master/install | bash + +set -e + +# +# Variables / Constants +# +baseDir=/home/labca +logDir="$baseDir/logs" +runId="`date +%y%m%d-%H%M%S`" +installLog="$logDir/install-${runId}.log" +logTimeFormat="+%Y-%m-%d %T.%3N" +cloneDir="$baseDir/labca" +adminDir="$baseDir/admin" +boulderDir="$baseDir/boulder" +boulderLabCADir="${boulderDir}_labca" +dockerComposeVersion="1.22.0" + +labcaUrl="https://github.com/hakwerk/labca/" +boulderUrl="https://github.com/letsencrypt/boulder/" +boulderTag="release-2018-11-05" + +# +# Color configuration +# +COL_NC='\e[0m' # No Color +COL_LIGHT_GREEN='\e[1;32m' +COL_LIGHT_RED='\e[1;31m' +TICK="[${COL_LIGHT_GREEN}✓${COL_NC}]" +CROSS="[${COL_LIGHT_RED}✗${COL_NC}]" +INFO="[i]" +DONE="${COL_LIGHT_GREEN} done!${COL_NC}" +OVER="\\r\\033[K" + +dn=$(dirname $0) +source "$dn/utils.sh" + +# +# Helper functions for informing the user and logging to file +# +msg_info() { + local msg="$1" + echo -ne " ${INFO} ${msg}..." + echo "[`date "${logTimeFormat}"`] [INFO ] ${msg}..." >> $installLog +} + +msg_ok() { + local msg="$1" + echo -e "${OVER} ${TICK} ${msg}" + echo "[`date "${logTimeFormat}"`] [OK ] ${msg}" >> $installLog +} + +msg_err() { + local msg="$1" + echo -e "${OVER} ${CROSS} ${msg}" + echo "[`date "${logTimeFormat}"`] [ERROR] ${msg}" >> $installLog +} + +msg_fatal() { + local msg="$1" + echo -e "\\n ${COL_LIGHT_RED}Error: ${msg}${COL_NC}\\n" + echo "[`date "${logTimeFormat}"`] [FATAL] ${msg}" >> $installLog + exit 1 +} + +# +# Log to /tmp first in case the labca user doesn't exist yet +# +start_temporary_log() { + backupLog=$installLog + installLog="/tmp/labca-install.log" + touch "$installLog" +} + +end_temporary_log() { + mv "$installLog" "$backupLog" + installLog=$backupLog + chown labca:labca "$installLog" +} + +# Must run as root +check_root() { + if [ "$EUID" -eq 0 ]; then + msg_ok "Running as root" + else + msg_err "Not running as root" + + local msg="Run using sudo" + if command -v sudo &> /dev/null; then + msg_ok "$msg" + exec curl -sSL https://raw.githubusercontent.com/hakwerk/labca/master/install | sudo bash "$@" + exit $? + else + msg_err "$msg" + echo -e " ${COL_LIGHT_RED}Script should be run as the root user${COL_NC}" + fi + fi +} + +# Create dedicated user +labca_user() { + adduser --gecos "LabCA,,," --disabled-login labca &>>$installLog && msg_ok "Created user 'labca'" || msg_ok "User 'labca' already exists" + [ -d "$logDir" ] || mkdir "$logDir" + chown -R labca:labca "$logDir" + + cd ~labca + local gig=$(sudo -u labca git config --global core.excludesfile 2>/dev/null) + if [ -z "$gig" ]; then + sudo -u labca git config --global core.excludesfile /home/labca/.gitignore_global >/dev/null 2>&1 || msg_info "WARNING: could not set core.excludesfile" + gig=$(sudo -u labca git config --global core.excludesfile 2>/dev/null) + fi + gig=${gig/\~/\/home\/labca} + gig=${gig:-/home/labca/.gitignore_global} + + [ -e "$gig" ] || sudo -u labca touch $gig + sudo -u labca grep config_labca "$gig" >/dev/null 2>&1 || sudo -u labca echo "config_labca/" >> "$gig" +} + +# +# Get the latest code from the git repository +# +clone_repo() { + local dir="$1" + local url="$2" + + local msg="Clone $url to $dir" + msg_info "$msg" + sudo -u labca git clone -q "$url" "$dir" &>>$installLog && msg_ok "$msg" || msg_fatal "Could not clone git repository" +} + +pull_repo() { + local dir="$1" + + cd "$dir" &>>$installLog || msg_fatal "Could not switch to directory '$dir'" + + local msg="Update git repository in $dir" + msg_info "$msg" + sudo -u labca git stash --all --quiet &>>$installLog || true + sudo -u labca git clean --quiet --force -d &>>$installLog || true + sudo -u labca git pull --quiet &>>$installLog && msg_ok "$msg" || msg_fatal "Could not update local repository" +} + +clone_or_pull() { + local dir="$1" + local url="$2" + + local parentdir=$(dirname "$dir") + local dirbase=$(basename "$dir") + + if [ -d "$dir" ]; then + local rc=0 + cd "$dir" + git status --short &> /dev/null || rc=$? + if [ $rc -gt 0 ]; then + cd "$parentdir" + mv "$dirbase" "${dirbase}_${runId}" && msg_ok "Backup existing non-git directory '$dir'" + clone_repo "$dir" "$url" + else + pull_repo "$dir" + fi + else + clone_repo "$dir" "$url" + fi +} + +# Restart the script if it was updated itself +restart_if_updated() { + local curChecksum="$1" + local gitRev=$(cd $cloneDir && git describe --always --tags) + echo "=== version $gitRev ($curChecksum) ===" >>$installLog + + if [ "$curChecksum" != "" ]; then + local newChecksum=$(md5sum $cloneDir/install 2>/dev/null | cut -d' ' -f1) + if [ "$curChecksum" != "$newChecksum" ]; then + msg_info "Restarting updated version of install script" + echo + exec $cloneDir/install + exit $? + fi + fi +} + +# Utility method to prompt the user for a config variable and export it +prompt_and_export() { + local varName="$1" + local varDefault="$2" + local promptMsg="$3" + local answer + + read -p "$promptMsg [$varDefault] " answer /dev/null | grep -v LABCA_FQDN | cut -d ":" -f 2- | tr -d " \",") + LABCA_FQDN=${cfgFqdn:-$(hostname -f)} + + local parsed=$(getopt --options=n: --longoptions=name:,fqdn: --name "$0" -- "$@" 2>>$installLog) || msg_fatal "Could not process commandline parameters" + eval set -- "$parsed" + local cmdlineFqdn + while true; do + case "$1" in + -n|--name|--fqdn) + cmdlineFqdn="$2" + shift 2 + ;; + --) + shift + break + ;; + *) + msg_fatal "Should not have reached this" + ;; + esac + done + + if [ "$cfgFqdn" == "" ]; then + if [ "$cmdlineFqdn" != "" ]; then + export LABCA_FQDN="$cmdlineFqdn" + else + prompt_and_export LABCA_FQDN "$LABCA_FQDN" "FQDN (Fully Qualified Domain Name) for this PKI host (users will use this in their browsers and clients)?" + fi + fi + + msg_ok "Determine web address" +} + +# Utility method to replace all instances of given variables in a file +replace_all() { + local filename="$1" + local var + + for var in ${@:2}; do + sed -i -e "s|$var|${!var}|g" $filename + done +} + +# Copy and configure the admin tree from the local repository +copy_admin() { + local rc=0 + + local msg="Setup admin application" + msg_info "$msg" + + [ -d "$adminDir" ] || mkdir "$adminDir" + cd "$adminDir" + git status --short &> /dev/null || rc=$? + if [ $rc -gt 0 ]; then + git init >>$installLog + fi + git add --all &>/dev/null || true + git commit --all --quiet -m "LabCA before update $runId" &>>$installLog && { msg_ok "Commit existing modifications of $adminDir"; msg_info "$msg"; } || true + + cp -rp $cloneDir/gui/* "./" &>>$installLog || msg_fatal "Cannot copy the admin files to $adminDir" + cp -p "$cloneDir/acme_tiny.py" "/home/labca/" &>>$installLog + + msg_ok "$msg" + msg="Configure the admin application" + msg_info "$msg" + + [ -e "$adminDir/data/config.json" ] || echo -e "{\n \"config\": {\n \"complete\": false\n },\n \"labca\": {\n \"fqdn\": \"$LABCA_FQDN\"\n },\n \"version\": \"\"\n}" > "$adminDir/data/config.json" + replace_all $adminDir/data/openssl.cnf LABCA_FQDN + replace_all $adminDir/data/issuer/openssl.cnf LABCA_FQDN + replace_all /home/labca/acme_tiny.py LABCA_FQDN + + cd "$cloneDir" + version=$(git describe --always HEAD^2 2>/dev/null || git describe --always HEAD 2>/dev/null) + cd "$adminDir" + grep \"version\" data/config.json &>/dev/null || sed -i -e 's/^}$/,\n "version": ""\n}/' data/config.json + sed -i -e "s/\"version\": \".*\"/\"version\": \"$version\"/" data/config.json + [ ! -e bin/labca ] || mv bin/labca bin/labca_prev + + chown -R labca:labca $baseDir + chown root:root "$cloneDir/cron_d" + + git add --all &>/dev/null || true + git commit --all --quiet -m "LabCA after update $runId" &>>$installLog || true + + msg_ok "$msg" +} + +# Update any outdated packages +update_upgrade() { + msg_info "Making sure all software is up-to-date" + apt update &>>$installLog + apt upgrade -y &>>$installLog + msg_ok "Software is up-to-date" +} + +# +# Install extra packages that we rely upon +# +install_pkg() { + local package="$1" + msg_info "Install package '$package'" + apt install -y "$package" &>>$installLog || msg_fatal "Could not install package '$package'" + msg_ok "Package '$package' is installed" +} + +install_extra() { + local packages=(apt-transport-https ca-certificates curl gnupg2 net-tools nginx software-properties-common tzdata ucspi-tcp zip) + for package in "${packages[@]}"; do + install_pkg "$package" + done + + curl -fsSL https://download.docker.com/linux/debian/gpg 2>>$installLog | apt-key add - &>>$installLog || msg_fatal "Could not download docker repository key" + add-apt-repository -r "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" &>>$installLog + add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" &>>$installLog + apt update &>>$installLog + install_pkg "docker-ce" + + msg_info "Install binary 'docker-compose'" + local dcver="" + [ -x /usr/local/bin/docker-compose ] && dcver="`/usr/local/bin/docker-compose --version`" + local vercmp=${dcver/$dockerComposeVersion/} + if [ "$dcver" == "$vercmp" ]; then + curl -sSL https://github.com/docker/compose/releases/download/$dockerComposeVersion/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose &>>$installLog || msg_fatal "Could not download docker-compose" + chmod +x /usr/local/bin/docker-compose + fi + msg_ok "Binary 'docker-compose' is installed" +} + +# Configure the static web pages (for end users) +static_web() { + local rc=0 + + local msg="Static web pages" + msg_info "$msg" + [ -e /etc/nginx/sites-available/labca ] || cp $cloneDir/nginx.conf /etc/nginx/sites-available/labca + [ -e /etc/nginx/sites-enabled/labca ] || ln -s ../sites-available/labca /etc/nginx/sites-enabled/ + rm -f /etc/nginx/sites-enabled/default + + cd /var/www/html + git status --short &> /dev/null || rc=$? + if [ $rc -gt 0 ]; then + git init >>$installLog + fi + git add --all &>/dev/null || true + git commit --all --quiet -m "LabCA before update $runId" &>>$installLog && { msg_ok "Commit existing modifications of $adminDir"; msg_info "$msg"; } || true + + mkdir -p .well-known/acme-challenge + mkdir -p crl + cp -rp $cloneDir/www/* . + sed -i -e "s|\[LABCA_CPS_LOCATION\]|http://$LABCA_FQDN/cps/|g" cps/index.html + sed -i -e "s|\[LABCA_CERTS_LOCATION\]|http://$LABCA_FQDN/certs/|g" cps/index.html + + local have_config=$(grep restarted $adminDir/data/config.json | grep true) + if [ "$have_config" != "" ]; then + export PKI_ROOT_CERT_BASE="$adminDir/data/root-ca" + export PKI_INT_CERT_BASE="$adminDir/data/issuer/ca-int" + export PKI_DEFAULT_O=$(grep organization $adminDir/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') + + $adminDir/apply-nginx + else + chown -R www-data:www-data . + fi + + git add --all &>/dev/null || true + git commit --all --quiet -m "LabCA after update $runId" &>>$installLog || true + + msg_ok "$msg" +} + +# Create a temporary self-signed certificate if there is no certificate yet +selfsigned_cert() { + if [ -e /etc/nginx/ssl/labca_cert.pem ]; then + msg_ok "Certificate is present" + else + local msg="Create self-signed certificate" + msg_info "$msg" + mkdir -p /etc/nginx/ssl + cd /etc/nginx/ssl + openssl req -x509 -nodes -sha256 -newkey rsa:2048 -keyout labca_key.pem -out labca_cert.pem -days 7 \ + -subj "/O=LabCA/CN=$LABCA_FQDN" -reqexts SAN -extensions SAN \ + -config <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nbasicConstraints=CA:FALSE\nnsCertType=server\nsubjectAltName=DNS:$LABCA_FQDN")) &>>$installLog + chown -R www-data:www-data labca_* + + service nginx restart &>>$installLog + msg_ok "$msg" + fi +} + +# Clone or update the boulder code (Let's Encrypt (tm) implementation of ACME protocol) +get_boulder() { + export GOPATH="/home/labca/gopath" + sudo -u labca mkdir -p "$GOPATH/src/github.com/letsencrypt" + [ -h "$boulderDir" ] || sudo -u labca ln -s "$GOPATH/src/github.com/letsencrypt/boulder" "$boulderDir" + + if [ -e "$boulderDir" ]; then + cd "$boulderDir" + git checkout -- docker-compose.yml errors/errors.go policy/pa.go + git reset --hard &>>$installLog + fi + + clone_or_pull "$GOPATH/src/github.com/letsencrypt/boulder" "$boulderUrl" + + cd "$boulderDir" + sudo -u labca git reset --hard $boulderTag &>>$installLog + msg_ok "Boulder checkout '$boulderTag'" +} + +# Configure boulder based on their test subdirectory +config_boulder() { + local msg="Setup boulder configuration folder" + msg_info "$msg" + + [ -d "$boulderLabCADir" ] || mkdir -p "$boulderLabCADir" + cd "$boulderLabCADir" + local rc=0 + git status --short &> /dev/null || rc=$? + if [ $rc -gt 0 ]; then + git init >>$installLog + fi + [ -d ".backup" ] || mkdir -p ".backup" + + git add --all &>/dev/null || true + git commit --all --quiet -m "LabCA before update $runId" &>>$installLog && { msg_ok "Commit existing modifications of $boulderLabCADir"; msg_info "$msg"; } || true + + [ ! -e "$boulderLabCADir/secrets/smtp_password" ] || mv "$boulderLabCADir/secrets/smtp_password" "$boulderLabCADir/secrets/smtp_password_PRESERVE" + cp -r "$boulderDir/test" -T "$boulderLabCADir" &>>$installLog + [ ! -e "$boulderLabCADir/secrets/smtp_password_PRESERVE" ] || mv "$boulderLabCADir/secrets/smtp_password_PRESERVE" "$boulderLabCADir/secrets/smtp_password" + chown -R labca:labca "$boulderLabCADir" + + msg_ok "$msg" + msg="Configure the boulder application" + msg_info "$msg" + + cd "$boulderDir" + sudo -u labca patch -p1 < $cloneDir/docker-compose.patch &>>$installLog + cp docker-compose.yml "$boulderLabCADir/.backup/" + + sudo -u labca patch -p1 < $cloneDir/policy_pa.patch &>>$installLog + cp policy/pa.go "$boulderLabCADir/.backup/" + + sudo -u labca patch -p1 < $cloneDir/mail_mailer.patch &>>$installLog + cp mail/mailer.go "$boulderLabCADir/.backup/" + + sudo -u labca patch -p1 < $cloneDir/expiration-mailer_main.patch &>>$installLog + cp cmd/expiration-mailer/main.go "$boulderLabCADir/.backup/" + + sudo -u labca patch -p1 < $cloneDir/notify-mailer_main.patch &>>$installLog + cp cmd/notify-mailer/main.go "$boulderLabCADir/.backup/" + + sudo -u labca patch -p1 -o "$boulderLabCADir/config/ca-a.json" < $cloneDir/test_config_ca_a.patch &>>$installLog + sudo -u labca patch -p1 -o "$boulderLabCADir/config/ca-b.json" < $cloneDir/test_config_ca_b.patch &>>$installLog + + sudo -u labca patch -p1 -o "$boulderLabCADir/config/expiration-mailer.json" < $cloneDir/config_expiration-mailer.patch &>>$installLog + + sed -i -e "s|https://letsencrypt.org/docs/rate-limits/|http://$LABCA_FQDN/rate-limits|" errors/errors.go &>>$installLog + cp errors/errors.go "$boulderLabCADir/.backup/" + + sed -i -e "s/\"150405/\"060102150405/" log/log.go &>>$installLog + cp log/log.go "$boulderLabCADir/.backup/" + + mkdir -p "cmd/mail-tester" + cp $cloneDir/mail-tester.go cmd/mail-tester/main.go + + cd "$boulderLabCADir" + sed -i -e "s/test-ca2.pem/test-ca.pem/" config/ocsp-responder.json + sed -i -e "s/test-ca2.pem/test-ca.pem/" config/ocsp-updater.json + sed -i -e "s/test-ca2.pem/test-ca.pem/" config/publisher.json + sed -i -e "s/test-ca2.pem/test-ca.pem/" config/wfe.json + sed -i -e "s/test-ca2.pem/test-ca.pem/" config/wfe2.json + sed -i -e "s/5001/443/g" config/va.json + sed -i -e "s/5002/80/g" config/va.json + sed -i -e "s|http://boulder:4000/terms/v1|http://$LABCA_FQDN/terms/v1|" config/wfe.json + sed -i -e "s|https://boulder:4431/terms/v7|https://$LABCA_FQDN/terms/v1|" config/wfe2.json + sed -i -e "s|http://boulder:4430/acme/issuer-cert|http://$LABCA_FQDN/acme/issuer-cert|" config/ca-a.json + sed -i -e "s|http://boulder:4430/acme/issuer-cert|http://$LABCA_FQDN/acme/issuer-cert|" config/ca-b.json + sed -i -e "s|http://127.0.0.1:4000/acme/issuer-cert|http://$LABCA_FQDN/acme/issuer-cert|" config/ca-a.json + sed -i -e "s|http://127.0.0.1:4000/acme/issuer-cert|http://$LABCA_FQDN/acme/issuer-cert|" config/ca-b.json + sed -i -e "s|http://boulder:4430/acme/issuer-cert|http://$LABCA_FQDN/acme/issuer-cert|" config/wfe2.json + sed -i -e "s|http://127.0.0.1:4000/acme/issuer-cert|https://$LABCA_FQDN/acme/issuer-cert|" config/wfe2.json + sed -i -e "s|http://127.0.0.1:4002/|http://$LABCA_FQDN/ocsp/|g" config/ca-a.json + sed -i -e "s|http://127.0.0.1:4002/|http://$LABCA_FQDN/ocsp/|g" config/ca-b.json + sed -i -e "s|http://example.com/cps|http://$LABCA_FQDN/cps/|g" config/ca-a.json + sed -i -e "s|http://example.com/cps|http://$LABCA_FQDN/cps/|g" config/ca-b.json + sed -i -e "s|1.2.3.4|1.3.6.1.4.1.44947.1.1.1|g" config/ca-a.json + sed -i -e "s|1.2.3.4|1.3.6.1.4.1.44947.1.1.1|g" config/ca-b.json + sed -i -e 's| "crl_url": "http://example.com/crl",||g' config/ca-a.json + sed -i -e 's| "crl_url": "http://example.com/crl",||g' config/ca-b.json + sed -i -e "s/Do What Thou Wilt/This PKI is only meant for internal (lab) usage; do NOT use this on the open internet\!/g" config/ca-a.json + sed -i -e "s/Do What Thou Wilt/This PKI is only meant for internal (lab) usage; do NOT use this on the open internet\!/g" config/ca-b.json + sed -i -e "s/ocspURL.Path = encodedReq/ocspURL.Path += encodedReq/" ocsp/helper/helper.go + sed -i -e "s/\"dnsTimeout\": \".*\"/\"dnsTimeout\": \"3s\"/" config/ra.json + sed -i -e "s/\"dnsTimeout\": \".*\"/\"dnsTimeout\": \"3s\"/" config/va.json + + for file in `find . -type f | grep -v .git`; do + sed -i -e "s|test/|labca/|g" $file + done + + sed -i -e "s/names/name\(s\)/" example-expiration-template + + rm test-ca2.pem + + local have_config=$(grep restarted $adminDir/data/config.json | grep true) + if [ "$have_config" != "" ]; then + export PKI_ROOT_CERT_BASE="$adminDir/data/root-ca" + export PKI_INT_CERT_BASE="$adminDir/data/issuer/ca-int" + export PKI_DNS=$(grep dns $adminDir/data/config.json | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g') + export PKI_DOMAIN=$(grep fqdn $adminDir/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g' | perl -p0e 's/.*?\.//') + export PKI_DOMAIN_MODE=$(grep domain_mode $adminDir/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') + export PKI_LOCKDOWN_DOMAINS=$(grep lockdown $adminDir/data/config.json | grep -v domain_mode | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') + export PKI_WHITELIST_DOMAINS=$(grep whitelist $adminDir/data/config.json | grep -v domain_mode | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') + + export PKI_EMAIL_SERVER=$(grep server $adminDir/data/config.json | head -1 | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g') + export PKI_EMAIL_PORT=$(grep port $adminDir/data/config.json | head -1 | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g') + export PKI_EMAIL_USER=$(grep user $adminDir/data/config.json | head -1 | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g') + export PKI_EMAIL_FROM=$(grep from $adminDir/data/config.json | head -1 | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g') + + $adminDir/apply-boulder + else + chown -R labca:labca "$boulderLabCADir" + fi + + git add --all &>/dev/null || true + git commit --all --quiet -m "LabCA after update $runId" &>>$installLog || true + + msg_ok "$msg" +} + +# Cleanup any now obsolete files +cleanup() { + local msg="Cleaning up obsolete files" + msg_info "$msg" + + rm -f /var/www/html/css/skeleton.css + rm -f /var/www/html/css/skeleton-tabs.css + rm -f /var/www/html/css/normalize.css + rm -f /var/www/html/css/font.css + rm -f /var/www/html/img/favicon.ico + rm -f /var/www/html/js/jquery-3.3.1.min.js + rm -f /var/www/html/js/skeleton-tabs.js + rm -f $adminDir/templates/cert.tmpl + rm -f $adminDir/templates/error.tmpl + rm -f $adminDir/templates/final.tmpl + rm -f $adminDir/templates/footer.tmpl + rm -f $adminDir/templates/header.tmpl + rm -f $adminDir/templates/index.tmpl + rm -f $adminDir/templates/login.tmpl + rm -f $adminDir/templates/polling.tmpl + rm -f $adminDir/templates/register.tmpl + rm -f $adminDir/templates/setup.tmpl + rm -f $adminDir/templates/wrapup.tmpl + + msg_ok "$msg" +} + +# Startup all the components +startup() { + local msg="Restart docker containers and service" + + cd "$boulderDir" + cnt=$(docker-compose ps | wc -l) + if [ "$cnt" == "2" ]; then + msg="Download docker images and build containers" + fi + msg_info "$msg (this will take a while!!)" + + docker-compose stop &>>$installLog || true + wait_down $PS_MYSQL &>>$installLog + wait_down $PS_BHSM &>>$installLog + wait_down $PS_LABCA &>>$installLog + wait_down $PS_BOULDER &>>$installLog + docker-compose up -d &>>$installLog + + [ -h "/etc/init.d/labca" ] || ln -s "$cloneDir/init_d" /etc/init.d/labca + update-rc.d labca defaults &>>$installLog + update-rc.d labca enable &>>$installLog + service labca stop &>>$installLog || true + wait_down $PS_SERVICE &>>$installLog + service labca start &>>$installLog + wait_up $PS_SERVICE &>>$installLog + + wait_up $PS_MYSQL &>>$installLog + wait_up $PS_BHSM &>>$installLog + wait_up $PS_LABCA &>>$installLog + wait_up $PS_BOULDER $PS_BOULDER_COUNT &>>$installLog + + msg_ok "$msg" +} + +# If the nginx certificate is self-signed then show extra text +first_time() { + local certFile="/etc/nginx/ssl/labca_cert.pem" + [ -e "$certFile" ] || msg_fatal "The SSL certificate $certFile does not exist" + + local subject=$(openssl x509 -noout -in "$certFile" -subject_hash) + local issuer=$(openssl x509 -noout -in "$certFile" -issuer_hash) + + if [ "$subject" == "$issuer" ]; then + echo + echo ======== + echo + echo "Congratulations! LabCA is now installed and should be available at https://$LABCA_FQDN" + echo "Please go there now to finish the setup. Note that a TEMPORARY (7 days) self-signed certificate" + echo "is used; as part of the setup verification a new certificate will be issued." + echo + fi +} + +# +# The actual main function to tie it all together +# +main() { + local curdir="$PWD" + + echo + start_temporary_log + check_root + install_pkg "git" + labca_user + end_temporary_log + + this=$0 + [ -e $this ] || this="$curdir/$0" + local checksum=$(md5sum $this 2>/dev/null | cut -d' ' -f1) + [ ! -e "$cloneDir/cron_d" ] || chown labca:labca "$cloneDir/cron_d" + clone_or_pull "$cloneDir" "$labcaUrl" + restart_if_updated "$checksum" + + get_fqdn "$@" + copy_admin + + update_upgrade + install_extra + + static_web + selfsigned_cert + + get_boulder + config_boulder + + cleanup + startup + + echo -e "$DONE" + echo + + first_time + + cd "$curdir" +} + +main "$@" diff --git a/logrotate_d b/logrotate_d new file mode 100644 index 0000000..d27d3ce --- /dev/null +++ b/logrotate_d @@ -0,0 +1,9 @@ +/etc/nginx/ssl/*.log +/home/labca/logs/cron-*.log +{ + rotate 4 + monthly + compress + missingok + notifempty +} diff --git a/mail-tester.go b/mail-tester.go new file mode 100644 index 0000000..f6eb078 --- /dev/null +++ b/mail-tester.go @@ -0,0 +1,125 @@ +package main + +import ( + "flag" + "fmt" + netmail "net/mail" + "os" + "time" + + "github.com/letsencrypt/boulder/bdns" + "github.com/letsencrypt/boulder/cmd" + "github.com/letsencrypt/boulder/features" + bmail "github.com/letsencrypt/boulder/mail" +) + +const usageString = ` +usage: +mail-tester --config + +args: + config File path to the configuration file for this service + recipient Email address to send an email to +` + +type config struct { + Mailer struct { + cmd.ServiceConfig + cmd.DBConfig + cmd.SMTPConfig + + From string + Subject string + + DNSTries int + DNSResolvers []string + + Features map[string]bool + } + + Syslog cmd.SyslogConfig + + Common struct { + DNSResolver string + DNSTimeout string + DNSAllowLoopbackAddresses bool + } +} + +func main() { + usage := func() { + fmt.Fprintf(os.Stderr, usageString) + os.Exit(1) + } + + configFile := flag.String("config", "", "File path to the configuration file for this service") + flag.Parse() + args := flag.Args() + recipient := args[0] + + if len(os.Args) <= 3 || *configFile == "" { + usage() + } + + var c config + err := cmd.ReadConfigFile(*configFile, &c) + cmd.FailOnError(err, "Reading JSON config file into config structure") + err = features.Set(c.Mailer.Features) + cmd.FailOnError(err, "Failed to set feature flags") + + scope, logger := cmd.StatsAndLogging(c.Syslog, c.Mailer.DebugAddr) + defer logger.AuditPanic() + logger.Info(cmd.VersionString()) + + clk := cmd.Clock() + + dnsTimeout, err := time.ParseDuration(c.Common.DNSTimeout) + cmd.FailOnError(err, "Couldn't parse DNS timeout") + dnsTries := c.Mailer.DNSTries + if dnsTries < 1 { + dnsTries = 1 + } + var resolver bdns.DNSClient + if len(c.Common.DNSResolver) != 0 { + c.Mailer.DNSResolvers = append(c.Mailer.DNSResolvers, c.Common.DNSResolver) + } + if !c.Common.DNSAllowLoopbackAddresses { + r := bdns.NewDNSClientImpl( + dnsTimeout, + c.Mailer.DNSResolvers, + scope, + clk, + dnsTries) + resolver = r + } else { + r := bdns.NewTestDNSClientImpl(dnsTimeout, c.Mailer.DNSResolvers, scope, clk, dnsTries) + resolver = r + } + + fromAddress, err := netmail.ParseAddress(c.Mailer.From) + cmd.FailOnError(err, fmt.Sprintf("Could not parse from address: %s", c.Mailer.From)) + + smtpPassword, err := c.Mailer.PasswordConfig.Pass() + cmd.FailOnError(err, "Failed to load SMTP password") + mailClient := bmail.New( + c.Mailer.Server, + c.Mailer.Port, + c.Mailer.Username, + smtpPassword, + nil, + resolver, + *fromAddress, + logger, + scope, + 1*time.Second, + 5*60*time.Second) + + mailClient.Connect() + defer mailClient.Close() + + recipients := []string{} + recipients = append(recipients, recipient) + + err = mailClient.SendMail(recipients, "Test Email from LabCA", "Test sending email from the LabCA server") + cmd.FailOnError(err, "mail-tester has failed") +} diff --git a/mail_mailer.patch b/mail_mailer.patch new file mode 100644 index 0000000..45ae8a7 --- /dev/null +++ b/mail_mailer.patch @@ -0,0 +1,85 @@ +diff --git a/mail/mailer.go b/mail/mailer.go +index 6dac0ab5..dfab66f4 100644 +--- a/mail/mailer.go ++++ b/mail/mailer.go +@@ -20,10 +20,13 @@ import ( + "time" + + "github.com/jmhodges/clock" ++ "golang.org/x/net/context" + + "github.com/letsencrypt/boulder/core" ++ "github.com/letsencrypt/boulder/bdns" + blog "github.com/letsencrypt/boulder/log" + "github.com/letsencrypt/boulder/metrics" ++ "github.com/letsencrypt/boulder/probs" + ) + + type idGenerator interface { +@@ -113,6 +116,7 @@ func New( + username, + password string, + rootCAs *x509.CertPool, ++ resolver bdns.DNSClient, + from mail.Address, + logger blog.Logger, + stats metrics.Scope, +@@ -125,6 +129,7 @@ func New( + server: server, + port: port, + rootCAs: rootCAs, ++ dnsClient: resolver, + }, + log: logger, + from: from, +@@ -163,7 +168,7 @@ func (m *MailerImpl) generateMessage(to []string, subject, body string) ([]byte, + fmt.Sprintf("To: %s", strings.Join(addrs, ", ")), + fmt.Sprintf("From: %s", m.from.String()), + fmt.Sprintf("Subject: %s", subject), +- fmt.Sprintf("Date: %s", now.Format(time.RFC822)), ++ fmt.Sprintf("Date: %s", now.Format(time.RFC1123Z)), + fmt.Sprintf("Message-Id: <%s.%s.%s>", now.Format("20060102T150405"), mid.String(), m.from.Address), + "MIME-Version: 1.0", + "Content-Type: text/plain; charset=UTF-8", +@@ -220,23 +225,32 @@ func (m *MailerImpl) Connect() error { + type dialerImpl struct { + username, password, server, port string + rootCAs *x509.CertPool ++ dnsClient bdns.DNSClient + } + + func (di *dialerImpl) Dial() (smtpClient, error) { +- hostport := net.JoinHostPort(di.server, di.port) +- var conn net.Conn +- var err error +- conn, err = tls.Dial("tcp", hostport, &tls.Config{ +- RootCAs: di.rootCAs, +- }) ++ deadline := time.Now().Add(30 * time.Second) ++ ctx, cancel := context.WithDeadline(context.Background(), deadline) ++ defer cancel() ++ ++ addrs, err := di.dnsClient.LookupHost(ctx, di.server) + if err != nil { +- return nil, err ++ problem := probs.DNS("%v", err) ++ return nil, problem + } +- client, err := smtp.NewClient(conn, di.server) ++ if len(addrs) == 0 { ++ return nil, probs.UnknownHost("No valid IP addresses found for %s", di.server) ++ } ++ ++ hostport := net.JoinHostPort(addrs[0].String(), di.port) ++ client, err := smtp.Dial(hostport) + if err != nil { + return nil, err + } +- auth := smtp.PlainAuth("", di.username, di.password, di.server) ++ client.StartTLS( &tls.Config{ ++ ServerName: di.server, ++ }) ++ auth := smtp.PlainAuth("", di.username, di.password, addrs[0].String()) + if err = client.Auth(auth); err != nil { + return nil, err + } diff --git a/mailer b/mailer new file mode 100755 index 0000000..d0a8c08 --- /dev/null +++ b/mailer @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +TODAY=`date '+%Y_%m_%d'` +LOGFILE=/home/labca/logs/cron-mailer.log +echo $TODAY >>$LOGFILE + +cd /home/labca/boulder +docker-compose exec -T boulder bin/expiration-mailer --config labca/config/expiration-mailer.json >>$LOGFILE 2>&1 diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..6cf37f4 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,89 @@ +server { + listen [::]:80 default_server ipv6only=off; + server_name _; + server_tokens off; + + root /var/www/html; + index index.html index.htm index.nginx-debian.html; + + location /admin/ { + return 301 https://$host$request_uri; + } + + location /acme/ { + return 301 https://$host$request_uri; + } + + location /directory { + return 301 https://$host$request_uri; + } + + location /ocsp/ { + include proxy_params; + proxy_pass http://127.0.0.1:4002/; + } + + location /terms/ { + try_files $uri $uri.html $uri/ =404; + } +} + +server { + listen [::]:443 default_server ssl ipv6only=off; + server_name _; + server_tokens off; + + ssl_certificate /etc/nginx/ssl/labca_cert.pem; + ssl_certificate_key /etc/nginx/ssl/labca_key.pem; + + root /var/www/html; + index index.html index.htm index.nginx-debian.html; + + location ~ ^/admin/static/(.+) { + alias /var/www/html/$1; + } + + location ~ ^/admin/.+/static/(.+) { + alias /var/www/html/$1; + } + + location /admin/ { + include proxy_params; + proxy_set_header X-Request-Base "/admin"; + proxy_pass http://127.0.0.1:3000/; + error_page 502 504 /502.html; + } + + location /admin/ws { + include proxy_params; + proxy_set_header X-Request-Base "/admin"; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_pass http://127.0.0.1:3000/ws; + } + + location /acme/ { + include proxy_params; + proxy_pass http://127.0.0.1:4001; + } + + location /directory { + include proxy_params; + proxy_pass http://127.0.0.1:4001; + } + + location /ocsp/ { + include proxy_params; + proxy_pass http://127.0.0.1:4002/; + } + + location /terms/ { + try_files $uri $uri.html $uri/ =404; + } + + # BEGIN temporary redirect + location = / { + return 302 /admin/; + } + # END temporary redirect +} diff --git a/notify-mailer_main.patch b/notify-mailer_main.patch new file mode 100644 index 0000000..9c35511 --- /dev/null +++ b/notify-mailer_main.patch @@ -0,0 +1,12 @@ +diff --git a/cmd/notify-mailer/main.go b/cmd/notify-mailer/main.go +index 8979edce..885f2247 100644 +--- a/cmd/notify-mailer/main.go ++++ b/cmd/notify-mailer/main.go +@@ -348,6 +348,7 @@ func main() { + cfg.NotifyMailer.Username, + smtpPassword, + nil, ++ nil, + *address, + log, + metrics.NewNoopScope(), diff --git a/policy_pa.patch b/policy_pa.patch new file mode 100644 index 0000000..b39237b --- /dev/null +++ b/policy_pa.patch @@ -0,0 +1,90 @@ +diff --git a/policy/pa.go b/policy/pa.go +index a8337bf7..83150102 100644 +--- a/policy/pa.go ++++ b/policy/pa.go +@@ -29,6 +29,8 @@ type AuthorityImpl struct { + blacklist map[string]bool + exactBlacklist map[string]bool + wildcardExactBlacklist map[string]bool ++ whitelist map[string]bool ++ lockdown map[string]bool + blacklistMu sync.RWMutex + + enabledChallenges map[string]bool +@@ -53,6 +55,8 @@ func New(challengeTypes map[string]bool) (*AuthorityImpl, error) { + type blacklistJSON struct { + Blacklist []string + ExactBlacklist []string ++ Whitelist []string ++ Lockdown []string + } + + // SetHostnamePolicyFile will load the given policy file, returning error if it +@@ -103,10 +107,20 @@ func (pa *AuthorityImpl) loadHostnamePolicy(b []byte) error { + // wildcardNameMap to block issuance for `*.`+parts[1] + wildcardNameMap[parts[1]] = true + } ++ whiteMap := make(map[string]bool) ++ for _, v := range bl.Whitelist { ++ whiteMap[v] = true ++ } ++ lockMap := make(map[string]bool) ++ for _, v := range bl.Lockdown { ++ lockMap[v] = true ++ } + pa.blacklistMu.Lock() + pa.blacklist = nameMap + pa.exactBlacklist = exactNameMap + pa.wildcardExactBlacklist = wildcardNameMap ++ pa.whitelist = whiteMap ++ pa.lockdown = lockMap + pa.blacklistMu.Unlock() + return nil + } +@@ -288,6 +302,14 @@ func (pa *AuthorityImpl) WillingToIssue(id core.AcmeIdentifier) error { + } + } + ++ ok, err := pa.checkWhitelist(domain) ++ if err != nil { ++ return err ++ } ++ if ok { ++ return nil ++ } ++ + // Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD. + icannTLD, err := extractDomainIANASuffix(domain) + if err != nil { +@@ -413,6 +435,31 @@ func (pa *AuthorityImpl) checkHostLists(domain string) error { + return nil + } + ++func (pa *AuthorityImpl) checkWhitelist(domain string) (bool,error) { ++ pa.blacklistMu.RLock() ++ defer pa.blacklistMu.RUnlock() ++ ++ if (pa.whitelist == nil) || (pa.lockdown == nil) { ++ return false, fmt.Errorf("Hostname policy not yet loaded.") ++ } ++ ++ labels := strings.Split(domain, ".") ++ for i := range labels { ++ joined := strings.Join(labels[i:], ".") ++ if pa.whitelist[joined] || pa.lockdown[joined] { ++ return true, nil ++ } ++ } ++ ++ if len(pa.lockdown) > 0 { ++ // In Lockdown mode, the domain MUST be in the list, so return an error if not found ++ return false, errBlacklisted ++ } else { ++ // In Whitelist mode, if the domain is not in the list, continue with the other checks ++ return false, nil ++ } ++} ++ + // ChallengesFor makes a decision of what challenges, and combinations, are + // acceptable for the given identifier. If the TLSSNIRevalidation feature flag + // is set, create TLS-SNI-01 challenges for revalidation requests even if diff --git a/renew b/renew new file mode 100755 index 0000000..c5e227c --- /dev/null +++ b/renew @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e + +cd /etc/nginx/ssl +date >> acme_tiny.log +python ~labca/acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /var/www/html/.well-known/acme-challenge/ > domain_chain.crt 2>> acme_tiny.log || exit 1 +mv domain_chain.crt labca_cert.pem +chown -R www-data:www-data labca_cert.pem + +service nginx reload diff --git a/restore b/restore new file mode 100755 index 0000000..f50edb6 --- /dev/null +++ b/restore @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -e + +FILE=/home/labca/backup/$1 + +[ "$1" != "" ] || (echo "You must provide a backup file name to restore"; exit 1) +[ -f $FILE ] || (echo "Backup file '$FILE' not found"; exit 1) + +BASE=$(echo "$FILE" | perl -p0e "s/.*\/(.*).tgz/\1/") +TMPDIR=/tmp/$BASE + +cd /tmp +tar xzf $FILE + +cd /home/labca/boulder +docker-compose exec bmysql mysql boulder_sa_integration <$TMPDIR/boulder_sa_integration.sql + +mv -f $TMPDIR/*key* $TMPDIR/*cert.pem $TMPDIR/*.csr /etc/nginx/ssl/ + +rm -rf /home/labca/admin/data && mv $TMPDIR/data /home/labca/admin/ + +rm -rf $TMPDIR diff --git a/smartrenew b/smartrenew new file mode 100755 index 0000000..fd66ab6 --- /dev/null +++ b/smartrenew @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -e + +RENEW=30 +TODAY=`date '+%Y_%m_%d'` + +echo $TODAY >> /etc/nginx/ssl/cron.log + +if ! expires=`openssl x509 -checkend $[ 86400 * $RENEW ] -noout -in /etc/nginx/ssl/labca_cert.pem`; then + echo " renewing!" >> /etc/nginx/ssl/cron.log + cp /etc/nginx/ssl/labca_cert.pem /etc/nginx/ssl/labca_cert_$TODAY.pem + ~labca/labca/renew +fi diff --git a/test_config_ca_a.patch b/test_config_ca_a.patch new file mode 100644 index 0000000..7af9aef --- /dev/null +++ b/test_config_ca_a.patch @@ -0,0 +1,15 @@ +diff --git a/test/config/ca-a.json b/test/config/ca-a.json +index 355cfae2..c93fa5a3 100644 +--- a/test/config/ca-a.json ++++ b/test/config/ca-a.json +@@ -29,10 +29,6 @@ + }, + "Issuers": [{ + "ConfigFile": "test/test-ca.key-pkcs11.json", +- "CertFile": "test/test-ca2.pem", +- "NumSessions": 2 +- }, { +- "ConfigFile": "test/test-ca.key-pkcs11.json", + "CertFile": "test/test-ca.pem", + "NumSessions": 2 + }], diff --git a/test_config_ca_b.patch b/test_config_ca_b.patch new file mode 100644 index 0000000..527bd58 --- /dev/null +++ b/test_config_ca_b.patch @@ -0,0 +1,15 @@ +diff --git a/test/config/ca-b.json b/test/config/ca-b.json +index 355cfae2..c93fa5a3 100644 +--- a/test/config/ca-b.json ++++ b/test/config/ca-b.json +@@ -29,10 +29,6 @@ + }, + "Issuers": [{ + "ConfigFile": "test/test-ca.key-pkcs11.json", +- "CertFile": "test/test-ca2.pem", +- "NumSessions": 2 +- }, { +- "ConfigFile": "test/test-ca.key-pkcs11.json", + "CertFile": "test/test-ca.pem", + "NumSessions": 2 + }], diff --git a/utils.sh b/utils.sh new file mode 100644 index 0000000..fb760c9 --- /dev/null +++ b/utils.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +set -e + +export PS_LABCA="bin/labca" +export PS_BOULDER="bin/boulder" +export PS_BOULDER_COUNT=12 +export PS_MYSQL="mysqld" +export PS_BHSM="pkcs11" +export PS_SERVICE="sudo___tcpserver" + +LOOPCOUNT=120 + +count() { + local pattern="${1/___/ }" + + local res=$(ps -eo pid,cmd | grep "$pattern" | grep -v grep | wc -l) + echo $res +} + +wait_count() { + local pattern="$1" + local count="$2" + local lc=0 + + # Allow more time for the boulder container... + if [ $count -gt 1 ]; then + LOOPCOUNT=240 + fi + + local c=$(count $pattern) + while ( [ $count -gt 0 ] && [ $c -lt $count ] ) || ( [ $count -eq 0 ] && [ $c -gt $count ] ) && [ $lc -lt $LOOPCOUNT ]; do + let lc=lc+1 + sleep 1 + c=$(count $pattern) + done + if ( [ $count -gt 0 ] && [ $c -ge $count ] ) || ( [ $count -eq 0 ] && [ $c -eq $count ] ); then + return + fi + if [ $lc -ge $LOOPCOUNT ]; then + pattern="${pattern/___/ }" + if [ $count -gt 1 ]; then + echo "FAILED to get $count of $pattern (only have $c)" + else + echo "FAILED to get $count of $pattern" + fi + fi +} + +wait_up() { + wait_count "$1" "${2:-1}" +} + +wait_down() { + wait_count "$1" 0 +} + diff --git a/www/502.html b/www/502.html new file mode 100644 index 0000000..a8af721 --- /dev/null +++ b/www/502.html @@ -0,0 +1,164 @@ + + + + + + + + + + LabCA + + + + + + + + + + + +
+ + + +
+
+
+
+ +

Please wait...

+

Trying to get to the page you requested...
+

+ +
+
+
+ +
+
+ + +
+
+ +
+
+ + + + + + + + + + + + diff --git a/www/certs/index.html b/www/certs/index.html new file mode 100644 index 0000000..1bfa6cc --- /dev/null +++ b/www/certs/index.html @@ -0,0 +1,101 @@ + + + + + + + + + + + Certificates | LabCA + + + + + + + + + +
+ + +
+
+
+

PKI Certificates

+

These are the Certification Authorities for this PKI (Public Key Infrastructure) instance:

+ + + + + + + + + + + + + + + + + + + + + + + + +
CA TypeDistinguished NameWindows formatLinux formatValidity Period
Root CA[PKI_ROOT_DN]root-ca.derroot-ca.pem[PKI_ROOT_VALIDITY]
Issuing CA[PKI_INT_DN]ca-int.derca-int.pem[PKI_INT_VALIDITY]
+ +

+ To trust the certificates provided by LabCA, all your client devices should install the root certificate in their + Trusted Root Certification Authorities store. You may choose to download the format best suited for your + Operating System: DER format for Windows machines or PEM format for Linux/unix machines and Android phones. +

+
+
+
+
+ + + + + + + + diff --git a/www/cps/index.html b/www/cps/index.html new file mode 100644 index 0000000..31d7c49 --- /dev/null +++ b/www/cps/index.html @@ -0,0 +1,171 @@ + + + + + + + + + + + CPS | LabCA + + + + + + + + + +
+ + +
+
+
+

Certification Practice Statement

+

1. Introduction

+

+ This Certification Practice Statement ("CPS") document outlines the certification services practices for this + particular instance running the LabCA software. PKI (Public Key Infrastructure) services include, but are not limited to, issuing, managing, + validating, revoking, and renewing Certificates. The services are provided for [PKI_COMPANY_NAME] internal use only. +

+

The following Certification Authorities are covered under this CPS:

+ + + + + + + + + + + + + +
CA TypeDistinguished NameSHA-256 Key FingerprintValidity Period
Root CA[PKI_ROOT_DN][PKI_ROOT_FINGERPRINT][PKI_ROOT_VALIDITY]
+

+ Certificates issued by this PKI can be used only to establish secure online communication between hosts (as + identified by the FQDN provided in the Certificate) and clients using the TLS protocol. A Certificate only represents + that the information contained in it was verified as reasonably correct when the Certificate was issued. +

+

+ Certificates may not be used for any application requiring fail-safe performance, providing financial services, + facilitating interference with encrypted communications or violating laws or regulations. +

+

+ Relying Parties should verify the validity of certificates via CRL or OCSP prior to relying on certificates. CRL and + OCSP location information is provided within certificates. +

+ +

2. Publication and Repository

+

This CPS is published at [LABCA_CPS_LOCATION]

+

+ Records of root and intermediate certificates, including those that have been revoked, are available at + [LABCA_CERTS_LOCATION] +

+

+ LabCA certificates contain URLs to locations where certificate-related information is published, including + revocation information via OCSP and/or CRLs. +

+ +

3. Identification and Authentication

+

+ LabCA certificates include a "Subject" field which identifies the subject entity (i.e. organization or domain). The + subject entity is identified using a distinguished name. +

+

+ LabCA certificates include an "Issuer" field which identifies the issuing entity. The issuing entity is identified + using a distinguished name. +

+ +

4. Certificate Life-Cycle Operational Requirements

+

+ Anyone associated with [PKI_COMPANY_NAME] may submit an application for a certificate via the ACME protocol. Issuance + will depend on proper validation and compliance with this PKI's policies. End-entity certificates are made available + to Subscribers via the ACME protocol as soon after issuance as reasonably possible. +

+

+ Subscribers are obligated to generate Key Pairs using reasonably trustworthy systems and to take reasonable measures + to protect their Private Keys from unauthorized use or disclosure. +

+

+ Relying Parties must fully evaluate the context in which they are relying on certificates and the information + contained in them, and decide to what extent the risk of reliance is acceptable. If the risk of relying on a + certificate is determined to be unacceptable, then Relying Parties should not use the certificate or should obtain + additional assurances before using the certificate. +

+

+ Relying Parties ignoring certificate expiration, revocation data provided via OCSP or CRL, or other pertinent + information do so at their own risk. +

+

Certificate revocation permanently ends the certificate's operational period prior to its stated validity period.

+ +

5. Facilities, Management, and Operational Controls

+

Operating this PKI is under full responsibility of [PKI_COMPANY_NAME].

+ +

6. Technical Security Controls

+

+ LabCA is not using a Hardware Security Module (HSM) for storing CA private keys. LabCA is intended + to be used in a lab or intranet environment with sufficient protection against bad actors. It may not be used as + publicly accessible PKI instance. +

+ +

7. Certificate, CRL, and OCSP Profile

+

Any requirements or policies regarding Certificates, CRLs and OCSP are at full discretion of [PKI_COMPANY_NAME].

+ +

8. Compliance audit

+

Not applicable.

+ +

9. Other Business and Legal Matters

+

+ LabCA CERTIFICATES AND SERVICES ARE PROVIDED "AS-IS". LabCA DISCLAIMS ANY AND ALL WARRANTIES OF ANY TYPE AND DOES + NOT ACCEPT ANY LIABILITY. +

+

EACH USER AFFIRMATIVELY AND EXPRESSLY WAIVES THE RIGHT TO HOLD LabCA RESPONSIBLE IN ANY WAY.

+ +

 

+
+
+
+
+ + + + + + + + diff --git a/www/css/bootstrap.min.css b/www/css/bootstrap.min.css new file mode 100644 index 0000000..ed3905e --- /dev/null +++ b/www/css/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/www/css/dataTables.bootstrap.css b/www/css/dataTables.bootstrap.css new file mode 100644 index 0000000..f65e264 --- /dev/null +++ b/www/css/dataTables.bootstrap.css @@ -0,0 +1,314 @@ +div.dataTables_length label { + font-weight: normal; + text-align: left; + white-space: nowrap; +} + +div.dataTables_length select { + width: 75px; + display: inline-block; +} + +div.dataTables_filter { + text-align: right; +} + +div.dataTables_filter label { + font-weight: normal; + white-space: nowrap; + text-align: left; +} + +div.dataTables_filter input { + margin-left: 0.5em; + display: inline-block; +} + +div.dataTables_info { + padding-top: 8px; + white-space: nowrap; +} + +div.dataTables_paginate { + margin: 0; + white-space: nowrap; + text-align: right; +} + +div.dataTables_paginate ul.pagination { + margin: 2px 0; + white-space: nowrap; +} + +@media screen and (max-width: 767px) { + div.dataTables_length, + div.dataTables_filter, + div.dataTables_info, + div.dataTables_paginate { + text-align: center; + } +} + + +table.dataTable td, +table.dataTable th { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + + +table.dataTable { + clear: both; + margin-top: 6px !important; + margin-bottom: 6px !important; + max-width: none !important; +} + +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting_asc_disabled, +table.dataTable thead .sorting_desc_disabled { + cursor: pointer; +} + +table.dataTable thead .sorting { background: url('../images/sort_both.png') no-repeat center right; } +table.dataTable thead .sorting_asc { background: url('../images/sort_asc.png') no-repeat center right; } +table.dataTable thead .sorting_desc { background: url('../images/sort_desc.png') no-repeat center right; } + +table.dataTable thead .sorting_asc_disabled { background: url('../images/sort_asc_disabled.png') no-repeat center right; } +table.dataTable thead .sorting_desc_disabled { background: url('../images/sort_desc_disabled.png') no-repeat center right; } + +table.dataTable thead > tr > th { + padding-left: 18px; + padding-right: 18px; +} + +table.dataTable th:active { + outline: none; +} + +/* Scrolling */ +div.dataTables_scrollHead table { + margin-bottom: 0 !important; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +div.dataTables_scrollHead table thead tr:last-child th:first-child, +div.dataTables_scrollHead table thead tr:last-child td:first-child { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +div.dataTables_scrollBody table { + border-top: none; + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +div.dataTables_scrollBody tbody tr:first-child th, +div.dataTables_scrollBody tbody tr:first-child td { + border-top: none; +} + +div.dataTables_scrollFoot table { + margin-top: 0 !important; + border-top: none; +} + +/* Frustratingly the border-collapse:collapse used by Bootstrap makes the column + width calculations when using scrolling impossible to align columns. We have + to use separate + */ +table.table-bordered.dataTable { + border-collapse: separate !important; +} +table.table-bordered thead th, +table.table-bordered thead td { + border-left-width: 0; + border-top-width: 0; +} +table.table-bordered tbody th, +table.table-bordered tbody td { + border-left-width: 0; + border-bottom-width: 0; +} +table.table-bordered th:last-child, +table.table-bordered td:last-child { + border-right-width: 0; +} +div.dataTables_scrollHead table.table-bordered { + border-bottom-width: 0; +} + + + + +/* + * TableTools styles + */ +.table.dataTable tbody tr.active td, +.table.dataTable tbody tr.active th { + background-color: #08C; + color: white; +} + +.table.dataTable tbody tr.active:hover td, +.table.dataTable tbody tr.active:hover th { + background-color: #0075b0 !important; +} + +.table.dataTable tbody tr.active th > a, +.table.dataTable tbody tr.active td > a { + color: white; +} + +.table-striped.dataTable tbody tr.active:nth-child(odd) td, +.table-striped.dataTable tbody tr.active:nth-child(odd) th { + background-color: #017ebc; +} + +table.DTTT_selectable tbody tr { + cursor: pointer; +} + +div.DTTT .btn:hover { + text-decoration: none !important; +} + +ul.DTTT_dropdown.dropdown-menu { + z-index: 2003; +} + +ul.DTTT_dropdown.dropdown-menu a { + color: #333 !important; /* needed only when demo_page.css is included */ +} + +ul.DTTT_dropdown.dropdown-menu li { + position: relative; +} + +ul.DTTT_dropdown.dropdown-menu li:hover a { + background-color: #0088cc; + color: white !important; +} + +div.DTTT_collection_background { + z-index: 2002; +} + +/* TableTools information display */ +div.DTTT_print_info { + position: fixed; + top: 50%; + left: 50%; + width: 400px; + height: 150px; + margin-left: -200px; + margin-top: -75px; + text-align: center; + color: #333; + padding: 10px 30px; + opacity: 0.95; + + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + + -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.5); +} + +div.DTTT_print_info h6 { + font-weight: normal; + font-size: 28px; + line-height: 28px; + margin: 1em; +} + +div.DTTT_print_info p { + font-size: 14px; + line-height: 20px; +} + +div.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 60px; + margin-left: -50%; + margin-top: -25px; + padding-top: 20px; + padding-bottom: 20px; + text-align: center; + font-size: 1.2em; + background-color: white; + background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0))); + background: -webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); + background: -moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); + background: -ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); + background: -o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); + background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); +} + + + +/* + * FixedColumns styles + */ +div.DTFC_LeftHeadWrapper table, +div.DTFC_LeftFootWrapper table, +div.DTFC_RightHeadWrapper table, +div.DTFC_RightFootWrapper table, +table.DTFC_Cloned tr.even { + background-color: white; + margin-bottom: 0; +} + +div.DTFC_RightHeadWrapper table , +div.DTFC_LeftHeadWrapper table { + border-bottom: none !important; + margin-bottom: 0 !important; + border-top-right-radius: 0 !important; + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +div.DTFC_RightHeadWrapper table thead tr:last-child th:first-child, +div.DTFC_RightHeadWrapper table thead tr:last-child td:first-child, +div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child, +div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +div.DTFC_RightBodyWrapper table, +div.DTFC_LeftBodyWrapper table { + border-top: none; + margin: 0 !important; +} + +div.DTFC_RightBodyWrapper tbody tr:first-child th, +div.DTFC_RightBodyWrapper tbody tr:first-child td, +div.DTFC_LeftBodyWrapper tbody tr:first-child th, +div.DTFC_LeftBodyWrapper tbody tr:first-child td { + border-top: none; +} + +div.DTFC_RightFootWrapper table, +div.DTFC_LeftFootWrapper table { + border-top: none; + margin-top: 0 !important; +} + + +/* + * FixedHeader styles + */ +div.FixedHeader_Cloned table { + margin: 0 !important +} + diff --git a/www/css/dataTables.responsive.css b/www/css/dataTables.responsive.css new file mode 100644 index 0000000..1060f9c --- /dev/null +++ b/www/css/dataTables.responsive.css @@ -0,0 +1,106 @@ +table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child, +table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child { + position: relative; + padding-left: 30px; + cursor: pointer; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child:before { + top: 8px; + left: 4px; + height: 16px; + width: 16px; + display: block; + position: absolute; + color: white; + border: 2px solid white; + border-radius: 16px; + text-align: center; + line-height: 14px; + box-shadow: 0 0 3px #444; + box-sizing: content-box; + content: '+'; + background-color: #31b131; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child.dataTables_empty:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child.dataTables_empty:before { + display: none; +} +table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td:first-child:before, +table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th:first-child:before { + content: '-'; + background-color: #d33333; +} +table.dataTable.dtr-inline.collapsed > tbody > tr.child td:before { + display: none; +} +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td:first-child, +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child { + padding-left: 27px; +} +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td:first-child:before, +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child:before { + top: 5px; + left: 4px; + height: 14px; + width: 14px; + border-radius: 14px; + line-height: 12px; +} +table.dataTable.dtr-column > tbody > tr > td.control, +table.dataTable.dtr-column > tbody > tr > th.control { + position: relative; + cursor: pointer; +} +table.dataTable.dtr-column > tbody > tr > td.control:before, +table.dataTable.dtr-column > tbody > tr > th.control:before { + top: 50%; + left: 50%; + height: 16px; + width: 16px; + margin-top: -10px; + margin-left: -10px; + display: block; + position: absolute; + color: white; + border: 2px solid white; + border-radius: 16px; + text-align: center; + line-height: 14px; + box-shadow: 0 0 3px #444; + box-sizing: content-box; + content: '+'; + background-color: #31b131; +} +table.dataTable.dtr-column > tbody > tr.parent td.control:before, +table.dataTable.dtr-column > tbody > tr.parent th.control:before { + content: '-'; + background-color: #d33333; +} +table.dataTable > tbody > tr.child { + padding: 0.5em 1em; +} +table.dataTable > tbody > tr.child:hover { + background: transparent !important; +} +table.dataTable > tbody > tr.child ul { + display: inline-block; + list-style-type: none; + margin: 0; + padding: 0; +} +table.dataTable > tbody > tr.child ul li { + border-bottom: 1px solid #efefef; + padding: 0.5em 0; +} +table.dataTable > tbody > tr.child ul li:first-child { + padding-top: 0; +} +table.dataTable > tbody > tr.child ul li:last-child { + border-bottom: none; +} +table.dataTable > tbody > tr.child span.dtr-title { + display: inline-block; + min-width: 75px; + font-weight: bold; +} diff --git a/www/css/font-awesome.min.css b/www/css/font-awesome.min.css new file mode 100644 index 0000000..9b27f8e --- /dev/null +++ b/www/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/www/css/labca.css b/www/css/labca.css new file mode 100644 index 0000000..e486f36 --- /dev/null +++ b/www/css/labca.css @@ -0,0 +1,128 @@ +.error { + color: #d9534f; +} + +.warning { + color: #f0ad4e; +} + +.success { + color: #5cb85c; +} + +.strength-none { + background-color: #bbb; +} + +.strength-bad { + background-color: #d9534f; +} + +.strength-med { + background-color: #f0ad4e; +} + +.strength-good { + background-color: #5cb85c; +} + +.footer { + padding-top: 20px; +} + +.hidden { + display: none; +} + +.dataTables_filter, .dataTables_paginate { + float: right; +} + +.pagination { + margin: 0; +} + +.fixed-corner { + position: fixed; + bottom: 0; + right: 2px; + padding: 0 7px; + font-style: italic; + font-size: small; +} + +#fileData { + margin-top: 1em; +} + +.admin-login { + color: #555; +} + +a.public { + color: forestgreen; +} + +i.ext-link { + font-size: smaller; +} + +p.caption { + padding-top: 0.8em; +} + +.mb0 { + margin-bottom: 0px; +} + +.mb5 { + margin-bottom: 5px; +} + +.mb15 { + margin-bottom: 15px; +} + +.mt20 { + margin-top: 20px; +} + +.p48>tbody>tr>td { + padding: 4px 8px; +} + +.btn-reg { + width: 5em; +} + +.btn-wide { + width: 10em; +} + +.vmiddle { + vertical-align: middle !important; +} + +.bd-modal-lg .modal-dialog { + display: table; + position: relative; + margin: 0 auto; + top: calc(40% - 24px); +} + +.bd-modal-lg .modal-dialog .modal-content { + text-align: center; + border: none; +} + +.non-fluid { + width: auto !important; +} + +.vizpwd { + float: left; + margin-left: 180px; + margin-top: -23px; + position: relative; + z-index: 2; +} diff --git a/www/css/metisMenu.min.css b/www/css/metisMenu.min.css new file mode 100644 index 0000000..a1d0ef3 --- /dev/null +++ b/www/css/metisMenu.min.css @@ -0,0 +1,10 @@ +/* + * metismenu - v1.1.3 + * Easy menu jQuery plugin for Twitter Bootstrap 3 + * https://github.com/onokumus/metisMenu + * + * Made by Osman Nuri Okumus + * Under MIT License + */ + +.arrow{float:right;line-height:1.42857}.glyphicon.arrow:before{content:"\e079"}.active>a>.glyphicon.arrow:before{content:"\e114"}.fa.arrow:before{content:"\f104"}.active>a>.fa.arrow:before{content:"\f107"}.plus-times{float:right}.fa.plus-times:before{content:"\f067"}.active>a>.fa.plus-times{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.plus-minus{float:right}.fa.plus-minus:before{content:"\f067"}.active>a>.fa.plus-minus:before{content:"\f068"} \ No newline at end of file diff --git a/www/css/sb-admin-2.min.css b/www/css/sb-admin-2.min.css new file mode 100644 index 0000000..a685970 --- /dev/null +++ b/www/css/sb-admin-2.min.css @@ -0,0 +1,5 @@ +/*! + * Start Bootstrap - SB Admin 2 v3.3.7+1 (http://startbootstrap.com/template-overviews/sb-admin-2) + * Copyright 2013-2016 Start Bootstrap + * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE) + */.chat,.timeline{list-style:none}body{background-color:#f8f8f8}#wrapper{width:100%}#page-wrapper{padding:0 15px;min-height:568px;background-color:#fff}@media (min-width:768px){#page-wrapper{position:inherit;margin:0 0 0 250px;padding:0 30px;border-left:1px solid #e7e7e7}}.navbar-top-links{margin-right:0}.navbar-top-links li{display:inline-block}.flot-chart,.navbar-top-links .dropdown-menu li{display:block}.navbar-top-links li:last-child{margin-right:15px}.navbar-top-links li a{padding:15px;min-height:50px}.navbar-top-links .dropdown-menu li:last-child{margin-right:0}.navbar-top-links .dropdown-menu li a{padding:3px 20px;min-height:0}.navbar-top-links .dropdown-menu li a div{white-space:normal}.navbar-top-links .dropdown-alerts,.navbar-top-links .dropdown-messages,.navbar-top-links .dropdown-tasks{width:310px;min-width:0}.navbar-top-links .dropdown-messages{margin-left:5px}.navbar-top-links .dropdown-tasks{margin-left:-59px}.navbar-top-links .dropdown-alerts{margin-left:-123px}.navbar-top-links .dropdown-user{right:0;left:auto}.sidebar .sidebar-nav.navbar-collapse{padding-left:0;padding-right:0}.sidebar .sidebar-search{padding:15px}.sidebar ul li{border-bottom:1px solid #e7e7e7}.sidebar ul li a.active{background-color:#eee}.sidebar .arrow{float:right}.sidebar .fa.arrow:before{content:"\f104"}.sidebar .active>a>.fa.arrow:before{content:"\f107"}.sidebar .nav-second-level li,.sidebar .nav-third-level li{border-bottom:none!important}.sidebar .nav-second-level li a{padding-left:37px}.sidebar .nav-third-level li a{padding-left:52px}@media (min-width:768px){.sidebar{z-index:1;position:absolute;width:250px;margin-top:51px}.navbar-top-links .dropdown-alerts,.navbar-top-links .dropdown-messages,.navbar-top-links .dropdown-tasks{margin-left:auto}}.btn-outline{color:inherit;background-color:transparent;transition:all .5s}.btn-primary.btn-outline{color:#428bca}.btn-success.btn-outline{color:#5cb85c}.btn-info.btn-outline{color:#5bc0de}.btn-warning.btn-outline{color:#f0ad4e}.btn-danger.btn-outline{color:#d9534f}.btn-danger.btn-outline:hover,.btn-info.btn-outline:hover,.btn-primary.btn-outline:hover,.btn-success.btn-outline:hover,.btn-warning.btn-outline:hover{color:#fff}.chat{margin:0;padding:0}.chat li{margin-bottom:10px;padding-bottom:5px;border-bottom:1px dotted #999}.chat li.left .chat-body{margin-left:60px}.chat li.right .chat-body{margin-right:60px}.chat li .chat-body p{margin:0}.chat .glyphicon,.panel .slidedown .glyphicon{margin-right:5px}.chat-panel .panel-body{height:350px;overflow-y:scroll}.login-panel{margin-top:25%}.flot-chart{height:400px}.flot-chart-content{width:100%;height:100%}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_desc_disabled{background:0 0}table.dataTable thead .sorting_asc:after{content:"\f0de";float:right;font-family:fontawesome}table.dataTable thead .sorting_desc:after{content:"\f0dd";float:right;font-family:fontawesome}table.dataTable thead .sorting:after{content:"\f0dc";float:right;font-family:fontawesome;color:rgba(50,50,50,.5)}.btn-circle{width:30px;height:30px;padding:6px 0;border-radius:15px;text-align:center;font-size:12px;line-height:1.428571429}.btn-circle.btn-lg{width:50px;height:50px;padding:10px 16px;border-radius:25px;font-size:18px;line-height:1.33}.btn-circle.btn-xl{width:70px;height:70px;padding:10px 16px;border-radius:35px;font-size:24px;line-height:1.33}.show-grid [class^=col-]{padding-top:10px;padding-bottom:10px;border:1px solid #ddd;background-color:#eee!important}.show-grid{margin:15px 0}.huge{font-size:40px}.panel-green{border-color:#5cb85c}.panel-green>.panel-heading{border-color:#5cb85c;color:#fff;background-color:#5cb85c}.panel-green>a{color:#5cb85c}.panel-green>a:hover{color:#3d8b3d}.panel-red{border-color:#d9534f}.panel-red>.panel-heading{border-color:#d9534f;color:#fff;background-color:#d9534f}.panel-red>a{color:#d9534f}.panel-red>a:hover{color:#b52b27}.panel-yellow{border-color:#f0ad4e}.panel-yellow>.panel-heading{border-color:#f0ad4e;color:#fff;background-color:#f0ad4e}.panel-yellow>a{color:#f0ad4e}.panel-yellow>a:hover{color:#df8a13}.timeline{position:relative;padding:20px 0}.timeline:before{content:" ";position:absolute;top:0;bottom:0;left:50%;width:3px;margin-left:-1.5px;background-color:#eee}.timeline>li{position:relative;margin-bottom:20px}.timeline>li:after,.timeline>li:before{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-panel{float:left;position:relative;width:46%;padding:20px;border:1px solid #d4d4d4;border-radius:2px;-webkit-box-shadow:0 1px 6px rgba(0,0,0,.175);box-shadow:0 1px 6px rgba(0,0,0,.175)}.timeline>li>.timeline-panel:before{content:" ";display:inline-block;position:absolute;top:26px;right:-15px;border-top:15px solid transparent;border-right:0 solid #ccc;border-bottom:15px solid transparent;border-left:15px solid #ccc}.timeline>li>.timeline-panel:after{content:" ";display:inline-block;position:absolute;top:27px;right:-14px;border-top:14px solid transparent;border-right:0 solid #fff;border-bottom:14px solid transparent;border-left:14px solid #fff}.timeline>li>.timeline-badge{z-index:100;position:absolute;top:16px;left:50%;width:50px;height:50px;margin-left:-25px;border-radius:50%;text-align:center;font-size:1.4em;line-height:50px;color:#fff;background-color:#999}.timeline>li.timeline-inverted>.timeline-panel{float:right}.timeline>li.timeline-inverted>.timeline-panel:before{right:auto;left:-15px;border-right-width:15px;border-left-width:0}.timeline>li.timeline-inverted>.timeline-panel:after{right:auto;left:-14px;border-right-width:14px;border-left-width:0}.timeline-badge.primary{background-color:#2e6da4!important}.timeline-badge.success{background-color:#3f903f!important}.timeline-badge.warning{background-color:#f0ad4e!important}.timeline-badge.danger{background-color:#d9534f!important}.timeline-badge.info{background-color:#5bc0de!important}.timeline-title{margin-top:0;color:inherit}.timeline-body>p,.timeline-body>ul{margin-bottom:0}.timeline-body>p+p{margin-top:5px}@media (max-width:767px){ul.timeline:before{left:40px}ul.timeline>li>.timeline-panel{width:calc(10%);width:-moz-calc(10%);width:-webkit-calc(10%);float:right}ul.timeline>li>.timeline-badge{top:16px;left:15px;margin-left:0}ul.timeline>li>.timeline-panel:before{right:auto;left:-15px;border-right-width:15px;border-left-width:0}ul.timeline>li>.timeline-panel:after{right:auto;left:-14px;border-right-width:14px;border-left-width:0}} \ No newline at end of file diff --git a/www/fonts/FontAwesome.otf b/www/fonts/FontAwesome.otf new file mode 100644 index 0000000..d4de13e Binary files /dev/null and b/www/fonts/FontAwesome.otf differ diff --git a/www/fonts/fontawesome-webfont.eot b/www/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..c7b00d2 Binary files /dev/null and b/www/fonts/fontawesome-webfont.eot differ diff --git a/www/fonts/fontawesome-webfont.svg b/www/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..8b66187 --- /dev/null +++ b/www/fonts/fontawesome-webfont.svg @@ -0,0 +1,685 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/fonts/fontawesome-webfont.ttf b/www/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..f221e50 Binary files /dev/null and b/www/fonts/fontawesome-webfont.ttf differ diff --git a/www/fonts/fontawesome-webfont.woff b/www/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..6e7483c Binary files /dev/null and b/www/fonts/fontawesome-webfont.woff differ diff --git a/www/fonts/fontawesome-webfont.woff2 b/www/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..7eb74fd Binary files /dev/null and b/www/fonts/fontawesome-webfont.woff2 differ diff --git a/www/fonts/glyphicons-halflings-regular.eot b/www/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..b93a495 Binary files /dev/null and b/www/fonts/glyphicons-halflings-regular.eot differ diff --git a/www/fonts/glyphicons-halflings-regular.svg b/www/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..94fb549 --- /dev/null +++ b/www/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/fonts/glyphicons-halflings-regular.ttf b/www/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/www/fonts/glyphicons-halflings-regular.ttf differ diff --git a/www/fonts/glyphicons-halflings-regular.woff b/www/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/www/fonts/glyphicons-halflings-regular.woff differ diff --git a/www/fonts/glyphicons-halflings-regular.woff2 b/www/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000..64539b5 Binary files /dev/null and b/www/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/www/img/fav-admin.png b/www/img/fav-admin.png new file mode 100644 index 0000000..d093332 Binary files /dev/null and b/www/img/fav-admin.png differ diff --git a/www/img/fav-public.png b/www/img/fav-public.png new file mode 100644 index 0000000..93dbbef Binary files /dev/null and b/www/img/fav-public.png differ diff --git a/www/img/spinner.gif b/www/img/spinner.gif new file mode 100644 index 0000000..81194c9 Binary files /dev/null and b/www/img/spinner.gif differ diff --git a/www/img/warning.png b/www/img/warning.png new file mode 100644 index 0000000..9b012da Binary files /dev/null and b/www/img/warning.png differ diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..e244430 --- /dev/null +++ b/www/index.html @@ -0,0 +1,91 @@ + + + + + + + + + + + LabCA + + + + + + + + + +
+ + +
+
+
+

LabCA

+ +

+ LabCA is a private CA (Certificate Authority) for use inside an organization, i.e. for creating HTTPS/SSL certificates + for machines that cannot be reached via the open internet. It is based on Let's Encrypt™ code for ACMEv2 + (Automated Certificate Management Environment) so all modern + LE clients should work. LabCA should not be used on the open internet, please use the official + Let's Encrypt™ + instance for that. +

+

+ To trust the certificates provided by LabCA, all your client devices should install the root certificate in their + Trusted Root Certification Authorities store. You may choose to download the format best suited for your + Operating System: DER format for Windows machines or PEM format for Linux/unix machines and Android phones:
+ Windows (.der) format |  + Linux (.pem) format +

+
+

More information

+

Additional information about this LabCA instance can be found on these pages:
+ Terms - the Usage Terms
+ CPS - the Certification Practice Statement +

+
+
+
+
+ + + + + + + + diff --git a/www/js/bootstrap.min.js b/www/js/bootstrap.min.js new file mode 100644 index 0000000..9bcd2fc --- /dev/null +++ b/www/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/www/js/dataTables.bootstrap.min.js b/www/js/dataTables.bootstrap.min.js new file mode 100644 index 0000000..f0d09b9 --- /dev/null +++ b/www/js/dataTables.bootstrap.min.js @@ -0,0 +1,8 @@ +/*! + DataTables Bootstrap 3 integration + ©2011-2014 SpryMedia Ltd - datatables.net/license +*/ +(function(){var f=function(c,b){c.extend(!0,b.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>>",renderer:"bootstrap"});c.extend(b.ext.classes,{sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm"});b.ext.renderer.pageButton.bootstrap=function(g,f,p,k,h,l){var q=new b.Api(g),r=g.oClasses,i=g.oLanguage.oPaginate,d,e,o=function(b,f){var j,m,n,a,k=function(a){a.preventDefault(); +c(a.currentTarget).hasClass("disabled")||q.page(a.data.action).draw(!1)};j=0;for(m=f.length;j",{"class":r.sPageButton+" "+ +e,"aria-controls":g.sTableId,tabindex:g.iTabIndex,id:0===p&&"string"===typeof a?g.sTableId+"_"+a:null}).append(c("",{href:"#"}).html(d)).appendTo(b),g.oApi._fnBindAction(n,{action:a},k))}};o(c(f).empty().html('
    ').children("ul"),k)};b.TableTools&&(c.extend(!0,b.TableTools.classes,{container:"DTTT btn-group",buttons:{normal:"btn btn-default",disabled:"disabled"},collection:{container:"DTTT_dropdown dropdown-menu",buttons:{normal:"",disabled:"disabled"}},print:{info:"DTTT_print_info"}, +select:{row:"active"}}),c.extend(!0,b.TableTools.DEFAULTS.oTags,{collection:{container:"ul",button:"li",liner:"a"}}))};"function"===typeof define&&define.amd?define(["jquery","datatables"],f):"object"===typeof exports?f(require("jquery"),require("datatables")):jQuery&&f(jQuery,jQuery.fn.dataTable)})(window,document); diff --git a/www/js/dataTables.responsive.js b/www/js/dataTables.responsive.js new file mode 100644 index 0000000..5b4743f --- /dev/null +++ b/www/js/dataTables.responsive.js @@ -0,0 +1,873 @@ +/*! Responsive 1.0.6 + * 2014-2015 SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary Responsive + * @description Responsive tables plug-in for DataTables + * @version 1.0.6 + * @file dataTables.responsive.js + * @author SpryMedia Ltd (www.sprymedia.co.uk) + * @contact www.sprymedia.co.uk/contact + * @copyright Copyright 2014-2015 SpryMedia Ltd. + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license/mit + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ + +(function(window, document, undefined) { + + +var factory = function( $, DataTable ) { +"use strict"; + +/** + * Responsive is a plug-in for the DataTables library that makes use of + * DataTables' ability to change the visibility of columns, changing the + * visibility of columns so the displayed columns fit into the table container. + * The end result is that complex tables will be dynamically adjusted to fit + * into the viewport, be it on a desktop, tablet or mobile browser. + * + * Responsive for DataTables has two modes of operation, which can used + * individually or combined: + * + * * Class name based control - columns assigned class names that match the + * breakpoint logic can be shown / hidden as required for each breakpoint. + * * Automatic control - columns are automatically hidden when there is no + * room left to display them. Columns removed from the right. + * + * In additional to column visibility control, Responsive also has built into + * options to use DataTables' child row display to show / hide the information + * from the table that has been hidden. There are also two modes of operation + * for this child row display: + * + * * Inline - when the control element that the user can use to show / hide + * child rows is displayed inside the first column of the table. + * * Column - where a whole column is dedicated to be the show / hide control. + * + * Initialisation of Responsive is performed by: + * + * * Adding the class `responsive` or `dt-responsive` to the table. In this case + * Responsive will automatically be initialised with the default configuration + * options when the DataTable is created. + * * Using the `responsive` option in the DataTables configuration options. This + * can also be used to specify the configuration options, or simply set to + * `true` to use the defaults. + * + * @class + * @param {object} settings DataTables settings object for the host table + * @param {object} [opts] Configuration options + * @requires jQuery 1.7+ + * @requires DataTables 1.10.1+ + * + * @example + * $('#example').DataTable( { + * responsive: true + * } ); + * } ); + */ +var Responsive = function ( settings, opts ) { + // Sanity check that we are using DataTables 1.10 or newer + if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.1' ) ) { + throw 'DataTables Responsive requires DataTables 1.10.1 or newer'; + } + + this.s = { + dt: new DataTable.Api( settings ), + columns: [] + }; + + // Check if responsive has already been initialised on this table + if ( this.s.dt.settings()[0].responsive ) { + return; + } + + // details is an object, but for simplicity the user can give it as a string + if ( opts && typeof opts.details === 'string' ) { + opts.details = { type: opts.details }; + } + + this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts ); + settings.responsive = this; + this._constructor(); +}; + +Responsive.prototype = { + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Constructor + */ + + /** + * Initialise the Responsive instance + * + * @private + */ + _constructor: function () + { + var that = this; + var dt = this.s.dt; + + dt.settings()[0]._responsive = this; + + // Use DataTables' private throttle function to avoid processor thrashing + $(window).on( 'resize.dtr orientationchange.dtr', dt.settings()[0].oApi._fnThrottle( function () { + that._resize(); + } ) ); + + // Destroy event handler + dt.on( 'destroy.dtr', function () { + $(window).off( 'resize.dtr orientationchange.dtr draw.dtr' ); + } ); + + // Reorder the breakpoints array here in case they have been added out + // of order + this.c.breakpoints.sort( function (a, b) { + return a.width < b.width ? 1 : + a.width > b.width ? -1 : 0; + } ); + + // Determine which columns are already hidden, and should therefore + // remain hidden. todo - should this be done? See thread 22677 + // + // this.s.alwaysHidden = dt.columns(':hidden').indexes(); + + this._classLogic(); + this._resizeAuto(); + + // Details handler + var details = this.c.details; + if ( details.type ) { + that._detailsInit(); + this._detailsVis(); + + dt.on( 'column-visibility.dtr', function () { + that._detailsVis(); + } ); + + // Redraw the details box on each draw. This is used until + // DataTables implements a native `updated` event for rows + dt.on( 'draw.dtr', function () { + dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) { + var row = dt.row( idx ); + + if ( row.child.isShown() ) { + var info = that.c.details.renderer( dt, idx ); + row.child( info, 'child' ).show(); + } + } ); + } ); + + $(dt.table().node()).addClass( 'dtr-'+details.type ); + } + + // First pass - draw the table for the current viewport size + this._resize(); + }, + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Private methods + */ + + /** + * Calculate the visibility for the columns in a table for a given + * breakpoint. The result is pre-determined based on the class logic if + * class names are used to control all columns, but the width of the table + * is also used if there are columns which are to be automatically shown + * and hidden. + * + * @param {string} breakpoint Breakpoint name to use for the calculation + * @return {array} Array of boolean values initiating the visibility of each + * column. + * @private + */ + _columnsVisiblity: function ( breakpoint ) + { + var dt = this.s.dt; + var columns = this.s.columns; + var i, ien; + + // Class logic - determine which columns are in this breakpoint based + // on the classes. If no class control (i.e. `auto`) then `-` is used + // to indicate this to the rest of the function + var display = $.map( columns, function ( col ) { + return col.auto && col.minWidth === null ? + false : + col.auto === true ? + '-' : + $.inArray( breakpoint, col.includeIn ) !== -1; + } ); + + // Auto column control - first pass: how much width is taken by the + // ones that must be included from the non-auto columns + var requiredWidth = 0; + for ( i=0, ien=display.length ; i
    ') + .css( { + width: 1, + height: 1, + overflow: 'hidden' + } ) + .append( clonedTable ); + + // Remove columns which are not to be included + inserted.find('th.never, td.never').remove(); + + inserted.insertBefore( dt.table().node() ); + + // The cloned header now contains the smallest that each column can be + dt.columns().eq(0).each( function ( idx ) { + columns[idx].minWidth = cells[ idx ].offsetWidth || 0; + } ); + + inserted.remove(); + } +}; + + +/** + * List of default breakpoints. Each item in the array is an object with two + * properties: + * + * * `name` - the breakpoint name. + * * `width` - the breakpoint width + * + * @name Responsive.breakpoints + * @static + */ +Responsive.breakpoints = [ + { name: 'desktop', width: Infinity }, + { name: 'tablet-l', width: 1024 }, + { name: 'tablet-p', width: 768 }, + { name: 'mobile-l', width: 480 }, + { name: 'mobile-p', width: 320 } +]; + + +/** + * Responsive default settings for initialisation + * + * @namespace + * @name Responsive.defaults + * @static + */ +Responsive.defaults = { + /** + * List of breakpoints for the instance. Note that this means that each + * instance can have its own breakpoints. Additionally, the breakpoints + * cannot be changed once an instance has been creased. + * + * @type {Array} + * @default Takes the value of `Responsive.breakpoints` + */ + breakpoints: Responsive.breakpoints, + + /** + * Enable / disable auto hiding calculations. It can help to increase + * performance slightly if you disable this option, but all columns would + * need to have breakpoint classes assigned to them + * + * @type {Boolean} + * @default `true` + */ + auto: true, + + /** + * Details control. If given as a string value, the `type` property of the + * default object is set to that value, and the defaults used for the rest + * of the object - this is for ease of implementation. + * + * The object consists of the following properties: + * + * * `renderer` - function that is called for display of the child row data. + * The default function will show the data from the hidden columns + * * `target` - Used as the selector for what objects to attach the child + * open / close to + * * `type` - `false` to disable the details display, `inline` or `column` + * for the two control types + * + * @type {Object|string} + */ + details: { + renderer: function ( api, rowIdx ) { + var data = api.cells( rowIdx, ':hidden' ).eq(0).map( function ( cell ) { + var header = $( api.column( cell.column ).header() ); + var idx = api.cell( cell ).index(); + + if ( header.hasClass( 'control' ) || header.hasClass( 'never' ) ) { + return ''; + } + + // Use a non-public DT API method to render the data for display + // This needs to be updated when DT adds a suitable method for + // this type of data retrieval + var dtPrivate = api.settings()[0]; + var cellData = dtPrivate.oApi._fnGetCellData( + dtPrivate, idx.row, idx.column, 'display' + ); + var title = header.text(); + if ( title ) { + title = title + ':'; + } + + return '
  • '+ + ''+ + title+ + ' '+ + ''+ + cellData+ + ''+ + '
  • '; + } ).toArray().join(''); + + return data ? + $('
      ').append( data ) : + false; + }, + + target: 0, + + type: 'inline' + } +}; + + +/* + * API + */ +var Api = $.fn.dataTable.Api; + +// Doesn't do anything - work around for a bug in DT... Not documented +Api.register( 'responsive()', function () { + return this; +} ); + +Api.register( 'responsive.index()', function ( li ) { + li = $(li); + + return { + column: li.data('dtr-index'), + row: li.parent().data('dtr-index') + }; +} ); + +Api.register( 'responsive.rebuild()', function () { + return this.iterator( 'table', function ( ctx ) { + if ( ctx._responsive ) { + ctx._responsive._classLogic(); + } + } ); +} ); + +Api.register( 'responsive.recalc()', function () { + return this.iterator( 'table', function ( ctx ) { + if ( ctx._responsive ) { + ctx._responsive._resizeAuto(); + ctx._responsive._resize(); + } + } ); +} ); + + +/** + * Version information + * + * @name Responsive.version + * @static + */ +Responsive.version = '1.0.6'; + + +$.fn.dataTable.Responsive = Responsive; +$.fn.DataTable.Responsive = Responsive; + +// Attach a listener to the document which listens for DataTables initialisation +// events so we can automatically initialise +$(document).on( 'init.dt.dtr', function (e, settings, json) { + if ( e.namespace !== 'dt' ) { + return; + } + + if ( $(settings.nTable).hasClass( 'responsive' ) || + $(settings.nTable).hasClass( 'dt-responsive' ) || + settings.oInit.responsive || + DataTable.defaults.responsive + ) { + var init = settings.oInit.responsive; + + if ( init !== false ) { + new Responsive( settings, $.isPlainObject( init ) ? init : {} ); + } + } +} ); + +return Responsive; +}; // /factory + + +// Define as an AMD module if possible +if ( typeof define === 'function' && define.amd ) { + define( ['jquery', 'datatables'], factory ); +} +else if ( typeof exports === 'object' ) { + // Node/CommonJS + factory( require('jquery'), require('datatables') ); +} +else if ( jQuery && !jQuery.fn.dataTable.Responsive ) { + // Otherwise simply initialise as normal, stopping multiple evaluation + factory( jQuery, jQuery.fn.dataTable ); +} + + +})(window, document); diff --git a/www/js/jquery.dataTables.min.js b/www/js/jquery.dataTables.min.js new file mode 100644 index 0000000..f127725 --- /dev/null +++ b/www/js/jquery.dataTables.min.js @@ -0,0 +1,166 @@ +/*! + DataTables 1.10.12 + ©2008-2015 SpryMedia Ltd - datatables.net/license +*/ +(function(h){"function"===typeof define&&define.amd?define(["jquery"],function(D){return h(D,window,document)}):"object"===typeof exports?module.exports=function(D,I){D||(D=window);I||(I="undefined"!==typeof window?require("jquery"):require("jquery")(D));return h(I,D,D.document)}:h(jQuery,window,document)})(function(h,D,I,k){function X(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()), +d[c]=e,"o"===b[1]&&X(a[e])});a._hungarianMap=d}function K(a,b,c){a._hungarianMap||X(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),K(a[d],b[d],c)):b[d]=b[e]})}function Da(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&E(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&E(a,a,"sZeroRecords","sLoadingRecords"); +a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&db(a)}function eb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");"boolean"===typeof a.scrollX&&(a.scrollX= +a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b").css({position:"fixed",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("
      ").css({position:"absolute",top:1,left:1, +width:100,overflow:"scroll"}).append(h("
      ").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function hb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==e;)a.hasOwnProperty(d)&& +(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Ea(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:I.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);ja(a,d,h(b).data())}function ja(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f= +(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(fb(c),K(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),E(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),E(b,c,"aDataSort"));var g=b.mData,j=Q(g),i=b.mRender?Q(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&& +(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return R(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed): +!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Y(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Fa(a);for(var c=0,d=b.length;cq[f])d(l.length+q[f],n);else if("string"===typeof q[f]){j=0;for(i=l.length;jb&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ca(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild); +c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ia(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c").appendTo(g));b=0;for(c=l.length;btr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(n.sFooterTH); +if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart= +-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!lb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:n;for(j=j?0:g;j",{"class":e?d[0]:""}).append(h("",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ka(a),g,n,i]);u(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ka(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function T(a,b){var c=a.oFeatures,d=c.bFilter; +c.bSort&&mb(a);d?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;O(a);a._drawHold=!1}function nb(a){var b=a.oClasses,c=h(a.nTable),c=h("
      ").insertBefore(c),d=a.oFeatures,e=h("
      ",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,l,q,t=0;t")[0]; +n=f[t+1];if("'"==n||'"'==n){l="";for(q=2;f[t+q]!=n;)l+=f[t+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),i.id=n[0].substr(1,n[0].length-1),i.className=n[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;t+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=ob(a);else if("f"==j&&d.bFilter)g=pb(a);else if("r"==j&&d.bProcessing)g=qb(a);else if("t"==j)g=rb(a);else if("i"==j&&d.bInfo)g=sb(a);else if("p"== +j&&d.bPaginate)g=tb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(n=i.length;q',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("
      ",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("
      ").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());O(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function tb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){O(a)},b=h("
      ").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures; +d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),k,l=0;for(k=f.p.length;lf&&(d=0)):"first"==b?d=0: +"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");u(a,null,"processing", +[a,b])}function rb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");l.length||(l=null);i=h("
      ",{"class":f.sScrollWrapper}).append(h("
      ",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:x(d):"100%"}).append(h("
      ",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box", +width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("
      ",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:x(d)}).append(b));l&&i.append(h("
      ",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:x(d):"100%"}).append(h("
      ",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===j?g:null).append(b.children("tfoot"))))); +var b=i.children(),k=b[0],f=b[1],t=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(t.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=t;a.aoDrawCallback.push({fn:ka,sName:"scrolling"});return i[0]}function ka(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style,n=j.children("table"),j=a.nScrollBody,l=h(j),q=j.style,t=h(a.nScrollFoot).children("div"), +m=t.children("table"),o=h(a.nTHead),F=h(a.nTable),p=F[0],r=p.style,u=a.nTFoot?h(a.nTFoot):null,Eb=a.oBrowser,Ua=Eb.bScrollOversize,s=G(a.aoColumns,"nTh"),P,v,w,y,z=[],A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};v=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==v&&a.scrollBarVis!==k)a.scrollBarVis=v,Y(a);else{a.scrollBarVis=v;F.children("thead, tfoot").remove();u&&(w=u.clone().prependTo(F),P=u.find("tr"),w= +w.find("tr"));y=o.clone().prependTo(F);o=o.find("tr");v=y.find("tr");y.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(qa(a,y),function(b,c){D=Z(a,b);c.style.width=a.aoColumns[D].sWidth});u&&J(function(a){a.style.width=""},w);f=F.outerWidth();if(""===c){r.width="100%";if(Ua&&(F.find("tbody").height()>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(F.outerWidth()-b);f=F.outerWidth()}else""!==d&&(r.width=x(d),f=F.outerWidth());J(E,v);J(function(a){B.push(a.innerHTML); +z.push(x(h(a).css("width")))},v);J(function(a,b){if(h.inArray(a,s)!==-1)a.style.width=z[b]},o);h(v).height(0);u&&(J(E,w),J(function(a){C.push(a.innerHTML);A.push(x(h(a).css("width")))},w),J(function(a,b){a.style.width=A[b]},P),h(w).height(0));J(function(a,b){a.innerHTML='
      '+B[b]+"
      ";a.style.width=z[b]},v);u&&J(function(a,b){a.innerHTML='
      '+C[b]+"
      ";a.style.width= +A[b]},w);if(F.outerWidth()j.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(Ua&&(j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(P-b);(""===c||""!==d)&&L(a,1,"Possible column misalignment",6)}else P="100%";q.width=x(P);g.width=x(P);u&&(a.nScrollFoot.style.width=x(P));!e&&Ua&&(q.height=x(p.offsetHeight+b));c=F.outerWidth();n[0].style.width=x(c);i.width=x(c);d=F.height()>j.clientHeight||"scroll"==l.css("overflow-y");e="padding"+(Eb.bScrollbarLeft?"Left": +"Right");i[e]=d?b+"px":"0px";u&&(m[0].style.width=x(c),t[0].style.width=x(c),t[0].style[e]=d?b+"px":"0px");F.children("colgroup").insertBefore(F.children("thead"));l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function J(a,b,c){for(var d=0,e=0,f=b.length,g,j;e").appendTo(j.find("tbody")); +j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=qa(a,j.find("thead")[0]);for(m=0;m").css({width:o.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(m=0;m").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()").css("width",x(a)).appendTo(b||I.body),d=c[0].offsetWidth;c.remove();return d}function Gb(a,b){var c=Hb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Hb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;fd&&(d=c.length,e=f);return e}function x(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function V(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;ae?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return ce?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(j=0;jg?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=V(a),a=a.oLanguage.oAria,f=0,g=d.length;f/g, +"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0e?e+1:3));e=0;for(f=d.length;ee?e+1:3))}a.aLastSort=d}function Ib(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j= +d.length?[0,c[1]]:c)}));e.search!==k&&h.extend(a.oPreviousSearch,Bb(e.search));b=0;for(c=e.columns.length;b=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Na(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ya(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=W(0,b):a<=d?(c=W(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=W(b-(c-2),b):(c=W(a-d+2,a+d-1),c.push("ellipsis"), +c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return za(b,a)},"num-fmt":function(b){return za(b,a,Xa)},"html-num":function(b){return za(b,a,Aa)},"html-num-fmt":function(b){return za(b,a,Aa,Xa)}},function(b,c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[xa(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this, +b)}}var m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new r(xa(this[v.iApiIndex])):new r(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1): +(""!==d.sX||""!==d.sY)&&ka(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a, +c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(), +[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return xa(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener= +function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=v.internal;for(var e in m.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},e=1t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&!o.renderer.header&&(o.renderer.header="jqueryui"): +o.renderer="jqueryui":h.extend(i,m.ext.classes,e.oClasses);q.addClass(i.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=e.iDisplayStart,o._iDisplayStart=e.iDisplayStart);null!==e.iDeferLoading&&(o.bDeferLoading=!0,g=h.isArray(e.iDeferLoading),o._iRecordsDisplay=g?e.iDeferLoading[0]:e.iDeferLoading,o._iRecordsTotal=g?e.iDeferLoading[1]:e.iDeferLoading);var r=o.oLanguage;h.extend(!0,r,e.oLanguage);""!==r.sUrl&&(h.ajax({dataType:"json",url:r.sUrl,success:function(a){Da(a);K(l.oLanguage,a);h.extend(true, +r,a);ga(o)},error:function(){ga(o)}}),n=!0);null===e.asStripeClasses&&(o.asStripeClasses=[i.sStripeOdd,i.sStripeEven]);var g=o.asStripeClasses,v=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return v.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),o.asDestroyStripes=g.slice());t=[];g=this.getElementsByTagName("thead");0!==g.length&&(da(o.aoHeader,g[0]),t=qa(o));if(null===e.aoColumns){p=[];g=0;for(j=t.length;g").appendTo(this));o.nTHead=j[0];j=q.children("tbody");0===j.length&&(j=h("").appendTo(this));o.nTBody=j[0];j=q.children("tfoot");if(0===j.length&&0").appendTo(this);0===j.length||0===j.children().length?q.addClass(i.sNoFooter):0/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,cc=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,M=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(Qa(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g, +"").replace(Ya[b],"."):a},Za=function(a,b,c){var d="string"===typeof a;if(M(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Xa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return M(a)?!0:!(M(a)||"string"===typeof a)?null:Za(a.replace(Aa,""),b,c)?!0:null},G=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e")[0],Zb=ua.textContent!==k,$b=/<.*?>/g,Oa=m.util.throttle,Tb=[],w=Array.prototype,dc=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]: +null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};r=function(a,b){if(!(this instanceof r))return new r(a,b);var c=[],d=function(a){(a=dc(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;ea?new r(b[a],this[a]):null},filter:function(a){var b=[];if(w.filter)b=w.filter.call(this,a,this);else for(var c=0,d=this.length;c").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=aa(d),e.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this}); +p(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});p(["row().child.remove()","row().child().remove()"],function(){cb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var ec=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d=0?b:g.length+b];if(typeof a==="function"){var e=Ba(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(ec):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1], +10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[Z(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length||!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});s("columns().header()", +"column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});s("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});s("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});s("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});s("columns().cache()","column().cache()", +function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ha(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});s("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ha(a.aoData,e,"anCells",b)},1)});s("columns().visible()","column().visible()",function(a,b){var c=this.iterator("column",function(b,c){if(a===k)return b.aoColumns[c].bVisible;var f=b.aoColumns,g=f[c],j=b.aoData,i,n,l;if(a!==k&&g.bVisible!==a){if(a){var m= +h.inArray(!0,G(f,"bVisible"),c+1);i=0;for(n=j.length;id;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]: +null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});return b?new r(c):c};m.camelToHungarian=K;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)|| +(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",function(a){na(a)})});p("settings()",function(){return new r(this.context,this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return G(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode, +d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new r(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(D).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));b.aaSorting=[];b.aaSortingFixed=[];va(b);h(l).removeClass(b.asStripeClasses.join(" ")); +h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";i[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),i.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a% +p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){p(b+"s().every()",function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,n){a.call(e[b](g,"cell"===b?h:d,"cell"===b?d:k),g,h,i,n)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=Q(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.12";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0, +sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null, +sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null, +fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1=== +a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries", +sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET", +renderer:null,rowId:"DT_RowId"};X(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};X(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null, +bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[], +aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k, +fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a= +this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=v={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{}, +header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd", +sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead", +sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ca="",Ca="",H=Ca+"ui-state-default",ia=Ca+"css_right ui-icon ui-icon-",Xb=Ca+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses, +m.ext.classes,{sPageButton:"fg-button ui-button "+H,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:H+" sorting_asc",sSortDesc:H+" sorting_desc",sSortable:H+" sorting",sSortableAsc:H+" sorting_asc_disabled",sSortableDesc:H+" sorting_desc_disabled",sSortableNone:H+" sorting_disabled",sSortJUIAsc:ia+"triangle-1-n",sSortJUIDesc:ia+"triangle-1-s",sSortJUI:ia+"carat-2-n-s", +sSortJUIAscAllowed:ia+"carat-1-n",sSortJUIDescAllowed:ia+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+H,sScrollFoot:"dataTables_scrollFoot "+H,sHeaderTH:H,sFooterTH:H,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ya(a, +b)]},simple_numbers:function(a,b){return["previous",ya(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ya(a,b),"next","last"]},_numbers:ya,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},k,l,m=0,p=function(b,d){var o,r,u,s,v=function(b){Ta(a,b.data.action,true)};o=0;for(r=d.length;o").appendTo(b);p(u,s)}else{k=null; +l="";switch(s){case "ellipsis":b.append('');break;case "first":k=j.sFirst;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":k=j.sPrevious;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":k=j.sNext;l=s+(e",{"class":g.sPageButton+" "+l,"aria-controls":a.sTableId,"aria-label":i[s], +"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(k).appendTo(b);Wa(u,{action:s},v);m++}}}},r;try{r=h(b).find(I.activeElement).data("dt-idx")}catch(o){}p(h(b).empty(),d);r&&h(b).find("[data-dt-idx="+r+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||M(a)?"date": +null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return M(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Aa,""):""},string:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob, +" "):a}});var za=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return M(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return M(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a, +b){return ab?-1:0}});db("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("
      ").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e, +f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var Yb=function(a){return"string"===typeof a?a.replace(//g,">").replace(/"/g,"""):a};m.render={number:function(a, +b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return Yb(f);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:Yb}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:lb,_fnAjaxParameters:ub,_fnAjaxUpdateDraw:vb,_fnAjaxDataSrc:sa,_fnAddColumn:Ea,_fnColumnOptions:ja, +_fnAdjustColumnSizing:Y,_fnVisibleToColumnIndex:Z,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:la,_fnColumnTypes:Ga,_fnApplyColumnDefs:ib,_fnHungarianMap:X,_fnCamelToHungarian:K,_fnLanguageCompat:Da,_fnBrowserDetect:gb,_fnAddData:N,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:jb,_fnSplitObjNotation:Ja,_fnGetObjectDataFn:Q,_fnSetObjectDataFn:R, +_fnGetDataMaster:Ka,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ca,_fnGetRowElements:Ia,_fnCreateTr:Ha,_fnBuildHead:kb,_fnDrawHead:ea,_fnDraw:O,_fnReDraw:T,_fnAddOptionsHtml:nb,_fnDetectHeader:da,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:pb,_fnFilterComplete:fa,_fnFilterCustom:yb,_fnFilterColumn:xb,_fnFilter:wb,_fnFilterCreateSearch:Pa,_fnEscapeRegex:Qa,_fnFilterData:zb,_fnFeatureHtmlInfo:sb,_fnUpdateInfo:Cb,_fnInfoMacros:Db,_fnInitialise:ga,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:ob, +_fnFeatureHtmlPaginate:tb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:qb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:rb,_fnScrollDraw:ka,_fnApplyToChildren:J,_fnCalculateColumnWidths:Fa,_fnThrottle:Oa,_fnConvertToWidth:Fb,_fnGetWidestNode:Gb,_fnGetMaxLenString:Hb,_fnStringToCss:x,_fnSortFlatten:V,_fnSort:mb,_fnSortAria:Jb,_fnSortListener:Va,_fnSortAttachListener:Ma,_fnSortingClasses:va,_fnSortData:Ib,_fnSaveState:wa,_fnLoadState:Kb,_fnSettingsFromNode:xa,_fnLog:L,_fnMap:E,_fnBindAction:Wa,_fnCallbackReg:z, +_fnCallbackFire:u,_fnLengthOverflow:Sa,_fnRenderer:Na,_fnDataSource:y,_fnRowAttributes:La,_fnCalculateEnd:function(){}});h.fn.dataTable=m;m.$=h;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable}); diff --git a/www/js/jquery.min.js b/www/js/jquery.min.js new file mode 100644 index 0000000..f6a6a99 --- /dev/null +++ b/www/js/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.1.0 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.0",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null!=a?a<0?this[a+this.length]:this[a]:f.call(this)},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"label"in b&&b.disabled===a||"form"in b&&b.disabled===a||"form"in b&&b.disabled===!1&&(b.isDisabled===a||b.isDisabled!==!a&&("label"in b||!ea(b))!==a)}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(_,aa),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=V.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(_,aa),$.test(j[0].type)&&qa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&sa(j),!a)return G.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||$.test(a)&&qa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){if(r.isFunction(b))return r.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return r.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(C.test(b))return r.filter(b,a,c);b=r.filter(b,a)}return r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType})}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/\S+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0, +r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ja=/^$|\/(?:java|ecma)script/i,ka={option:[1,""],thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};ka.optgroup=ka.option,ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead,ka.th=ka.td;function la(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function ma(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=la(l.appendChild(f),"script"),j&&ma(g),c){k=0;while(f=g[k++])ja.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var pa=d.documentElement,qa=/^key/,ra=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,sa=/^([^.]*)(?:\.(.+)|)/;function ta(){return!0}function ua(){return!1}function va(){try{return d.activeElement}catch(a){}}function wa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)wa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ua;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(pa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c-1:r.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h\x20\t\r\n\f]*)[^>]*)\/>/gi,ya=/\s*$/g;function Ca(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Da(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ea(a){var b=Aa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&za.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(m&&(e=oa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(la(e,"script"),Da),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=la(h),f=la(a),d=0,e=f.length;d0&&ma(g,!i&&la(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(la(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!ya.test(a)&&!ka[(ia.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Xa(a,b,c,d,e){return new Xa.prototype.init(a,b,c,d,e)}r.Tween=Xa,Xa.prototype={constructor:Xa,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Xa.propHooks[this.prop];return a&&a.get?a.get(this):Xa.propHooks._default.get(this)},run:function(a){var b,c=Xa.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Xa.propHooks._default.set(this),this}},Xa.prototype.init.prototype=Xa.prototype,Xa.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Xa.propHooks.scrollTop=Xa.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Xa.prototype.init,r.fx.step={};var Ya,Za,$a=/^(?:toggle|show|hide)$/,_a=/queueHooks$/;function ab(){Za&&(a.requestAnimationFrame(ab),r.fx.tick())}function bb(){return a.setTimeout(function(){Ya=void 0}),Ya=r.now()}function cb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=aa[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function db(a,b,c){for(var d,e=(gb.tweeners[b]||[]).concat(gb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?hb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K); +if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),hb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ib[b]||r.find.attr;ib[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=ib[g],ib[g]=e,e=null!=c(a,b,d)?g:null,ib[g]=f),e}});var jb=/^(?:input|select|textarea|button)$/i,kb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):jb.test(a.nodeName)||kb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});var lb=/[\t\r\n\f]/g;function mb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,mb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,mb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,mb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=mb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(c)+" ").replace(lb," ").indexOf(b)>-1)return!0;return!1}});var nb=/\r/g,ob=/[\x20\t\r\n\f]+/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(nb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:r.trim(r.text(a)).replace(ob," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type,g=f?null:[],h=f?e+1:d.length,i=e<0?h:f?e:0;i-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ha.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,""),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" + + + + + +