diff --git a/.gitignore b/.gitignore index 274d13b..30786ed 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__/ # Distribution / packaging .Python env/ +env build/ develop-eggs/ dist/ @@ -56,3 +57,7 @@ target/ .DS_Store files/* .idea + +db.sqlite3 + +data/* diff --git a/LICENSE b/LICENSE index e06d208..8f71f43 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Apache License + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/README.md b/README.md index 790adf2..88ecb1a 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,40 @@ SDN Internet Router (sir) ========================= -This tool in combination with [pmacct](http://www.pmacct.net/) allows you to get the full BGP feed from your upstream providers/peers and install on the FIB only the relevant prefixes for you. The main benefit of this approach is that you will not need a very expensive router to do peering. A cheap and very fast switch might be enough. +The SDN Internet Router, abbreviated SIR, is an agent that you can add to your router. The agent exposes information +that your router can't expose by itself like the BGP table, traffic per BGP prefix or traffic per ASN. This data +is provided both via a WebUI and an API to access this data. -I recommend you to start reading the [How To: Simple Setup](http://sdn-internet-router-sir.readthedocs.org/en/latest/how_to_simple/index.html), there you can see what this is about, what you need and how to achieve it. +The agent is vendor agnostic as it gathers data using both BGP and netflow/sflow/ipfix. This means it can be attached +to any router or switch that supports those protocols. -You can also check the following [slides](docs/_static/SDN_Internet_Router-sir-Nov14.pdf) and [video](http://youtu.be/o1njanXhQqM?list=PLXSSXAe33jI2IIWtfnnEj5J7B7KoixKCe). +Features +======== -Documentation -============= +The agent will expose a Web UI and an API that will allow you do things like: -You can find the documentation on [Read the Docs](http://sdn-internet-router-sir.readthedocs.org/en/latest/). +* Retrieve Top ASN's based in bandwidth usage. +* Retrieve Top prefixes based in bandwidth usage. +* Simulate what would happen if you had top N prefixes only in your FIB instead of the full routing table. +* Store and retrieve arbitrary data. +* Get raw BGP from your router. +* Get raw flow data from your router. +* Look for all the prefixes that traverses or originates in a particular ASN. +* Check all the prefixes in the router that allows you to reach certain prefixes or IP. + +You can read the full list of features in the following [link](http://sdn-internet-router-sir.readthedocs.org/en/latest/features/index.html). + +Applications +============ +This agent will give you some visibility about your network. You can use this data to better choose your network equipment, to do traffic engineering, capacity planning, peering decisions... anything you want. You can see some use cases in the following [link](http://sdn-internet-router-sir.readthedocs.org/en/latest/use_cases/index.html). -Note -==== +Here is a list of links where you can get tools that leverages on SIR: -This software is in very early stages. There is a lot of documentation missing and some bits and pieces might be refactored. If you plan to test it I suggest you contact me. I can help you to deploy it while keeping you informed of the changes I might be doing that could potentially affect you. +* [pySIR](https://github.com/dbarrosop/pySIR) - This is a python API that helps you interact with the API. It implements all the API calls that the API supports. It's just a convenient of coding without taking care of the requests or having to handle errors. +* [sir_tools](https://github.com/dbarrosop/sir_tools) - A collection of tools that takes advantage of SIR API to perform several operations. +Documentation +============= + +You can find the documentation on [Read the Docs](http://sdn-internet-router-sir.readthedocs.org/en/latest/). diff --git a/bgp_controller/__init__.py b/analytics/__init__.py similarity index 100% rename from bgp_controller/__init__.py rename to analytics/__init__.py diff --git a/analytics/api.py b/analytics/api.py new file mode 100644 index 0000000..f0a0195 --- /dev/null +++ b/analytics/api.py @@ -0,0 +1,69 @@ +import helpers.api +from flask import g + + +def top_prefixes(request): + # curl http://127.0.0.1:5000/api/v1.0/top_prefixes\?limit_prefixes=10\&start_time\=2015-07-13T14:00\&end_time\=2015-07-14T14:00 + db = getattr(g, 'db') + start_time = request.args.get('start_time') + end_time = request.args.get('end_time') + limit_prefixes = int(request.args.get('limit_prefixes', 0)) + net_masks = request.args.get('net_masks', '') + exclude_net_masks = request.args.get('exclude_net_masks', False) + + result = db.aggregate_per_prefix( + start_time, end_time, + limit=limit_prefixes, + net_masks=net_masks, + exclude_net_masks=exclude_net_masks) + + parameters = { + 'limit_prefixes': limit_prefixes, + 'start_time': start_time, + 'end_time': end_time, + 'net_masks': net_masks, + 'exclude_net_masks': exclude_net_masks, + } + return helpers.api.build_api_response(result, error=False, **parameters) + + +def top_asns(request): + # curl http://127.0.0.1:5000/api/v1.0/top_asns\?start_time=2015-07-13T14:00\&end_time=2015-07-14T14:00 + db = getattr(g, 'db') + start_time = request.args.get('start_time') + end_time = request.args.get('end_time') + + result = db.aggregate_per_as(start_time, end_time) + parameters = { + 'start_time': start_time, + 'end_time': end_time, + } + return helpers.api.build_api_response(result, error=False, **parameters) + + +def find_prefix(request, prefix): + # curl http://127.0.0.1:5000/api/v1.0/top_asns\?start_time=2015-07-13T14:00\&end_time=2015-07-14T14:00 + fs = getattr(g, 'fs') + date = request.args.get('date') + print date + result = fs.find_prefix(prefix, date) + parameters = { + 'prefix': prefix, + 'date': date, + } + return helpers.api.build_api_response(result, error=False, **parameters) + + +def find_prefixes_asn(request, asn): + # curl http://127.0.0.1:5000/api/v1.0/top_asns\?start_time=2015-07-13T14:00\&end_time=2015-07-14T14:00 + fs = getattr(g, 'fs') + date = request.args.get('date') + origin_only = request.args.get('origin_only', False) + + result = fs.find_prefixes_asn(asn, date, origin_only) + parameters = { + 'asn': asn, + 'date': date, + 'origin_only': origin_only, + } + return helpers.api.build_api_response(result, error=False, **parameters) diff --git a/analytics/views.py b/analytics/views.py new file mode 100644 index 0000000..7846e14 --- /dev/null +++ b/analytics/views.py @@ -0,0 +1,128 @@ +from flask import render_template +from flask import g + + +def _init_context_dates(db, request): + context = dict() + dates = db.get_dates() + + starting_time = min(len(dates), 25) + + context['avail_start_time'] = dates[0].strftime('%Y-%m-%dT%H:%M') + context['avail_end_time'] = dates[-1].strftime('%Y-%m-%dT%H:%M') + context['start_time'] = request.form.get('start_time', dates[-starting_time].strftime('%Y-%m-%dT%H:%M')) + context['end_time'] = request.form.get('end_time', context['avail_end_time']) + return context + + +def start_page(request): + return render_template('analytics/start_page.html') + + +def offloaded_traffic(request): + db = getattr(g, 'db', None) + context = _init_context_dates(db, request) + + context['num_prefixes'] = int(request.form.get('num_prefixes', 1000)) + + if request.method == 'GET': + context['total_bytes'] = 0 + context['offloaded_bytes'] = 0 + context['percentage'] = 0.0 + elif request.method == 'POST': + context['total_bytes'] = db.get_total_traffic(context['start_time'], context['end_time']) + context['offloaded_bytes' + ] = db.offloaded_bytes(context['num_prefixes'], context['start_time'], context['end_time']) + context['percentage'] = float(context['offloaded_bytes']) * 100.0 / float(context['total_bytes']) + + return render_template('analytics/offloaded_traffic.html', **context) + + +def aggregate(request, field): + db = getattr(g, 'db', None) + context = _init_context_dates(db, request) + + context['flow_aggr'] = list() + context['time_series'] = dict() + context['time_series_times'] = list() + + if field == 'as': + aggregate_method = db.aggregate_per_as + timeseries_method = db.timeseries_per_as + context['title'] = 'ASN\'s' + elif field == 'prefix': + aggregate_method = db.aggregate_per_prefix + timeseries_method = db.timeseries_per_prefix + context['title'] = 'Prefixes' + + if request.method == 'POST': + context['time_series_times'] = db.get_dates_in_range(context['start_time'], context['end_time']) + + context['flow_aggr'] = aggregate_method(context['start_time'], context['end_time']) + time_series = dict() + for a in context['flow_aggr'][0:10]: + time_series[a['key']] = timeseries_method(context['start_time'], context['end_time'], a['key']) + + context['time_series'] = time_series + + return render_template('analytics/analytics_aggregate.html', **context) + + +def simulate(request): + db = getattr(g, 'db', None) + context = _init_context_dates(db, request) + + context['num_prefixes'] = int(request.form.get('num_prefixes', 1000)) + context['time_series'] = dict() + context['time_series_times'] = list() + + if request.method == 'POST': + context['time_series_times'] = db.get_dates_in_range(context['start_time'], context['end_time']) + + time_series = dict() + time_series['total_bytes'] = list() + time_series['offloaded_bytes'] = list() + + for time_serie in context['time_series_times']: + time_series['total_bytes'].append(db.get_total_traffic(time_serie, time_serie)) + time_series['offloaded_bytes'].append(db.offloaded_bytes(context['num_prefixes'], time_serie, time_serie)) + + context['time_series'] = time_series + + return render_template('analytics/simulate.html', **context) + + +def find_prefix(request): + fs = getattr(g, 'fs', None) + context = dict() + context['available_dates'] = [d.strftime('%Y-%m-%dT%H:%M:01') for d in fs.get_available_dates()] + + context['query_name'] = 'Prefix' + if request.method == 'GET': + context['query'] = '' + context['prefixes'] = dict() + context['date'] = context['available_dates'][-1] + elif request.method == 'POST': + context['date'] = request.form.get('date') + context['query'] = request.form.get('query') + context['prefixes'] = fs.find_prefix(context['query'], context['date']) + return render_template('analytics/find_prefix.html', **context) + + +def find_prefix_asn(request): + fs = getattr(g, 'fs', None) + context = dict() + context['available_dates'] = [d.strftime('%Y-%m-%dT%H:%M:01') for d in fs.get_available_dates()] + + context['query_name'] = 'ASN' + if request.method == 'GET': + context['query'] = '' + context['prefixes'] = dict() + context['origin_only'] = True + context['date'] = context['available_dates'][-1] + elif request.method == 'POST': + context['date'] = request.form.get('date') + context['query'] = request.form.get('query') + context['origin_only'] = eval(request.form.get('origin_only', True)) + context['prefixes'] = fs.find_prefixes_asn(context['query'], context['date'], context['origin_only']) + return render_template('analytics/find_prefix.html', **context) diff --git a/bgp_controller/backend/__init__.py b/api/__init__.py similarity index 100% rename from bgp_controller/backend/__init__.py rename to api/__init__.py diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..77bf15a --- /dev/null +++ b/api/views.py @@ -0,0 +1,12 @@ +from flask import render_template +from flask import g + +import yaml + + +def start_page(request): + context = dict() + with open('api/api_documentation.yaml', 'r') as stream: + context['documentation'] = yaml.load(stream) + + return render_template('api/start_page.html', **context) diff --git a/bgp_controller/backend/base.py b/bgp_controller/backend/base.py deleted file mode 100644 index 4dbfb37..0000000 --- a/bgp_controller/backend/base.py +++ /dev/null @@ -1,107 +0,0 @@ -class Backend: - """ - This class defines all the methods and their input that a backend has to provide in order to be able to used by the - BGPController. - """ - - def __init__(self, conf): - self.conf = conf['backend_options'] - - def open(self): - """ - Opens the backend - - :return: None - """ - raise NotImplementedError - - def close(self): - """ - Closes the backend - - :return: None - """ - - raise NotImplementedError - - def get_best_prefixes(self, start_time, end_time, max_routes, packet_sampling): - """ - This method will return the best prefixes within the specified range - - :param start_time: (datetime) Starting time for the time range - :param end_time: (datetime) Ending time for the time range - :param max_routes: (int) Maximum routes you want - :param packet_sampling: (int) Packet sampling of the data stored - :return: A PrefixTable containing the best prefixes within the specified range - """ - raise NotImplementedError - - def get_raw_prefixes(self, start_time, end_time, packet_sampling): - """ - This method will return all the prefixes within the specified range - - :param start_time: (datetime) Starting time for the time range - :param end_time: (datetime) Ending time for the time range - :param packet_sampling: (int) Packet sampling of the data stored - :return: A PrefixTable containing all the prefixes within the specified range - """ - raise NotImplementedError - - def get_previous_prefixes(self, start_time, end_time): - """ - This method will return the latest best prefixes within the specified range - - :param start_time: (datetime) Starting time for the time range - :param end_time: (datetime) Ending time for the time range - :return: A PrefixTable containing all the latest best prefixes within the specified range - """ - raise NotImplementedError - - def save_prefix_table(self, prefix_table, date): - """ - This method will save a PrefixTable into the backend. In order to be unique a date must be specified. - - :param prefix_table: A PrefixTable. - :param date: (datetime) Unique date to mark the prefixes. - :return: None - """ - raise NotImplementedError - - def save_dict(self, data_dict, db_table): - """ - This will save a dictionary into a table. The columns of the dict will be the fields and the values, the values. - - :param data_dict: Dictionary to save. - :param db_table: The table name - :return: None - """ - raise NotImplementedError - - def get_data_from_table(self, table, filter=None): - """ - Will a list of lists where the first element is the schema definition (column names) and the rest of the - elements are a list of values in the same order as column names. - :param table: (str) Table to query. - :param filter: (str) Optional filter to send to the query - :return: A list of lists containing the column names as the first element and the values as the rest. - """ - raise NotImplementedError - - def get_available_dates_in_range(self, start_time, end_time): - """ - Given a time frame returns a list of valid dates with data. - - :param start_time: (datetime) Starting time for the time range - :param end_time: (datetime) Ending time for the time range - :return: A list of dates - """ - raise NotImplementedError - - def purge_data(self, current_time): - """ - This method will delete all of the old data in the database. - - :param current_time: (datetime) Current time - :return: None - """ - raise NotImplementedError diff --git a/bgp_controller/backend/sqlite.py b/bgp_controller/backend/sqlite.py deleted file mode 100644 index ea943a3..0000000 --- a/bgp_controller/backend/sqlite.py +++ /dev/null @@ -1,159 +0,0 @@ -from base import Backend -import sqlite3 as lite -from bgp_controller.prefix_table import Prefix, PrefixTable -from datetime import timedelta - -import os - -import logging -logger = logging.getLogger('sir') - -# TODO Save BGP in SQL? - -class SQLite(Backend): - """ - Name: - SQLite - Author: - David Barroso - Description: - This backend connects to a SQLite database. It will choose the best prefixes by the amount of data reported by pmacct. - Configuration: - * **sqlite_file** - Full path to the database file. - * **retention** - How many days to keep pmacct raw data and best_prefixes data. - Example: - Configuration example:: - - backend_options: - sqlite_file: '/Users/dbarroso/Documents/workspace/pmacct_data/output/flows/pmacct.db' # Path to the SQLite database - retention: 7 # Days to hold old data. - """ - - def open(self): - logger.info('action=OPEN_BACKEND backend=SQLITE file=%s' % self.conf['sqlite_file']) - if not os.path.isfile(self.conf['sqlite_file']): - raise Exception("Database file doesn't exist: %s" % self.conf['sqlite_file']) - - self.con = lite.connect(self.conf['sqlite_file']) - - def close(self): - logger.info('action=CLOSE_BACKEND backend=SQLITE file=%s' % self.conf['sqlite_file']) - self.con.close() - - def _execute_query(self, query): - try: - self.con.row_factory = lite.Row - cur = self.con.cursor() - cur.execute(query) - result = cur.fetchall() - except lite.OperationalError: - raise Exception('The following query failed:\n%s' % query) - - return result - - def _get_pt(self, list, sampling=1): - pt = PrefixTable() - - for p in list: - prefix = Prefix(p[0], p[1], p[2], p[3], p[4], sampling) - pt.add(prefix) - - return pt - - def get_best_prefixes(self, start_time, end_time, max_routes, packet_sampling): - logger.debug('action=GET_BEST_PREFIXES start_time=%s end_time=%s' % (start_time, end_time)) - query = (""" - SELECT ip_dst, mask_dst, AVG(bytes), AVG(packets), stamp_updated - FROM acct - WHERE datetime(stamp_updated) BETWEEN datetime('%s') AND datetime('%s') - GROUP BY ip_dst, mask_dst ORDER BY AVG(bytes) DESC - LIMIT %s; - """) % (start_time, end_time, max_routes) - return self._get_pt(self._execute_query(query), packet_sampling) - - def get_raw_prefixes(self, start_time, end_time, packet_sampling): - logger.debug('action=GET_RAW_PREFIXES start_time=%s end_time=%s' % (start_time, end_time)) - query = (""" - SELECT ip_dst, mask_dst, bytes, packets, stamp_updated - FROM acct - WHERE - datetime(stamp_updated) BETWEEN datetime('%s') AND datetime('%s') - AND - stamp_updated = ( - SELECT MAX(stamp_updated) FROM acct - WHERE datetime(stamp_updated) BETWEEN datetime('%s') AND datetime('%s') - ); - """) % (start_time, end_time, start_time, end_time) - return self._get_pt(self._execute_query(query), packet_sampling) - - def get_previous_prefixes(self, start_time, end_time): - logger.debug('action=GET_PREVIOUS_PREFIXES start_time=%s end_time=%s' % (start_time, end_time)) - query = (""" - SELECT ip_dst, mask_dst, bytes, packets, stamp_updated - FROM best_prefixes - WHERE - datetime(stamp_updated) BETWEEN datetime('%s') AND datetime('%s') - AND - stamp_updated = (SELECT MAX(stamp_updated) FROM best_prefixes); - """) % (start_time, end_time) - - return self._get_pt(self._execute_query(query)) - - def save_prefix_table(self, prefix_table, date): - logger.debug('action=SAVE_PREFIX_TABLE date=%s' % (date)) - cur = self.con.cursor() - - for prefix in prefix_table: - values = ( - str(prefix.get_prefix_network()), - prefix.get_prefix_mask(), - prefix.get_packets(), - prefix.get_bytes(), - date.strftime('%Y-%m-%d %H:%M:%S') - ) - cur.execute("INSERT INTO best_prefixes VALUES %s;" % (str(values))) - - self.con.commit() - - def save_dict(self, data_dict, db_table): - logger.debug('action=SAVE_DICT db_table=%s' % (db_table)) - - columns = tuple(data_dict.keys()) - values = tuple(data_dict.values()) - - cur = self.con.cursor() - cur.execute("INSERT INTO %s %s VALUES %s;" % (db_table, str(columns), str(values))) - self.con.commit() - - def get_data_from_table(self, table, filter=None): - logging.debug('action=GET_DATA_FROM_TABLE table=%s filter=%s' % (table, filter)) - if filter is None: - query = ("SELECT * FROM %s;") % table - else: - query = ("SELECT * FROM %s WHERE %s;") % (table, filter) - - result = self._execute_query(query) - result.insert(0, result[0].keys()) - return result - - def get_available_dates_in_range(self, start_time, end_time): - logger.debug('action=GET_AVAILABLE_DATES_IN_RANGE start_time=%s end_time=%s' % (start_time, end_time)) - query = (""" - SELECT stamp_updated FROM acct WHERE datetime(stamp_updated) - BETWEEN datetime('%s') AND datetime('%s') - GROUP BY stamp_updated; - """) % (start_time, end_time) - return self._execute_query(query) - - def _purge_databases(self, table, field, timestamp): - query = ( """ DELETE FROM %s WHERE %s < datetime('%s') """ ) % (table, field, timestamp) - self._execute_query(query) - self.con.commit() - - def purge_data(self, current_time): - purge_time = current_time - timedelta(hours = self.conf['retention'] * 24) - - logger.debug('action=PURGE_DATA date=%s' % (purge_time)) - - self._purge_databases('acct', 'stamp_updated', purge_time) - self._purge_databases('best_prefixes', 'stamp_updated', purge_time) diff --git a/bgp_controller/bgpc.py b/bgp_controller/bgpc.py deleted file mode 100644 index 674c106..0000000 --- a/bgp_controller/bgpc.py +++ /dev/null @@ -1,78 +0,0 @@ -from pydoc import locate -from datetime import datetime, timedelta - -import logging -logger = logging.getLogger('sir') - -# TODO Send BGP Table - -class BGPController: - def __init__(self, conf): - self.conf = conf - backend_class = locate('bgp_controller.backend.%s' % self.conf['backend']) - self.backend = backend_class(self.conf) - - def run(self): - logger.info('action=RUN') - start = datetime.now() - timedelta(hours = self.conf['history']) - end = datetime.now() - - self.backend.open() - self.process_prefixes(start, end) - self.execute_plugins(time=end, simulation=False, last_run=True) - self.backend.purge_data(end) - self.backend.close() - - def simulate(self): - logger.info('action=SIMULATE') - start = datetime.strptime(self.conf['simulate']['start_date'], self.conf['simulate']['date_format']) - end = datetime.strptime(self.conf['simulate']['end_date'], self.conf['simulate']['date_format']) - - self.backend.open() - - dates = self.backend.get_available_dates_in_range(start, end) - - iteration = 0 - total_iterations = len(dates) - - for date in dates: - iteration += 1 - - end_date = datetime.strptime(date[0], self.conf['simulate']['date_format']) - start_date = end_date - timedelta(hours = self.conf['history']) - self.process_prefixes(start_date, end_date) - - last_run = iteration == total_iterations-1 - self.execute_plugins(time=end_date, simulation=True, last_run=last_run) - - self.backend.close() - - def process_prefixes(self, start, end): - logger.info('action=PROCESS_PREFIX start_date=%s end_date=%s' % (start, end)) - - # The best prefix for the previous run - self.prev_pt = self.backend.get_previous_prefixes(start, end) - - # The new best prefixes - self.new_pt = self.backend.get_best_prefixes(start, end, self.conf['max_routes'], self.conf['packet_sampling']) - self.backend.save_prefix_table(self.new_pt, end) - - # Raw data from the last hour - self.raw_pt = self.backend.get_raw_prefixes(start, end, self.conf['packet_sampling']) - - def execute_plugins(self, time, simulation, last_run): - for plugin in self.conf['plugins']: - logger.info('action=EXECUTE_PLUGIN plugin=%s' % plugin) - plugin_class = locate('bgp_controller.plugins.%s' % plugin) - plugin = plugin_class( - conf = self.conf, - backend = self.backend, - raw_pt = self.raw_pt, - new_pt = self.new_pt, - prev_pt = self.prev_pt, - bgp_table = None, - time = time, - simulation = simulation, - last_run = last_run - ) - plugin._execute() diff --git a/bgp_controller/plugins/base.py b/bgp_controller/plugins/base.py deleted file mode 100644 index 26d37c8..0000000 --- a/bgp_controller/plugins/base.py +++ /dev/null @@ -1,32 +0,0 @@ - -class PrefixPlugin: - skip_simulation = False - run_once_during_simulations = False - - def __init__(self, conf, backend, raw_pt, new_pt, prev_pt, bgp_table, time, simulation, last_run): - self.conf = conf - self.backend = backend - self.raw_pt = raw_pt - self.new_pt = new_pt - self.prev_pt = prev_pt - self.bgp_table = bgp_table - self.time = time - self.simulation = simulation - self.last_run = last_run - - def _execute(self): - if not self.simulation or (self.simulation and not self.skip_simulation): - runnable = True - else: - runnable = False - - if not self.run_once_during_simulations or ( self.run_once_during_simulations and self.last_run): - pass # We stick with the previous decision - else: - runnable = False - - if runnable: - self.run() - - def run(self): - raise NotImplementedError diff --git a/bgp_controller/plugins/bird.py b/bgp_controller/plugins/bird.py deleted file mode 100644 index 83c6791..0000000 --- a/bgp_controller/plugins/bird.py +++ /dev/null @@ -1,46 +0,0 @@ -from base import PrefixPlugin - -import os - - -class Bird(PrefixPlugin): - """ - Name: - Bird - Author: - David Barroso - Description: - Updates the prefix list that controls which prefix lists to install on the FIB and reloads bird. - Requires: - - new_pt - Configuration: - - policy_file: path to the file where to save the prefix list - - reload_bird: Whether to reload bird or not. - Example: - Configuration example:: - - Bird: - policy_file: '/Users/dbarroso/Documents/workspace/pmacct_data/allow_prefixes.bird' - reload_bird: True - """ - skip_simulation = False - run_once_during_simulations = True - - def run(self): - policy_file = self.conf['Bird']['policy_file'] - - of = open(policy_file, 'w') - # header - of.write('function allow_prefixes() {\n return net ~ [\n') - - # allowed prefixes - for p in self.new_pt.get_prefixes()[0:-1]: - of.write(' %s,\n' % p.get_prefix()) - - # last prefix does not have a comma - of.write(' %s\n' % self.new_pt.get_prefixes()[-1].get_prefix()) - of.write(' ];\n}\n') - of.close() - - if self.conf['Bird']['reload_bird']: - os.system("birdc configure") diff --git a/bgp_controller/plugins/statistics.py b/bgp_controller/plugins/statistics.py deleted file mode 100644 index f3d9724..0000000 --- a/bgp_controller/plugins/statistics.py +++ /dev/null @@ -1,173 +0,0 @@ -from base import PrefixPlugin - -import pandas as pd -import matplotlib - -matplotlib.use('Agg') - -class RouteStatistics(PrefixPlugin): - """ - Name: - RouteStatistics - Author: - David Barroso - Description: - Keeps historical data of which prefixes are added, kept, removed, etc. on every run. The data is - saved in the backend with the following format:: - - Time,Total,Kept,Added,Removed,Expired - - In addition it will generate a graph for better visualization. - Requires: - - prev_pt - - new_pt - - time - Configuration: - - db_table: Where to store/retrieve the data in the backend - - png_file: Where to save the graph - - plot_days: Days to plot - Example: - Configuration example:: - - RouteStatistics: - db_table: 'route_statistics' - png_file: '/Users/dbarroso/Documents/workspace/pmacct_data/route_statistics.png' - plot_days: 2 - - You can adapt the backend database to use this plugin by using the sql script provided in the folder - *'/sql/plugins/route_statistics.sql'* - """ - - skip_simulation = False - run_once_during_simulations = False - - def process_data(self): - data = dict() - data['time'] = self.time.strftime('%Y-%m-%d %H:%M:%S') - data['total'] = len(self.new_pt) - data['kept'] = len(self.new_pt.common_prefixes(self.prev_pt)) - data['removed'] = len(self.prev_pt.missing_prefixes(self.new_pt)) - self.new_pt.expired_prefixes - data['added'] = len(self.new_pt.missing_prefixes(self.prev_pt)) - - self.backend.save_dict(data, self.conf['RouteStatistics']['db_table']) - - def plot(self): - pd.set_option('display.mpl_style', 'default') - table = self.backend.get_data_from_table(self.conf['RouteStatistics']['db_table']) - - raw_data = list() - - for row in table[1:]: - raw_data.append( - { - table[0][0]: row[0], - table[0][1]: row[1], - table[0][2]: row[2], - table[0][3]: row[3], - table[0][4]: row[4], - } - ) - time_frame = self.conf['RouteStatistics']['plot_days']*24 - data = pd.DataFrame(raw_data)[-time_frame:] - plot = data.plot( - x='time', - figsize = (9,9), - grid=True, - title='Route Statistics, max_routes: %s, history: %s' % - (self.conf['max_routes'], self.conf['history']), - legend=True, - ) - fig = plot.get_figure() - fig.savefig(self.conf['RouteStatistics']['png_file']) - - def run(self): - self.process_data() - - if self.last_run: - self.plot() - - -class OffloadedBytes(PrefixPlugin): - """ - Name: - OffloadedBytes - Author: - David Barroso - Description: - Keeps historical data of how much data is send and how much is offloaded. We consider that data is - offloaded when a prefix in raw_pt was present in prev_pt. The data saved on the backend has the - following format:: - - Time,Total,Offloaded,% - - In addition it will generate a graph for better visualization. - Requires: - - raw_pt - - prev_pt - - time - Configuration: - - db_table: Where to store/retrieve the data in the backend - - png_file: Where to save the graph - - plot_days: Days to plot - Example: - Configuration example:: - - OffloadedBytes: - db_table: 'offloaded_bytes' - png_file: '/Users/dbarroso/Documents/workspace/pmacct_data/offloaded_bytes.png' - plot_days: 2 - - You can adapt the backend database to use this plugin by using the sql script provided in the folder - *'/sql/plugins/offloaded_bytes.sql'* - - """ - - skip_simulation = False - run_once_during_simulations = False - - def plot(self): - pd.set_option('display.mpl_style', 'default') - - table = self.backend.get_data_from_table(self.conf['OffloadedBytes']['db_table']) - - raw_data = list() - - for row in table[1:]: - raw_data.append( - { - table[0][0]: row[0], - table[0][1]: row[1], - table[0][2]: float(row[2]), - table[0][3]: row[3], - } - ) - time_frame = self.conf['OffloadedBytes']['plot_days']*24 - data = pd.DataFrame(raw_data)[-time_frame:] - - plot = data.plot( - x='time', - secondary_y=['percentage'], - figsize = (9,9), - grid=True, - title='Data Offloaded, max_routes: %s, history: %s' % - (self.conf['max_routes'], self.conf['history']), - legend=True, - ) - fig = plot.get_figure() - fig.savefig(self.conf['OffloadedBytes']['png_file']) - - def process(self): - data = dict() - data['time'] = self.time.strftime('%Y-%m-%d %H:%M:%S') - data['total_bytes'] = self.raw_pt.get_total_bytes() - data['offloaded'] = sum(p.get_bytes() for p in self.raw_pt if self.prev_pt.prefix_present(p)) - - data['percentage'] = float(data['offloaded'])*100/float(data['total_bytes']) - - self.backend.save_dict(data, self.conf['OffloadedBytes']['db_table']) - - def run(self): - self.process() - - if self.last_run: - self.plot() diff --git a/bgp_controller/prefix_table.py b/bgp_controller/prefix_table.py deleted file mode 100644 index 74d03d2..0000000 --- a/bgp_controller/prefix_table.py +++ /dev/null @@ -1,176 +0,0 @@ -class Prefix: - def __init__(self, prefix, mask, packets, bytes, date, sampling=1): - self.prefix = prefix - self.mask = mask - self.packets = int(packets*sampling) - self.bytes = int(bytes*sampling) - self.date = date - - def __eq__(self, other): - return self.prefix == other.prefix and self.mask == other.mask - - def __str__(self): - return '%s/%s, packets: %s, bytes: %s, date: %s' %\ - (self.prefix, self.mask, self.packets, self.bytes, self.date) - - def __hash__(self): - return hash(self.prefix) ^ hash(self.mask) - - def get_prefix(self): - return '%s/%s' % (self.prefix, self.mask) - - def get_prefix_network(self): - return self.prefix - - def get_prefix_mask(self): - return self.mask - - def get_bytes(self): - return self.bytes - - def get_packets(self): - return self.packets - - -class PrefixTable: - def __init__(self, packet_sampling=1): - self.packet_sampling = packet_sampling - self.prefix_set = set() - self.prefixes = dict() - self.expired_prefixes = 0 - - def __len__(self): - return len(self.prefix_set) - - def __iter__(self): - for p in self.prefixes.items(): - yield(p[1]) - - def get(self, prefix): - return self.prefixes[prefix] - - def remove(self, prefix): - self.prefix_set.remove(prefix.get_prefix()) - del(self.prefixes[prefix.get_prefix()]) - - def add(self, prefix): - self.prefix_set.add(prefix.get_prefix()) - self.prefixes[prefix.get_prefix()] = prefix - - def common_prefixes(self, pt): - """ - Args: - * pt: A PrefixTable object - Returns: - A set containing all prefixes present both in the current PrefixTable and in pt - """ - return self.prefix_set & pt.prefix_set - - def missing_prefixes(self, pt): - """ - Args: - * pt: A PrefixTable object - Returns: - A set containing all prefixes present in the current object but not in pt - """ - return self.prefix_set - pt.prefix_set - - def get_total_bytes(self): - return sum(p.get_bytes() for p in self.prefixes.values()) - - def prefix_present(self, prefix): - return prefix.get_prefix() in self.prefix_set - - def get_prefixes(self): - return self.prefixes.values() - - ''' - def replace_prefix_table(self, dictionary): - self.prefixes = dict(dictionary) - self.prefix_set = set(self.prefixes.keys()) - - def iteritems(self): - for key, value in self.prefixes.iteritems(): - yield key, value - - def copy_prefixes(self, pt): - self.prefixes = dict(pt.prefixes) - self.prefix_set = set(pt.prefix_set) - - def set_max_routes(self, max_routes): - self.max_routes = max_routes - - def get_max_routes(self): - return self.max_routes - - def set_max_age(self, max_age): - self.max_age = max_age - - def get_max_age(self): - return self.max_age - - def set_min_bytes(self, min_bytes): - self.min_bytes = min_bytes - - def get_min_bytes(self): - return self.min_bytes - - def set_packet_sampling(self, packet_sampling): - self.packet_sampling = packet_sampling - - def get_packet_sampling(self): - return self.packet_sampling - - def get_total_bytes(self): - return sum(p.get_bytes() for p in self.prefixes.values()) - - def load_from_csv(self, file_name, csv_delimiter, read_ext_data=False): - with open(file_name, "rb") as f: - reader = csv.DictReader(f, delimiter=csv_delimiter) - - for p in reader: - packets = int(p['PACKETS']) * self.packet_sampling - bytes = int(p['BYTES']) * self.packet_sampling - - prefix = Prefix(p['DST_IP'], p['DST_MASK'], packets, bytes) - - if read_ext_data: - prefix.set_age(int(p['AGE'])) - prefix.set_avg_bytes(int(p['AVG_BYTES'])) - prefix.set_avg_packets(int(p['AVG_PACKETS'])) - - if prefix.get_bytes() > self.min_bytes: - # FIXME This is due to pmacct duplicates prefixes. Inefficient as hell so we should fix it - if self.prefix_present(prefix): - self.get(prefix.get_prefix()).average(prefix) - else: - self.add(prefix) - - def join_prefix_tables(self, pt): - continuing_prefixes = self.common_prefixes(pt) - old_prefixes = pt.missing_prefixes(self) - - # We "average" bytes and packets and reset the age on continuing routes - for p in continuing_prefixes: - self.get(p).reset_age() - self.get(p).average(pt.get(p)) - - # We increment age on the old routes - for p in old_prefixes: - self.add(pt.get(p)) - self.get(p).increment_age() - - def filter_routes(self): - if self.max_routes == 0 or len(self) < self.max_routes: - pass - else: - num_prefixes = len(self) - if self.get_max_age() > 0: - prefixes = set(v for k, v in self.iteritems() if v.get_age() <= self.get_max_age()) - else: - prefixes = set(v for k, v in self.iteritems()) - - self.expired_prefixes = num_prefixes - len(prefixes) - prefixes = sorted(prefixes, key=lambda x: x.bytes, reverse=True)[:self.get_max_routes()] - self.replace_prefix_table({x.get_prefix(): x for x in prefixes}) - ''' \ No newline at end of file diff --git a/docs/api/api_v1.0.rst b/docs/api/api_v1.0.rst new file mode 100644 index 0000000..d47359b --- /dev/null +++ b/docs/api/api_v1.0.rst @@ -0,0 +1,615 @@ +********************** +API v1.0 Documentation +********************** + +### +API +### + +Variables +********* + +When reading this documentation you will find variables in two forms: + +* ****: Variables that are between ``<>`` have to be replaced by their values in the URL. For example, ``/api/v1.0/variables/categories/`` will turn into ``/api/v1.0/variables/categories/my_category``. +* **variable**: Variables that are NOT enclosed by ``<>``: + * If the method is a GET variables that are between ``<>`` have to be replaced by their values in the URL. For example, ``/api/v1.0/variables/categories/`` will turn into ``/api/v1.0/variables/categories/my_category``. + * If the method is a POST or a PUT variables variables that are between ``<>`` have to be sent as a JSON object. + +Responses +********* + +All the responses from the agent will be in JSON format and will include three sections: + +* **meta**: Meta information about the response. For example, *request_time*, *length* of the response or if there was any *error*. +* **parameters**: The parameters used for the call. +* **result**: The result of the call or a description of the error if there was any. + +For example, for the following call:: + + /api/v1.0/analytics/top_prefixes?limit_prefixes=10&start_time=2015-07-13T14:00&end_time=2015-07-14T14:00&net_masks=20,24 + +You will get the following response: + +.. code-block:: json + :linenos: + + { + "meta": { + "error": false, + "length": 10, + "request_time": 11.99163 + }, + "parameters": { + "end_time": "2015-07-14T14:00", + "exclude_net_masks": false, + "limit_prefixes": 10, + "net_masks": "20,24", + "start_time": "2015-07-13T14:00" + }, + "result": [ + { + "as_dst": 43650, + "key": "194.14.177.0/24", + "sum_bytes": 650537594 + }, + ... + { + "as_dst": 197301, + "key": "80.71.128.0/20", + "sum_bytes": 5106731 + } + ] + } + +######### +Endpoints +######### + +Analytics Endpoint +****************** + +/api/v1.0/analytics/top_prefixes +================================ + +GET +--- + +Description +___________ + +Retrieves TOP prefixes sorted by the amount of bytes that they consumed during the specified period of time. + +Arguments +_________ + +* **start_time**: Mandatory. Datetime in unicode string following the format ``%Y-%m-%dT%H:%M:%S``. Starting time of the range. +* **end_time**: Mandatory. Datetime in unicode string following the format ``%Y-%m-%dT%H:%M:%S``. Ending time of the range. +* **limit_prefixes**: Optional. Number of top prefixes to retrieve. +* **net_masks**: Optional. List of prefix lengths to filter in or out. +* **exclude_net_masks**: Optional. If set to any value it will return prefixes with a prefix length not included in net_masks. If set to 0 it will return only prefixes with a prefix length included in net_masks. Default is 0. + +Returns +_______ + +A list of prefixes sorted by sum_bytes. The attribute sum_bytes is the amount of bytes consumed during the specified time. + +Examples +-------- + +:: + + http://127.0.0.1:5000/api/v1.0/analytics/top_prefixes?limit_prefixes=10&start_time=2015-07-13T14:00&end_time=2015-07-14T14:00 + http://127.0.0.1:5000/api/v1.0/analytics/top_prefixes?limit_prefixes=10&start_time=2015-07-13T14:00&end_time=2015-07-14T14:00&net_masks=20,24 + http://127.0.0.1:5000/api/v1.0/analytics/top_prefixes?limit_prefixes=10&start_time=2015-07-13T14:00&end_time=2015-07-14T14:00&net_masks=20,24&exclude_net_masks=1 + + +/api/v1.0/analytics/top_asns +============================ + +GET +--- + +Description +___________ + +Retrieves TOP ASN's sorted by the amount of bytes that they consumed during the specified period of time. + +Arguments +_________ + +* **start_time**: Mandatory. Datetime in unicode string following the format ``%Y-%m-%dT%H:%M:%S``. Starting time of the range. +* **end_time**: Mandatory. Datetime in unicode string following the format ``%Y-%m-%dT%H:%M:%S``. Ending time of the range. + +Returns +_______ + +A list of ASN's sorted by sum_bytes. The attribute sum_bytes is the amount of bytes consumed during the specified time. + +Examples +-------- + +:: + + http://127.0.0.1:5000/api/v1.0/analytics/top_asns?start_time=2015-07-13T14:00&end_time=2015-07-14T14:00 + +/api/v1.0/analytics/find_prefix// +======================================================== + +GET +--- + +Description +___________ + +Finds all prefixes in the system that contains the prefix ``/`` + +Arguments +_________ + +* **<>**: Mandatory. IP prefix of the network you want to find. +* **<>**: Mandatory. Prefix length of the network you want to find. +* **date**: Mandatory. Datetime in unicode string following the format ``'%Y-%m-%dT%H:%M:%S'``. + +Returns +_______ + +It will return a dictionary where keys are the IP's of the BGP peers peering with SIR. Each one will have a list of prefixes that contain the prefix queried. + +Examples +-------- + +:: + + $ curl http://127.0.0.1:5000/api/v1.0/analytics/find_prefix/192.2.3.1/32\?date\=2015-07-22T05:00:01 + { + "meta": { + "error": false, + "length": 2, + "request_time": 1.88076 + }, + "parameters": { + "date": "2015-07-22T05:00:01", + "prefix": "192.2.3.1/32" + }, + "result": { + "193.182.244.0": [ + { + "as_path": "1299 3356", + "bgp_nexthop": "62.115.48.29", + "comms": "1299:20000 8403:100 8403:2001", + "event_type": "dump", + "ip_prefix": "192.2.0.0/16", + "local_pref": 100, + "origin": 0, + "peer_ip_src": "193.182.244.0" + } + ], + "193.182.244.64": [ + { + "as_path": "1299 3356", + "bgp_nexthop": "80.239.132.249", + "comms": "1299:20000 8403:100 8403:2001", + "event_type": "dump", + "ip_prefix": "192.2.0.0/16", + "local_pref": 100, + "origin": 0, + "peer_ip_src": "193.182.244.64" + } + ] + } + } + +/api/v1.0/analytics/find_prefixes_asn/ +=========================================== + +GET +--- + +Description +___________ + +Finds all prefixes in the system that traverses and/or originates in ```` + +Arguments +_________ + +* **<>**: Mandatory. ASN you want to query. +* **date**: Mandatory. Datetime in unicode string following the format ``'%Y-%m-%dT%H:%M:%S'``. +* **origin_only**: Optional. If set to any value it will return only prefixes that originate in ````. + +Returns +_______ + +It will return a dictionary where keys are the IP's of the BGP peers peering with SIR. Each one will have a list of prefixes that traverses and/or originates in ```` + +Examples +-------- + +:: + + curl http://127.0.0.1:5000/api/v1.0/analytics/find_prefixes_asn/345\?date\=2015-07-22T05:00:01\&origin_only=1 + { + "meta": { + "error": false, + "length": 2, + "request_time": 1.15757 + }, + "parameters": { + "asn": "345", + "origin_only": "1", + "date": "2015-07-22T05:00:01" + }, + "result": { + "193.182.244.0": [ + { + "as_path": "1299 209 721 27064 575 306 345", + "bgp_nexthop": "62.115.48.29", + "comms": "1299:25000 8403:100 8403:2001", + "event_type": "dump", + "ip_prefix": "55.3.0.0/16", + "local_pref": 100, + "origin": 0, + "peer_ip_src": "193.182.244.0" + }, + { + "as_path": "1299 209 721 27065 6025 345", + "bgp_nexthop": "62.115.48.29", + "comms": "1299:20000 8403:100 8403:2001", + "event_type": "dump", + "ip_prefix": "156.112.250.0/24", + "local_pref": 100, + "origin": 0, + "peer_ip_src": "193.182.244.0" + } + ], + "193.182.244.64": [ + { + "as_path": "1299 209 721 27064 575 306 345", + "bgp_nexthop": "80.239.132.249", + "comms": "1299:25000 8403:100 8403:2001", + "event_type": "dump", + "ip_prefix": "55.3.0.0/16", + "local_pref": 100, + "origin": 0, + "peer_ip_src": "193.182.244.64" + }, + { + "as_path": "1299 209 721 27065 6025 345", + "bgp_nexthop": "80.239.132.249", + "comms": "1299:20000 8403:100 8403:2001", + "event_type": "dump", + "ip_prefix": "156.112.250.0/24", + "local_pref": 100, + "origin": 0, + "peer_ip_src": "193.182.244.64" + } + ] + } + } + +Variables Endpoint +****************** + +/api/v1.0/variables +=================== + +GET +--- + +Description +___________ + +Retrieves all the variables in the system. + +Arguments +_________ + +Returns +_______ + +A list of all the variables. + +Examples +________ + +:: + + http://127.0.0.1:5000/api/v1.0/variables + +POST +---- + +Description +___________ + +You can create a variable from the CLI with curl like this: + +:: + + curl -i -H "Content-Type: application/json" -X POST -d '{"name": "test_var", "content": "whatever", "category": "development", "extra_vars": {"ads": "qwe", "asd": "zxc"}}' http://127.0.0.1:5000/api/v1.0/variables + +Arguments +_________ + +* **content**: Content of the variable. +* **category**: Category of the variable. +* **name**: Name of the variable. +* **extra_vars**: Use this field to add extra data to your variable. It is recommended to use a JSON string. + +Returns +_______ + +The variable that was just created. + +Examples +________ + +/api/v1.0/variables/categories +============================== + +GET +--- + +Description +___________ + +Retrieves all the categories in the system. + +Arguments +_________ + +Returns +_______ + +A list of all the categories. + +Examples +________ + +:: + + http://127.0.0.1:5000/api/v1.0/variables/categories + +/api/v1.0/variables/categories/ +========================================= + +GET +--- + +Description +___________ + +Retrieves all the variables the belong to in the system. + +Arguments +_________ + +* ****: Category you want to query. + +Returns +_______ + +A list of variables belonging to . + +Examples +________ + +:: + + http://127.0.0.1:5000/api/v1.0/variables/categories/ + +/api/v1.0/variables/categories// +================================================ + +GET +--- + +Description +___________ + +Retrieves the variable with and . + +Arguments +_________ + +* ****: Category of the variable you want to retrieve. +* ****: Name of the variable you want to retrieve. + +Returns +_______ + +A list of variables belonging to . + +Examples +________ + +:: + + http://127.0.0.1:5000/api/v1.0/variables/categories// + +PUT +--- + +Description +___________ + +This API call allows you to modify all of some of the values of a variable. For example, you can update the name and the extra_vars of a variable with the following command: + +.. code-block:: json + :linenos: + + curl -i -H "Content-Type: application/json" -X PUT -d '{"name": "test_varc", "extra_vars": "{'my_param1': 'my_value1', 'my_param2': 'my_value2'}"}' http://127.0.0.1:5000/api/v1.0/variables/categories/development/test_vara HTTP/1.0 200 OK Content-Type: application/json Content-Length: 358 Server: Werkzeug/0.10.4 Python/2.7.8 Date: Tue, 21 Jul 2015 10:16:22 GMT + { + "meta": { + "error": false, + "length": 1, + "request_time": 0.0055 + }, + "parameters": { + "categories": "development", + "name": "test_vara" + }, + "result": [ + { + "category": "development", + "content": "whatever", + "extra_vars": "{my_param1: my_value1, my_param2: my_value2}", + "name": "test_varc" + } + ] + } + +Arguments +_________ + +* **category**: Optional. New category. +* **content**: Optional. New content. +* **name**: Optional. New name. +* ****: Name of the variable you want to modify. +* ****: Category of the variable you want to modify. +* **extra_vars**: Optional. New extra_vars. + +Returns +_______ + +The variable with the new data. + +Examples +________ + +:: + + http://127.0.0.1:5000/api/v1.0/variables/categories// + +DELETE +------ + +Description +___________ + +Deletes a variable. For example: + +.. code-block:: html + :linenos: + + curl -i -X DELETE http://127.0.0.1:5000/api/v1.0/variables/categories/deveopment/test_vara HTTP/1.0 200 OK Content-Type: application/json Content-Length: 183 Server: Werkzeug/0.10.4 Python/2.7.8 Date: Tue, 21 Jul 2015 10:17:27 GMT + { + "meta": { + "error": false, + "length": 0, + "request_time": 0.0016 + }, + "parameters": { + "categories": "deveopment", + "name": "test_vara" + }, + "result": [] + } + +Arguments +_________ + +* ****: Category of the variable you want to delete. +* ****: Name of the variable you want to delete. + +Returns +_______ + +An empty list. + +Examples +________ + +:: + + http://127.0.0.1:5000/api/v1.0/variables/categories// + +Pmacct Endpoint +*************** + +/api/v1.0/pmacct/dates +====================== + +GET +--- + +Description +___________ + +Retrieves all the available dates in the system. + +Arguments +_________ + +Returns +_______ + +A list of all the available dates in the system. + +Examples +________ + +:: + + http://127.0.0.1:5000/api/v1.0/pmacct/dates + +/api/v1.0/pmacct/flows +====================== + +GET +--- + +Description +___________ + +Retrieves all the available dates in the system. + +Arguments +_________ + +* **start_time**: Mandatory. Datetime in unicode string following the format ``'%Y-%m-%dT%H:%M:%S'``. Starting time of the range. +* **end_time**: Mandatory. Datetime in unicode string following the format ``'%Y-%m-%dT%H:%M:%S'``. Ending time of the range. + +Returns +_______ + +A list of all the available dates in the system. + +Examples +________ + +:: + + http://127.0.0.1:5000/api/v1.0/pmacct/flows?limit_prefixes=10&start_time=2015-07-14T14:00&end_time=2015-07-14T14:01 + http://127.0.0.1:5000/api/v1.0/pmacct/flows?limit_prefixes=10&start_time=2015-07-13T14:00&end_time=2015-07-14T14:00 + +/api/v1.0/pmacct/bgp_prefixes +============================= + +GET +--- + +Description +___________ + +Retrieves all the BGP prefixes in the system. + +.. warning:: Do it only if need it. If you have the full feed this can return hundreds of MB of data. + +Arguments +_________ + +* **date**: Mandatory. Datetime in unicode string following the format ``'%Y-%m-%dT%H:%M:%S'``. + +Returns +_______ + +A list of all the available BGP prefixes in the system. + +Examples +________ + +:: + + http://127.0.0.1:5000/api/v1.0/pmacct/bgp_prefixes?date=2015-07-16T11:00:01 diff --git a/docs/api/bgpc.rst b/docs/api/bgpc.rst deleted file mode 100644 index 3d69648..0000000 --- a/docs/api/bgpc.rst +++ /dev/null @@ -1,7 +0,0 @@ -BGPController -************* - -.. autoclass:: bgp_controller.bgpc.BGPController - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/index.rst b/docs/api/index.rst deleted file mode 100644 index 355553f..0000000 --- a/docs/api/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -API Documentation -***************** - -.. toctree:: - :maxdepth: 2 - - prefix - prefixtable - bgpc diff --git a/docs/api/prefix.rst b/docs/api/prefix.rst deleted file mode 100644 index 5f2f1d1..0000000 --- a/docs/api/prefix.rst +++ /dev/null @@ -1,7 +0,0 @@ -Prefix -****** - -.. autoclass:: bgp_controller.prefix_table.Prefix - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/prefixtable.rst b/docs/api/prefixtable.rst deleted file mode 100644 index 0448354..0000000 --- a/docs/api/prefixtable.rst +++ /dev/null @@ -1,7 +0,0 @@ -PrefixTable -*********** - -.. autoclass:: bgp_controller.prefix_table.PrefixTable - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/architecture/global_architecture.dot b/docs/architecture/global_architecture.dot new file mode 100644 index 0000000..60d0148 --- /dev/null +++ b/docs/architecture/global_architecture.dot @@ -0,0 +1,16 @@ +digraph SIR { + graph [truecolor=True, bgcolor="#ffffff5f"] + node [shape="ellipse", style="rounded,filled", fontname="Source Code Pro", fontsize=12] + edge [fontname="Source Code Pro", fontsize=10, style=dashed] + + aggregator[fillcolor="#5F9EA0", height=0.75, width=0.75]; + + sir_lon -> aggregator; + sir_fra -> aggregator; + sir_ash -> aggregator; + sir_sjc -> aggregator; + sir_jpn -> aggregator; + sir_syd -> aggregator[label="Lots of Data!"]; + + +} diff --git a/docs/architecture/global_architecture.png b/docs/architecture/global_architecture.png new file mode 100644 index 0000000..45c45f6 Binary files /dev/null and b/docs/architecture/global_architecture.png differ diff --git a/docs/architecture/global_architecture.rst b/docs/architecture/global_architecture.rst new file mode 100644 index 0000000..e94594e --- /dev/null +++ b/docs/architecture/global_architecture.rst @@ -0,0 +1,19 @@ +=================== +Global Architecture +=================== + +Although each agent is meant to run independently the API allows you to combine/aggregate data from multiple locations +and take global decisions. + +.. image:: global_architecture.png + :align: center + :alt: global_architecture + + +Once you have a global view of your global network and your traffic patterns you can do things like: + +* Send users to a location where you know you are peering with the network that user belongs to. +* Redirect users to another POP where his/her network is reachable. +* Redirect users based on cost, latency, bandwidth or any other metric. +* Predict your global traffic patterns and optimize your capacity. +* Build reports to see who you should be peering with. diff --git a/docs/architecture/index.rst b/docs/architecture/index.rst new file mode 100644 index 0000000..bf92ca4 --- /dev/null +++ b/docs/architecture/index.rst @@ -0,0 +1,9 @@ +************ +Architecture +************ + +.. toctree:: + :maxdepth: 2 + + local_architecture + global_architecture diff --git a/docs/architecture/local_architecture.dot b/docs/architecture/local_architecture.dot new file mode 100644 index 0000000..cf0335c --- /dev/null +++ b/docs/architecture/local_architecture.dot @@ -0,0 +1,29 @@ +digraph SIR { + graph [truecolor=True, bgcolor="#ffffff5f"] + node [shape="ellipse", style="rounded,filled", fontname="Source Code Pro", fontsize=12] + edge [fontname="Source Code Pro", fontsize=10, style=dashed] + + subgraph network { + node [shape="box"] + router00[label="router00", fillcolor="#5F9EA0", height=0.75, width=0.75]; + } + + subgraph cluster_1 { + node [shape="component"] + pmacct; + SIR; + } + + apps[fillcolor="#FFB6C1"]; + user[fillcolor="#FFD8E4"]; + monitoring_tool[fillcolor="#33D8E9"]; + + + router00 -> pmacct[label="netflow &\nBGP"] + pmacct -> SIR[label="Flows aggr.\nper prefix\n& BGP"] + + SIR -> apps [label="gathers data\nvia the API"]; + SIR -> user [label="gathers data\nvia the\nWebUI/API"]; + + monitoring_tool -> SIR[label="Store metrics\nfor apps\nconsumption"]; +} diff --git a/docs/architecture/local_architecture.png b/docs/architecture/local_architecture.png new file mode 100644 index 0000000..28e9490 Binary files /dev/null and b/docs/architecture/local_architecture.png differ diff --git a/docs/architecture/local_architecture.rst b/docs/architecture/local_architecture.rst new file mode 100644 index 0000000..d6a33f7 --- /dev/null +++ b/docs/architecture/local_architecture.rst @@ -0,0 +1,18 @@ +================== +Local Architecture +================== + +The local architecture of the SIR agent is quite simple: + +.. image:: local_architecture.png + :align: center + :alt: local_architecture + +There are two main components: + +* `pmacct `_ - This is a flow collector that is going to get BGP and flow information from your routers. +* **SIR** - This code is going to do some analytics with the data collected by pmacct and expose it to apps/users via the WebUI and the API. + +Users and applications can consume the data exposed by SIR and take peering or traffic engineering decisions. +Applications and users can also store data in the agent. This is useful if you want to influence the behavior of +some other applications later on. diff --git a/docs/architecture/what_is_it.rst b/docs/architecture/what_is_it.rst new file mode 100644 index 0000000..c6533f3 --- /dev/null +++ b/docs/architecture/what_is_it.rst @@ -0,0 +1,13 @@ +=========== +What is it? +=========== + +The SDN Internet Router, abbreviated SIR, is an agent that you can add to your router. The agent exposes information +that your router can't expose by itself; providing both a WebUI and an API to access this data. + +The agent is vendor agnostic as it gathers data using both BGP and netflow/sflow/ipfix. This means it can be attached +to any router or switch that supports those protocols. + +======== +Features +======== diff --git a/docs/backends/index.rst b/docs/backends/index.rst deleted file mode 100644 index b894fd7..0000000 --- a/docs/backends/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Backends Documentation -********************** - -.. toctree:: - :maxdepth: 2 - - sqlite diff --git a/docs/backends/sqlite.rst b/docs/backends/sqlite.rst deleted file mode 100644 index 669714f..0000000 --- a/docs/backends/sqlite.rst +++ /dev/null @@ -1,8 +0,0 @@ -SQLite -****** - -sqlite.SQLite -============= - -.. autoclass:: bgp_controller.backend.sqlite.SQLite - diff --git a/docs/developers_guide/developing_backends.rst b/docs/developers_guide/developing_backends.rst deleted file mode 100644 index 229c4ab..0000000 --- a/docs/developers_guide/developing_backends.rst +++ /dev/null @@ -1,51 +0,0 @@ -******************* -Developing Backends -******************* - -If you want to develop your own Backend plugin the easiest way to do it is: - - #. Create a new file and a new class inheriting from the class :py:class:`~bgp_controller.backend.base.Backend` - #. Override all the methods inherited from the parent class with the exception of __init__ - #. Implement your own code respecting the input and the output of its methods. - -Configuration Options -===================== - -If you need to add configuration variables for your backend like connection string, username, password, -or others, you can specify them in the configuration file inside the 'backend_options' dictionary. For example:: - - backend_options: - sqlite_file: '/workspace/pmacct_data/output/flows/pmacct.db' # Path to the SQLite database - retention: 7 # Days to hold old data. - -You will be able to access those variables as 'self.conf[variable_name]'. For example:: - - >>> print self.conf['retention'] - 7 - -Documenting the backend -======================= - -If you are writing a backend, please, follow this convention when documenting it:: - - Name: - Name of the plugin - Author: - Author's Name - Description: - Description of the backend - Configuration: - A list containing which configuration parameters are required and why. - Example: - A brief example on how to use it and configure it. - -Backend -======= - -Below you can find the base :py:class:`~bgp_controller.backend.base.Backend` you have to inherit from. You can see all -the methods you have to implement, their input and their output. - -.. autoclass:: bgp_controller.backend.base.Backend - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/developers_guide/developing_plugins.rst b/docs/developers_guide/developing_plugins.rst deleted file mode 100644 index 44160b2..0000000 --- a/docs/developers_guide/developing_plugins.rst +++ /dev/null @@ -1,168 +0,0 @@ -****************** -Developing plugins -****************** - -Developing plugins is very easy. What you have to do is to write a class that inherits from *plugins.base.PrefixPlugin*, configure a few class constants and overwrite the method **run**:: - - from base import PrefixPlugin - - class FooPlugin(PrefixPlugin): - skip_simulation = False - run_once_during_simulations = True - - def run(self): - print "foo" - -Class Constants -=============== - -When you create your own plugin you have to set two constants that will define when your plugin will be triggered: - - - **skip_simulation**: If set to *True* the plugin will not be executed in *simulations*. - - **run_once_during_simulations**: If set to *True*, when running in *simulations*, the plugin will be called on the last iteration of the simulation. If set to *False*, it will be called on every iteration. - -Variables -========= - -When you plugin is called it will contain the following variables: - - - **conf**: A dictionary containing all the parameters set on the configuration file. - - **backend** An instance of the backend. Useful to store and retrieve data from the backend. - - **data_file**: Filename of the pmacct file we are processing - - **raw_pt**: A PrefixTable containing all prefixes that have been processed - - **new_pt**: A PrefixTable containing only the prefixes that have passed our criteria - - **prev_pt**: A PrefixTable containing the prefixes that we had until now. - - **bgp_table**: A dictionary containing the BGP feed that pmacct is getting. - - **time**: Time of this run - - **simulation**: True if we are running a simulation. - - **last_run**: True if it is the last iteration of the simulation or if it's not a simulation. - -Configuration Options -===================== - -If you need to add configuration variables for your backend like connection string, username, password, -or others, you can specify them in the configuration file inside a dictionary. For example:: - - RouteStatistics: - db_table: 'route_statistics' - png_file: '/Users/dbarroso/Documents/workspace/pmacct_data/route_statistics.png' - plot_days: 2 - -You will be able to access those variables as 'self.conf[variable_name]'. For example:: - - >>> print self.conf['RouteStatistics']['plot_days'] - 2 - -Backend Methods -=============== - -There are two useful methods that backends have to provide that are useful for writing plugins. These are: - - * :py:meth:`bgp_controller.backend.base.Backend.save_dict` - * :py:meth:`bgp_controller.backend.base.Backend.get_data_from_table` - -Check the :py:class:`~bgp_controller.backend.base.Backend` documentation for more info on how to use those methods. - -Documenting the Plugin -====================== - -For convenience we will document every plugin using the class docstring. We will follow the following format for standarization:: - - Name: - Name of the plugin - Author: - Author's Name - Description: - Description of what the module does - Requires: - A list containing which variables are required. - Configuration: - A list containing which configuration parameters are required and why. - Example: - A brief example on how to use it and configure it. - - -Example -======= - -To wrap up, check the following plugin as an example:: - - from base import PrefixPlugin - - import pandas as pd - import matplotlib - - matplotlib.use('Agg') - - class RouteStatistics(PrefixPlugin): - """ - Name: - RouteStatistics - Author: - David Barroso - Description: - Keeps historical data of which prefixes are added, kept, removed, etc. on every run. The data is - saved on a CSV file with the following format:: - - Time,Total,Kept,Added,Removed,Expired - - In addition it will generate a graph for better visualization. - Requires: - - prev_pt - - new_pt - - time - Configuration: - - db_table: Where to store/retrieve the data in the backend - - png_file: Where to save the graph - - plot_days: Days to plot - """ - - skip_simulation = False - run_once_during_simulations = False - - def process_data(self): - data = dict() - data['time'] = self.time.strftime('%Y-%m-%d %H:%M:%S') - data['total'] = len(self.new_pt) - data['kept'] = len(self.new_pt.common_prefixes(self.prev_pt)) - data['removed'] = len(self.prev_pt.missing_prefixes(self.new_pt)) - self.new_pt.expired_prefixes - data['added'] = len(self.new_pt.missing_prefixes(self.prev_pt)) - - self.backend.save_dict(data, self.conf['RouteStatistics']['db_table']) - - def plot(self): - pd.set_option('display.mpl_style', 'default') - table = self.backend.get_data_from_table(self.conf['RouteStatistics']['db_table']) - - raw_data = list() - - for row in table[1:]: - raw_data.append( - { - table[0][0]: row[0], - table[0][1]: row[1], - table[0][2]: row[2], - table[0][3]: row[3], - table[0][4]: row[4], - } - ) - time_frame = self.conf['RouteStatistics']['plot_days']*24 - data = pd.DataFrame(raw_data)[-time_frame:] - plot = data.plot( - x='time', - figsize = (9,9), - grid=True, - title='Route Statistics, max_routes: %s, history: %s' % - (self.conf['max_routes'], self.conf['history']), - legend=True, - ) - fig = plot.get_figure() - fig.savefig(self.conf['RouteStatistics']['png_file']) - - def run(self): - self.process_data() - - if self.last_run: - self.plot() - - diff --git a/docs/developers_guide/index.rst b/docs/developers_guide/index.rst deleted file mode 100644 index 21ea1f7..0000000 --- a/docs/developers_guide/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -Developers Guide -**************** - -.. toctree:: - :maxdepth: 2 - - developing_plugins - developing_backends diff --git a/docs/features/analytics.rst b/docs/features/analytics.rst new file mode 100644 index 0000000..16d1e30 --- /dev/null +++ b/docs/features/analytics.rst @@ -0,0 +1,68 @@ +######### +Analytics +######### + +Top Prefixes +------------ + +You can get the top N prefixes for a given period of time. + +.. image:: top_prefixes.png + :align: center + :alt: top_prefixes + +Top ASN's +--------- + +You can get the top ASN's for a given period of time. + +.. image:: top_asn.png + :align: center + :alt: top_asn + +Search Prefixes +--------------- + +You can search for any prefix of any prefix-length and get back all the possible routes that you could choose to reach +it. You will not get only the longest prefix match but all the possible matches. + +.. image:: search_prefix.png + :align: center + :alt: search_prefix + +.. note:: ``peer_ip_src`` is useful if you are sending data to the agent from different routers as it shows the IP of + the router that provided those prefixes + +Search ASN's +------------ + +You can search for any ASN and see all the prefixes that either traverse and/or originated in that ASN. Set the option +``origin_as_only`` to indicate if you want to see only prefixes originated in that ASN or if you want prefixes transiting +that ASN as well. + +.. image:: search_asn.png + :align: center + :alt: search_asn + +.. note:: ``peer_ip_src`` is useful if you are sending data to the agent from different routers as it shows the IP of + the router that provided those prefixes + +Offloaded Traffic +----------------- + +For a given period of time and a number of prefixes N, returns the amount of traffic matched if you had only the top N +prefixes installed in the FIB vs the Total Traffic. + +.. image:: offloaded.png + :align: center + :alt: offloaded + +Simulate FIB Optimization +------------------------- + +For a given period of time and a number of prefixes N, simulates how the switch/router would perform if you had only +the top N prefixes installed in the FIB vs the Total Traffic. + +.. image:: simulate.png + :align: center + :alt: simulate diff --git a/docs/features/index.rst b/docs/features/index.rst new file mode 100644 index 0000000..deb2518 --- /dev/null +++ b/docs/features/index.rst @@ -0,0 +1,14 @@ +******** +Features +******** + +The agent enhances your router adding some nice features that are exposed both via the WebUI and the API. You can see +some examples of the features in the following pages. Although the examples are shown using the WebUI all the features +are also available via the API. Actually, features are built first for the API so there is the possibility that +you can do something with the API but not with the WebUI. + +.. toctree:: + :maxdepth: 2 + + analytics + variables diff --git a/docs/features/offloaded.png b/docs/features/offloaded.png new file mode 100644 index 0000000..b4d8a76 Binary files /dev/null and b/docs/features/offloaded.png differ diff --git a/docs/features/search_asn.png b/docs/features/search_asn.png new file mode 100644 index 0000000..b082b65 Binary files /dev/null and b/docs/features/search_asn.png differ diff --git a/docs/features/search_prefix.png b/docs/features/search_prefix.png new file mode 100644 index 0000000..6f30223 Binary files /dev/null and b/docs/features/search_prefix.png differ diff --git a/docs/features/simulate.png b/docs/features/simulate.png new file mode 100644 index 0000000..fa2a615 Binary files /dev/null and b/docs/features/simulate.png differ diff --git a/docs/features/top_asn.png b/docs/features/top_asn.png new file mode 100644 index 0000000..37d2305 Binary files /dev/null and b/docs/features/top_asn.png differ diff --git a/docs/features/top_prefixes.png b/docs/features/top_prefixes.png new file mode 100644 index 0000000..ee07556 Binary files /dev/null and b/docs/features/top_prefixes.png differ diff --git a/docs/features/variables.png b/docs/features/variables.png new file mode 100644 index 0000000..8c6e24e Binary files /dev/null and b/docs/features/variables.png differ diff --git a/docs/features/variables.rst b/docs/features/variables.rst new file mode 100644 index 0000000..7c3f1e2 --- /dev/null +++ b/docs/features/variables.rst @@ -0,0 +1,17 @@ +######### +Variables +######### + +You can store in the agent and retrieve later any arbitrary data that you want. This is useful if you plan to write +applications that consumes data via the API to perform some actions. For example, in the example below you can see +a variable called ``fib_optimizer``. The data in the content is the actual configuration for that application. The +application will retrieve the Top N prefixes via the API and install them in a commodity switch. Thanks to these +feature we can tweak the behavior of that application from a centralized management tool while allowing the device +to run independently. + +Use this in any way you want. This will not influence how the agent performs. It's just a convenient way for you +to store/retrieve some data for some applications that you might be running using the SIR API. + +.. image:: variables.png + :align: center + :alt: variables diff --git a/docs/how_to_asr/how_to_asr_scenario.dot b/docs/how_to_asr/how_to_asr_scenario.dot new file mode 100644 index 0000000..aee104b --- /dev/null +++ b/docs/how_to_asr/how_to_asr_scenario.dot @@ -0,0 +1,30 @@ +digraph SIR { + graph [truecolor=True, bgcolor="#ffffff5f"] + node [shape="box", style="rounded,filled", fontname="Source Code Pro", fontsize=12] + edge [fontname="Source Code Pro", fontsize=10, style=dashed] + + subgraph network { + IXP[label="Exchange Point", fillcolor="#F5F5DC"]; + Transit[fillcolor="#FFDEAD"]; + ASR[label="ASR", fillcolor="#5F9EA0", height=0.75, width=0.75]; + + Transit -> ASR[label="0.0.0.0/0", dir=none, style=solid]; + IXP -> ASR[label="lots of\nprefixes", dir=none, style=solid]; + } + + subgraph cluster_1 { + node [shape="component"] + pmacct; + sir; + } + ASR -> pmacct[label="netflow &\nBGP"] + pmacct -> sir[label="Flows aggr.\nper prefix\n& BGP"] + + subgraph external { + node [shape="ellipse"]; + peering_manager[fillcolor="#FFB6C1"]; + + peering_manager -> sir[label="gather data\nvia the API"]; + } + +} diff --git a/docs/how_to_asr/how_to_asr_scenario.png b/docs/how_to_asr/how_to_asr_scenario.png new file mode 100644 index 0000000..7d7fcf0 Binary files /dev/null and b/docs/how_to_asr/how_to_asr_scenario.png differ diff --git a/docs/how_to_asr/index.rst b/docs/how_to_asr/index.rst new file mode 100644 index 0000000..c021c7e --- /dev/null +++ b/docs/how_to_asr/index.rst @@ -0,0 +1,10 @@ +************* +How To: ASR's +************* + +.. toctree:: + :maxdepth: 2 + + scenario + pmacct + sir diff --git a/docs/how_to_asr/pmacct.rst b/docs/how_to_asr/pmacct.rst new file mode 100644 index 0000000..5e2fd38 --- /dev/null +++ b/docs/how_to_asr/pmacct.rst @@ -0,0 +1,267 @@ +=============== +Enabling pmacct +=============== + +First, we have to install `pmacct `_ with JSON, IPv6 and SQLite3 support. Compiling pmacct for your specific distro is out of the scope of this document but you can find below some instructions on how to do it for a debian based distro. + +Compiling pmacct +---------------- + +These are some of the dependancies that you might need:: + + apt-get install libpcap-dev libsqlite3-dev libjansson-dev zlib1g-dev + +And this is how to compile pmacct:: + + $ wget http://www.pmacct.net/pmacct-1.5.1.tar.gz + --2015-07-23 09:25:46-- http://www.pmacct.net/pmacct-1.5.1.tar.gz + Connecting to 127.0.0.1:3128... connected. + Proxy request sent, awaiting response... 200 OK + Length: 874563 (854K) [application/x-gzip] + Saving to: ‘pmacct-1.5.1.tar.gz’ + + 100%[==============================================================================>] 874,563 --.-K/s in 0.09s + + 2015-07-23 09:25:46 (9.80 MB/s) - ‘pmacct-1.5.1.tar.gz’ saved [874563/874563] + + $ tar xzf pmacct-1.5.1.tar.gz + $ cd pmacct-1.5.1 + $ ./configure --enable-sqlite3 --enable-jansson --enable-ipv6 --prefix=/pmacct-1.5.1 + creating cache ./config.cache + checking for a BSD compatible install... /usr/bin/install -c + checking whether build environment is sane... yes + checking whether make sets ${MAKE}... yes + checking for working aclocal-1.4... missing + checking for working autoconf... missing + checking for working automake-1.4... missing + checking for working autoheader... missing + checking for working makeinfo... missing + checking for gcc... gcc + checking whether the C compiler (gcc ) works... yes + checking whether the C compiler (gcc ) is a cross-compiler... no + checking whether we are using GNU C... yes + checking whether gcc accepts -g... yes + checking OS... Linux + checking hardware... x86_64 + checking for ranlib... ranlib + checking whether to enable debugging compiler options... no + checking whether to relax compiler optimizations... no + checking whether to disable linking against shared objects... no + checking for dlopen... no + checking for dlopen in -ldl... yes + checking for gmake... no + checking for make... make + checking whether make sets ${MAKE}... (cached) yes + checking for __progname... yes + checking for extra flags needed to export symbols... --export-dynamic + checking for static inline... yes + checking endianess... little + checking unaligned accesses... ok + checking whether to enable L2 features... yes + checking whether to enable IPv6 code... yes + checking for inet_pton... yes + checking for inet_ntop... yes + checking whether to enable IPv4-mapped IPv6 sockets ... yes + checking whether to enable IP prefix labels... checking default locations for pcap.h... found in /usr/include + checking default locations for libpcap... no + checking for pcap_dispatch in -lpcap... yes + checking for pcap_setnonblock in -lpcap... yes + checking packet capture type... linux + checking whether to enable MySQL support... checking how to run the C preprocessor... gcc -E + no + checking whether to enable PostgreSQL support... no + checking whether to enable MongoDB support... no + checking whether to enable SQLite3 support... yes + checking default locations for libsqlite3... not found + checking for sqlite3_open in -lsqlite3... yes + checking default locations for sqlite3.h... found in /usr/include + checking whether to enable RabbitMQ/AMQP support... no + checking whether to enable GeoIP support... no + checking whether to enable Jansson support... yes + checking default locations for Jansson library... not found + checking for json_object in -ljansson... yes + checking default locations for jansson.h... found in /usr/include + checking for ANSI C header files... no + checking for sys/wait.h that is POSIX.1 compatible... yes + checking for getopt.h... yes + checking for sys/select.h... yes + checking for sys/time.h... yes + checking for u_int64_t in sys/types.h... yes + checking for u_int32_t in sys/types.h... yes + checking for u_int16_t in sys/types.h... yes + checking for u_int8_t in sys/types.h... yes + checking for uint64_t in sys/types.h... no + checking for uint32_t in sys/types.h... no + checking for uint16_t in sys/types.h... no + checking for uint8_t in sys/types.h... no + checking whether to enable 64bit counters... yes + checking whether to enable multithreading in pmacct... yes + checking whether to enable ULOG support... no + checking return type of signal handlers... void + checking for strlcpy... no + checking for vsnprintf... no + checking for setproctitle... no + checking for mallopt... no + + PLATFORM ..... : x86_64 + OS ........... : Linux 3.13.0-34-generic + COMPILER ..... : gcc + CFLAGS ....... : -O2 -g -O2 + LIBS ......... : -ljansson -lsqlite3 -lpcap -ldl -lm -lz -lpthread + SERVER_LIBS ...: -lnfprobe_plugin -Lnfprobe_plugin/ -lsfprobe_plugin -Lsfprobe_plugin/ -lbgp -Lbgp/ -ltee_plugin -Ltee_plugin/ -lisis -Lisis/ -lbmp -Lbmp/ + LDFLAGS ...... : -Wl,--export-dynamic + + Now type 'make' to compile the source code. + + Are you willing to get in touch with other pmacct users? + Join the pmacct mailing-list by sending a message to pmacct-discussion-subscribe@pmacct.net + + Need for documentation and examples? + Read the README file or go to http://wiki.pmacct.net/ + + + updating cache ./config.cache + creating ./config.status + creating Makefile + creating src/Makefile + creating src/nfprobe_plugin/Makefile + creating src/sfprobe_plugin/Makefile + creating src/bgp/Makefile + creating src/tee_plugin/Makefile + creating src/isis/Makefile + creating src/bmp/Makefile + $ make + ... (output omitted for clarity) + $ sudo make install + ... (output omitted for clarity) + +Configuring pmacct +------------------ + +To configure pmacct you will need to know the IP the ASR will use as source IP for both netflow and BGP. Once you know, paste the following configuration in the file ``/pmacct-1.5.1/etc/pmacct.conf``:: + + $ cd /pmacct-1.5.1 + $ sudo mkdir etc + $ sudo vi etc/pmacct.conf + daemonize: true + + plugins: sqlite3[simple] + + sql_db[simple]: /pmacct-1.5.1/output/pmacct.db + sql_refresh_time[simple]: 3600 + sql_history[simple]: 60m + sql_history_roundoff[simple]: h + sql_table[simple]: acct + sql_table_version[simple]: 9 + + aggregate: dst_net, dst_mask, dst_as + + bgp_daemon: true + bgp_daemon_ip: $ASR_SRC_IP + bgp_daemon_max_peers: 1 + bgp_table_dump_file: /pmacct-1.5.1/output/bgp-$peer_src_ip-%Y_%m_%dT%H_%M_%S.txt + bgp_table_dump_refresh_time: 3600 + + nfacctd_as_new: bgp + nfacctd_net: bgp + nfacctd_port: 9999 + nfacctd_ip: $ASR_SRC_IP + nfacctd_time_new: true + +.. warning:: Don't forget to replace ``$ASR_SRC_IP`` with the IP your ASR will use for both netflow and BGP. + +Now it's time to setup the database:: + + $ sudo mkdir output + $ sudo sqlite3 output/pmacct.db << EOF + CREATE TABLE 'acct' ( + 'tag' INT(8) NOT NULL DEFAULT 0, + 'class_id' CHAR(16) NOT NULL DEFAULT ' ', + 'mac_src' CHAR(17) NOT NULL DEFAULT '0:0:0:0:0:0', + 'mac_dst' CHAR(17) NOT NULL DEFAULT '0:0:0:0:0:0', + 'vlan' INT(4) NOT NULL DEFAULT 0, + 'as_src' INT(8) NOT NULL DEFAULT 0, + 'as_dst' INT(8) NOT NULL DEFAULT 0, + 'ip_src' CHAR(15) NOT NULL DEFAULT '0.0.0.0', + 'ip_dst' CHAR(15) NOT NULL DEFAULT '0.0.0.0', + 'mask_dst' INTEGER(1) NOT NULL DEFAULT 0, + 'port_src' INT(4) NOT NULL DEFAULT 0, + 'port_dst' INT(4) NOT NULL DEFAULT 0, + 'tcp_flags' INT(4) NOT NULL DEFAULT 0, + 'ip_proto' CHAR(6) NOT NULL DEFAULT 0, + 'tos' INT(4) NOT NULL DEFAULT 0, + 'packets' INT NOT NULL, + 'bytes' BIGINT NOT NULL, + 'flows' INT NOT NULL DEFAULT 0, + 'stamp_inserted' DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00', + 'stamp_updated' DATETIME, + 'collumn' peer_as_srcINT(8) NOT NULL DEFAULT 0, + 'peer_as_dst' INT(8) NOT NULL DEFAULT 0, + 'peer_as_src' INT(8) NOT NULL DEFAULT 0, + 'peer_dst_ip' TEXT NOT NULL DEFAULT '0.0.0.0', + PRIMARY KEY(tag,class_id,mac_src,mac_dst,vlan,as_src,as_dst,ip_src,ip_dst,mask_dst,port_src,port_dst,ip_proto,tos,stamp_inserted) + ); + CREATE TABLE 'variables' ( + 'name' TEXT, + 'content' TEXT, + 'category' TEXT, + PRIMARY KEY(name,category) + ); + + CREATE INDEX acct_idx1 ON acct(stamp_updated); + CREATE INDEX acct_idx2 ON acct(stamp_updated, as_dst); + CREATE INDEX acct_idx3 ON acct(stamp_updated, ip_dst, mask_dst); + + CREATE INDEX variables_idx1 ON variables(category); + EOF + +Finally, it's just a matter of starting pmacct:: + + $ sudo /pmacct-1.5.1/sbin/nfacctd -f /pmacct-1.5.1/etc/pmacct.conf + +Configuring the ASR +------------------- + +Configuring the ASR is relatively easy, you only have to configure netflow to send the flows that you want to process and BGP to send the prefixes you want to use for the aggregation. Here is an example:: + + flow exporter-map SIR + version v9 + options interface-table timeout 60 + template data timeout 60 + ! + transport udp 9999 + source Loopback0 + destination $PMACCT_IP + ! + flow monitor-map SIR-FMM + record ipv4 + exporter SIR + cache timeout active 60 + cache timeout inactive 15 + ! + sampler-map SIR + random 1 out-of 10000 + + interface HundredGigE0/0/0/1 + flow ipv4 monitor SIR-FMM sampler SIR egress + + route-policy PASS + pass + end-policy + + route-policy BLOCK + drop + end-policy + + router bgp $AS + neighbor $PMACCT_IP + remote-as $AS + description SIR + update-source Loopback0 + address-family ipv4 unicast + route-policy BLOCK in + route-policy PASS out + +.. warning:: Don't forget to replace ``$PMACCT_IP`` with the IP of the server where you are running pmacct and ``$AS`` with your own AS. + +.. note:: If you want you can configure several ASR's pointing to the same SIR agent. The only thing you have to change is in pmacct's configuration the parameter ``bgp_daemon_max_peers`` to the number of ASR's that you are going to configure. diff --git a/docs/how_to_asr/scenario.rst b/docs/how_to_asr/scenario.rst new file mode 100644 index 0000000..a2c4f41 --- /dev/null +++ b/docs/how_to_asr/scenario.rst @@ -0,0 +1,12 @@ +======== +Scenario +======== + +We want to deploy the SIR agent connecting with an ASR to add some visibility to our edge router. This visibility will allows us to understand who we are sending traffic to and to who peer with. + +We can't run code in the ASR's so we are going to deploy the agent on a linux machine in the Datacenter. This shouldn't be an issue as both BGP and netflow can be sent to a device that is not directly connected. + +.. image:: how_to_asr_scenario.png + :alt: how_to_asr_scenario + +Per se this is not very interesting, however, if you have a large network with several POPs, having this agent on each location would give you a nice way of gathering and aggregating data. diff --git a/docs/how_to_asr/sir.rst b/docs/how_to_asr/sir.rst new file mode 100644 index 0000000..bab4519 --- /dev/null +++ b/docs/how_to_asr/sir.rst @@ -0,0 +1,40 @@ +================== +Enabling SIR agent +================== + +This is super easy to do. First clone SIR and install requirements:: + + $ git clone git@github.com:dbarrosop/sir.git + Cloning into 'sir'... + remote: Counting objects: 761, done. + remote: Compressing objects: 100% (154/154), done. + remote: Total 761 (delta 85), reused 0 (delta 0), pack-reused 599 + Receiving objects: 100% (761/761), 1.84 MiB | 1.25 MiB/s, done. + Resolving deltas: 100% (373/373), done. + Checking connectivity... done. + $ cd sir + $ pip install -U -r requirements.txt + /lib/python2.7/site-packages/pip/_vendor/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning. + InsecurePlatformWarning + Requirement already up-to-date: flask in /lib/python2.7/site-packages (from -r requirements.txt (line 1)) + Requirement already up-to-date: Werkzeug>=0.7 in /lib/python2.7/site-packages (from flask->-r requirements.txt (line 1)) + Requirement already up-to-date: Jinja2>=2.4 in /lib/python2.7/site-packages (from flask->-r requirements.txt (line 1)) + Requirement already up-to-date: itsdangerous>=0.21 in /lib/python2.7/site-packages (from flask->-r requirements.txt (line 1)) + Requirement already up-to-date: markupsafe in /lib/python2.7/site-packages (from Jinja2>=2.4->flask->-r requirements.txt (line 1)) + +Now configure SIR. Edit file ``settings.py``:: + + DATABASE = '/pmacct-1.5.1/output/pmacct.db' # Path to the db + BGP_FOLDER = '/spotify/pmacct-1.5.1/output' # Path to the folder where BGP info is + DEBUG = False # Set to True only if you are trying to develop and your environment is completely secure + SECRET_KEY = 'My_super_duper_secret_key' # Secret key. Keep it secret. + BIND_IP = '0.0.0.0' # IP you want to bind the service to + PORT= 8080 # Port you want to bind the service to + +Now you can start SIR and access it on the ``IP:PORT`` specified above.:: + + $ sudo python sir.py + +Finally, you should look into exposing SIR via some application server. you can read more about it in the following link: + + * ``_ diff --git a/docs/how_to_eos/index.rst b/docs/how_to_eos/index.rst new file mode 100644 index 0000000..eb5d4e5 --- /dev/null +++ b/docs/how_to_eos/index.rst @@ -0,0 +1,7 @@ +*********** +How To: EOS +*********** + +Soon... + +http://dbarrosop.github.io/download/sir_pmacct/pmacct_1.5.0-EOS_4.15.0.zip diff --git a/docs/how_to_simple/index.rst b/docs/how_to_simple/index.rst deleted file mode 100644 index 83988a0..0000000 --- a/docs/how_to_simple/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -******************** -How To: Simple Setup -******************** - -.. toctree:: - :maxdepth: 2 - - - scenario - installing - pmacct - sir - running - simulating \ No newline at end of file diff --git a/docs/how_to_simple/installing.rst b/docs/how_to_simple/installing.rst deleted file mode 100644 index 0826bf1..0000000 --- a/docs/how_to_simple/installing.rst +++ /dev/null @@ -1,19 +0,0 @@ -============================== -Configuring the BGP controller -============================== - -We are going to run the BGP controller inside the **Internet Router**. You can run it outside if you prefer or if you can´t install software on your switch. - -As we already explained, the BGP Controller consists of two different software pieces: - - * pmacct_ - Which will get the BGP feed and flow statistics from the Internet Router and aggregate statistics per BGP prefix. - * sir - Which will get the data generated by pmacct and compute the TopN prefixes and do various things with it. - -We are going to use the following folders in our system: - - * **/pmacct** - Here is where we are going to install pmacct - * **/sir** - Here is where we are going install sir - * **/bgp_controller** - Here is where we are going to store the backend files and the data generated by sir - - -.. _pmacct: http://www.pmacct.net/ \ No newline at end of file diff --git a/docs/how_to_simple/pmacct.rst b/docs/how_to_simple/pmacct.rst deleted file mode 100644 index 914e511..0000000 --- a/docs/how_to_simple/pmacct.rst +++ /dev/null @@ -1,141 +0,0 @@ -================== -Configuring pmacct -================== - -Before installing pmacct we have to configure the Internet Router to send flow statistics via sFlow/Netflow to pmacct. In addition, we will have to configure the Internet Router to peer with pmacct and send all the prefixes coming from our peers. - -^^^^^^^^^^^^^^^^^^^ -Netflow/sFlow Agent -^^^^^^^^^^^^^^^^^^^ - -Configuring this agent on the Internet Router is out of the scope of this document. You should be able to find enough documentation on the Internet on how to do this with your network device. - -^^^^^^^^^^^^^^^^^^^ -Peering with pmacct -^^^^^^^^^^^^^^^^^^^ - -We will have to update bird configuration on the Internet Router in order to peer with pmacct. Doing that is very simple. Just add the following configuration to the file **/etc/bird/bird.conf** (you will probably have to update the IP's and the AS's):: - - #Pmacct - protocol bgp { - local as 65010; - - # As we are running pmacct on the same host we specify a different TCP port - # In addition, pmacct works as an iBGP neighbor - neighbor 10.0.1.10 port 1179 as 65010; - - # This will enable ADD-PATHs. If we have two routes for the same prefix - # we will send all paths to pmacct instead of sending only the best path - add paths tx; - - export filter { - # We only want to send the prefixes coming from the peers - if from = 10.0.0.2 then accept; - reject; - }; - } - -^^^^^^^^^^^^^^^^^ -Installing pmacct -^^^^^^^^^^^^^^^^^ - -Configuring pmacct is also out of the scope of this document. However, I am attaching some basic instructions I used to setup pmacct on my lab. To compile and install pmacct do the following:: - - $ sudo apt-get install libpcap-dev make gcc libjansson-dev - $ wget http://www.pmacct.net/pmacct-1.5.0.tar.gz - $ tar xvzf pmacct-1.5.0.tar.gz - $ cd pmacct-1.5.0/ - $ sudo mkdir -p /pmacct - $ ./configure --prefix=/pmacct --enable-jansson - $ make - $ sudo make install - -Finally, pmacct configuration might slightly differ depending on the protocol you are using to report flow statistics. In my example I am running fProbe as a netflow agent so I will be using netflow. We will need two files: - -* **/pmacct/etc/nfacctd.conf**:: - - daemonize: True - - # We use SQLite3 as backend - plugins: sqlite3[simple] - - sql_db[simple]: /bgp_controller/bgpc.db - sql_refresh_time[simple]: 3600 - sql_history[simple]: 60m - sql_history_roundoff[simple]: h - sql_table[simple]: acct - - aggregate: dst_net, dst_mask - - bgp_daemon: true - bgp_daemon_ip: 10.0.1.10 - # As we are running pmacct on the same Internet Router we specify the port - # If you are running pmacct on a dedicate machine you can skip this - bgp_daemon_port: 1179 - bgp_daemon_max_peers: 2 - bgp_agent_map: /pmacct/etc/agent_to_peer.map - bgp_table_dump_file: /bgp_controller/bgp/bgp-$peer_src_ip.txt - bgp_table_dump_refresh_time: 3600 - - nfacctd_as_new: bgp - nfacctd_net: bgp - # Port and IP to bind pmacct - nfacctd_port: 9996 - nfacctd_ip: 127.0.0.1 - -* **/pmacct/etc/agent_to_peer.map**:: - - # id=$BGP_ROUTER_ID_INTERNET ROUTER ip=$SRC_IP_FOR_NETFLOW_MESSAGES - id=10.0.0.10 ip=127.0.0.1 - -Before starting pmacct we have to create the database:: - - $ sqlite3 /bgp_controller/bgpc.db - SQLite version 3.8.5 2014-08-15 22:37:57 - Enter ".help" for usage hints. - sqlite> CREATE TABLE `acct` ( - ...> `mac_src` CHAR(17) NOT NULL DEFAULT '0:0:0:0:0:0', - ...> `mac_dst` CHAR(17) NOT NULL DEFAULT '0:0:0:0:0:0', - ...> `ip_src` CHAR(15) NOT NULL DEFAULT '0.0.0.0', - ...> `ip_dst` CHAR(15) NOT NULL DEFAULT '0.0.0.0', - ...> `mask_dst` INTEGER(1) NOT NULL DEFAULT 0, - ...> `src_port` INT(4) NOT NULL DEFAULT 0, - ...> `dst_port` INT(4) NOT NULL DEFAULT 0, - ...> `ip_proto` CHAR(6) NOT NULL DEFAULT 0, - ...> `packets` INT NOT NULL, - ...> `bytes` BIGINT NOT NULL, - ...> `stamp_inserted` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00', - ...> `stamp_updated` DATETIME, - ...> PRIMARY KEY(mac_src,mac_dst,ip_src,ip_dst,mask_dst,src_port,dst_port,ip_proto,stamp_inserted) - ...> ); - sqlite> ^D - $ - -Now you can start pmacct and check that it works:: - - # Start pmacct - $ sudo /pmacct/sbin/nfacctd -f /pmacct/etc/nfacctd.conf - WARN ( default/core ): Daemonizing. Hmm, bye bye screen. - - # Check on the Internet Router that BGP is up - $ sudo birdc - BIRD 1.4.5 ready. - bird> show protocols bgp4 - name proto table state since info - bgp4 BGP master up 18:31:28 Established - -After an hour you should have data:: - - $ sqlite3 /bgp_controller/bgpc.db - SQLite version 3.8.2 2013-12-06 14:53:30 - Enter ".help" for instructions - Enter SQL statements terminated with a ";" - sqlite> SELECT count(*) from acct; - 21923 - sqlite> ^D - $ - - # BGP information - $ ls /bgp_controller/bgp/ - bgp-10_0_1_10.txt - diff --git a/docs/how_to_simple/running.rst b/docs/how_to_simple/running.rst deleted file mode 100644 index 7c83ad6..0000000 --- a/docs/how_to_simple/running.rst +++ /dev/null @@ -1,32 +0,0 @@ -=========== -Running sir -=========== - -Now it's time to run it and see if it works:: - - # We check the routing table first - $ sudo ip route | grep bird - 192.168.0.0/24 via 10.0.1.11 dev eth2 proto bird - - # We run the controller - $ python /sir/sir.py run -c /etc/sir-conf.yaml - BIRD 1.4.5 ready. - Reading configuration from /etc/bird/bird.conf - Reconfigured - -If we don't get any error we check the routing table again and the content of the directory *'/bgp_controller/'*:: - - $ ip route | grep bird - 130.239.0.0/16 via 10.0.0.2 dev eth1 proto bird - 185.31.17.0/24 via 10.0.0.2 dev eth1 proto bird - 192.30.252.0/24 via 10.0.0.2 dev eth1 proto bird - 192.168.0.0/24 via 10.0.1.11 dev eth2 proto bird - 195.20.224.0/19 via 10.0.0.2 dev eth1 proto bird - $ ls /spotify/sir_data/ - latest_data.csv offloaded_bytes.png route_statistics.png - bgp bgpc.db - -Now you have to make sure you run the controller every hour after pmacct has saved the data on disc. To do that you can add a cron task, create the file **/etc/cron.d/sir** with the content:: - - 5 * * * * root /usr/bin/env python /sir/sir.py run -c /etc/sir-conf.yaml - diff --git a/docs/how_to_simple/scenario.rst b/docs/how_to_simple/scenario.rst deleted file mode 100644 index 3fa6e6e..0000000 --- a/docs/how_to_simple/scenario.rst +++ /dev/null @@ -1,148 +0,0 @@ -======== -Scenario -======== - -You can deploy the SDN Internet Router in many ways. Below you can find the simplest deployment where the SDN Internet Router might be useful. We will also use this example to show how to deploy the BGP Controller. - -.. image:: ../img/how_to_scenario.png - :alt: Network Diagram - -Our Internet Router is connected to: - - - **Transit provider** - Our transit provider will send the default route only. - - **Peer** - Peers will send a bunch of prefixes belonging to them. For the sake of simplicity we have only one peer and it´s on the same network as the Transit Provider. - - **DC** - From the DC we will get the network where our servers are located. - -The Internet Router will be configured with the following default configuration: - - - Readvertise the networks coming from the **DC** to our **Transit Provider** and our **peers**. - - Readvertise the default route to our **DC**. - - Accept the default route coming from the **Transit provider** - - Reject everything incoming from our **peers**. - -This means that the Internet Router by default will: - - - Get traffic inbound from the **Transit Provider** and our **peers** directed towards our **DC**. - - Send traffic outbound towards our **Transit Provider** only. - -Using a router running *bird* the configuration will be as follows (when installing *bird* make sure you are using a recent version of the software):: - - # This file includes the allow_prefixes() method, which will - # decide which prefixes to install on the routing table. - include "/etc/bird/allow_prefixes.bird"; - - router id 10.0.0.10; - log syslog all; - debug protocols all; - - listen bgp address 10.0.0.10 port 179; - listen bgp address 10.0.1.10 port 179; - - # protocol kernel will control which routes go from the RIB to the FIB - protocol kernel { - export filter { - # We accept the routes coming from our Transit Provider - if from = 10.0.0.1 then accept; - # We also accept everything coming from our DC - if from = 10.0.1.11 then accept; - # This method is what the BGP controller will update later - if allow_prefixes() then accept; - # The rest is rejected. - reject; - }; - } - - protocol device { - scan time 10; - primary 10.0.0.0/24; - primary 10.0.1.0/24; - } - - # Transit Provider - protocol bgp { - local as 65010; - neighbor 10.0.0.1 as 65001; - - export filter { - # We only send the route coming from the DC - if from = 10.0.1.11 then accept; - reject; - }; - } - - # Peer - protocol bgp { - local as 65010; - neighbor 10.0.0.2 as 65002; - - export filter { - # We only send the route coming from the DC - if from = 10.0.1.11 then accept; - reject; - }; - } - - # DC - protocol bgp { - local as 65010; - neighbor 10.0.1.11 as 65011; - - export filter { - # We only want to send the default route to the DC - if net = 0.0.0.0/0 then accept; - reject; - }; - } - -In addition you will have to create the file */etc/bird/allow_prefixes.bird* with the following content:: - - function allow_prefixes() - { - return net ~ [ - # Dummy prefix that you will surely not have in your RIB. - # The reason for this is that the list cannot be empty. - 1.2.3.4/32 - ]; - } - -Let´s see if this is working:: - - # We start the service - $ sudo service bird start - [ ok ] Starting BIRD Internet Routing Daemon (IPv4): bird. - - # We connect to bird - $ sudo birdc - - # Routes from the Transit Provider - bird> show route protocol bgp1 - 0.0.0.0/0 via 10.0.0.1 on eth1 [bgp1 10:36] * (100) [AS65001i] - - # Routes from the Peer - bird> show route protocol bgp2 - 188.3.176.0/21 via 10.0.0.2 on eth1 [bgp2 10:36] * (100) [AS65002i] - 194.3.206.0/24 via 10.0.0.2 on eth1 [bgp2 10:36] * (100) [AS65002i] - 212.5.192.0/19 via 10.0.0.2 on eth1 [bgp2 10:36] * (100) [AS65002i] - 194.8.226.0/23 via 10.0.0.2 on eth1 [bgp2 10:36] * (100) [AS65002i] - ... - - # Routes from the DC - bird> show route protocol bgp3 - 192.168.0.0/24 via 10.0.1.11 on eth2 [bgp3 10:36] * (100) [AS65011i] - - # Routes to the Transit Provider and the Peer - bird> show route export bgp1 - 192.168.0.0/24 via 10.0.1.11 on eth2 [bgp3 10:36] * (100) [AS65011i] - bird> show route export bgp2 - 192.168.0.0/24 via 10.0.1.11 on eth2 [bgp3 10:36] * (100) [AS65011i] - - # Routes to the DC - bird> show route export bgp3 - 0.0.0.0/0 via 10.0.0.1 on eth1 [bgp1 10:36] * (100) [AS65001i] - - # Routes installed on the FIB - bird> show route export kernel1 - 0.0.0.0/0 via 10.0.0.1 on eth1 [bgp1 10:36] * (100) [AS65001i] - 192.168.0.0/24 via 10.0.1.11 on eth2 [bgp3 10:36] * (100) [AS65011i] - -As you can see we are doing exactly what we described before. So far, the routes coming from our **peer** are just being ignored. We are not installing them on the FIB. The BGP Controller will override this behavior later by modifying the contents of the file */etc/bird/allow_prefixes.bird*. diff --git a/docs/how_to_simple/simulating.rst b/docs/how_to_simple/simulating.rst deleted file mode 100644 index fbff3ab..0000000 --- a/docs/how_to_simple/simulating.rst +++ /dev/null @@ -1,120 +0,0 @@ -============== -Simulating sir -============== - -Before running sir in production you might want just to let pmacct collect data for a coupe of days and then run sir in simulation mode. That will let you test how the system is going to behave. You can easily do that by doing some small changes on the configuration (showing only relevant configuration):: - - # We can try different parameters - history: 72 - max_routes: 30000 # Maximum routes allowed - packet_sampling: 10000 # Packet sampling configured on the flow agent - - # We log to stderr instead to syslog - logging_level: DEBUG - log_to_syslog: False - syslog_server_ip: 127.0.0.1 - syslog_server_port: 514 - log_to_stderr: True - - pmacct_bgp_folder: '/bgp_controller/bgp' - - backend: 'sqlite.SQLite' - backend_options: - sqlite_file: '/bgp_controller/bgpc.db' # Path to the SQLite database - retention: 7 # Days to hold old data. - - # Simulation period - simulate: - date_format: '%Y-%m-%d %H:%M:%S' - start_date: '2014-12-18 00:00:00' - end_date: '2014-12-20 00:00:00' - - plugins: - - 'statistics.RouteStatistics' - - 'statistics.OffloadedBytes' - - 'bird.Bird' - - RouteStatistics: - db_table: 'route_statistics' - png_file: '/bgp_controller/route_statistics.png' - plot_days: 1 - - OffloadedBytes: - db_table: 'offloaded_bytes' - png_file: '/bgp_controller/offloaded_bytes.png' - plot_days: 1 - - # We don't want bird to reload the process - Bird: - policy_file: '/etc/bird/allow_prefixes.bird' - reload_bird: False - - -You can try different parameters to see how it behaves. The key points is instruct the bird plugin to not reload the configuration and to setup the proper dates for the simulation. Now we can try to run the simulation:: - - $ env python sir.py simulate -c /etc/sir-conf.yaml - INFO:sir:action=SIMULATE - INFO:sir:action=OPEN_BACKEND backend=SQLITE file=/bgp_controller/bgpc.db - DEBUG:sir:action=GET_AVAILABLE_DATES_IN_RANGE start_time=2014-12-18 00:00:00 end_time=2014-12-20 00:00:00 - INFO:sir:action=PROCESS_PREFIX start_date=2014-12-17 23:00:01 end_date=2014-12-18 11:00:01 - DEBUG:sir:action=GET_PREVIOUS_PREFIXES start_time=2014-12-17 23:00:01 end_time=2014-12-18 11:00:01 - DEBUG:sir:action=GET_BEST_PREFIXES start_time=2014-12-17 23:00:01 end_time=2014-12-18 11:00:01 - DEBUG:sir:action=SAVE_PREFIX_TABLE date=2014-12-18 11:00:01 - DEBUG:sir:action=GET_RAW_PREFIXES start_time=2014-12-17 23:00:01 end_time=2014-12-18 11:00:01 - INFO:sir:action=EXECUTE_PLUGIN plugin=statistics.RouteStatistics - DEBUG:sir:action=SAVE_DICT db_table=route_statistics - INFO:sir:action=EXECUTE_PLUGIN plugin=statistics.OffloadedBytes - DEBUG:sir:action=SAVE_DICT db_table=offloaded_bytes - INFO:sir:action=EXECUTE_PLUGIN plugin=bird.Bird - ... - INFO:sir:action=PROCESS_PREFIX start_date=2014-12-16 23:00:01 end_date=2014-12-19 23:00:01 - DEBUG:sir:action=GET_PREVIOUS_PREFIXES start_time=2014-12-16 23:00:01 end_time=2014-12-19 23:00:01 - DEBUG:sir:action=GET_BEST_PREFIXES start_time=2014-12-16 23:00:01 end_time=2014-12-19 23:00:01 - DEBUG:sir:action=SAVE_PREFIX_TABLE date=2014-12-19 23:00:01 - DEBUG:sir:action=GET_RAW_PREFIXES start_time=2014-12-16 23:00:01 end_time=2014-12-19 23:00:01 - INFO:sir:action=EXECUTE_PLUGIN plugin=statistics.RouteStatistics - DEBUG:sir:action=SAVE_DICT db_table=route_statistics - INFO:sir:action=EXECUTE_PLUGIN plugin=statistics.OffloadedBytes - DEBUG:sir:action=SAVE_DICT db_table=offloaded_bytes - INFO:sir:action=EXECUTE_PLUGIN plugin=bird.Bird - INFO:sir:action=CLOSE_BACKEND backend=SQLITE file=/bgp_controller/bgpc.db - -If everything worked as expected and you run the same plugins as I did you should have: - - * The file *'/etc/bird/allow_prefixes.bird'* with content:: - - $ cat /etc/bird/allow_prefixes.bird - function allow_prefixes() { - return net ~ [ - 87.238.168.0/21, - 186.182.128.0/18, - 87.99.0.0/18, - 212.15.224.0/20, - 92.255.216.0/22, - 185.25.216.0/22, - 193.37.237.0/24, - 46.21.216.0/21, - ... (output truncated) - 212.233.136.0/24, - 181.199.192.0/19, - 195.3.173.0/24, - 213.180.64.0/19, - 91.209.141.0/24, - 145.88.0.0/15, - 87.234.0.0/16, - 105.158.48.0/21 - ]; - } - - * Similar graphs to the ones below on the folder *'/bgp_controller/'* - - * Offloaded Bytes - - .. image:: ../img/offloaded_bytes.png - :alt: Offloaded Bytes - * Route Statistics - - .. image:: ../img/route_statistics.png - :alt: Route Statistics - -Hopefully, this will be enough hints on how to use the BGP Controller to get the most of your network. \ No newline at end of file diff --git a/docs/how_to_simple/sir.rst b/docs/how_to_simple/sir.rst deleted file mode 100644 index eb8893c..0000000 --- a/docs/how_to_simple/sir.rst +++ /dev/null @@ -1,68 +0,0 @@ -=============== -Configuring sir -=============== - -First step is to clone the GIT repository and install the requirements:: - - $ cd / - $ git clone git@github.com:dbarrosop/sir.git - $ cd sir - $ pip install -r requirements.txt - -Then you will need a configuration file with a content similar to the following:: - - history: 72 - max_routes: 30000 # Maximum routes allowed - packet_sampling: 10000 # Packet sampling configured on the flow agent - - logging_level: DEBUG - log_to_syslog: False - syslog_server_ip: 127.0.0.1 - syslog_server_port: 514 - log_to_stderr: True - - # Where pmacct stores the BGP feed - pmacct_bgp_folder: '/bgp_controller/bgp' - - backend: 'sqlite.SQLite' - backend_options: - sqlite_file: '/bgp_controller/bgpc.db' # Path to the SQLite database - retention: 7 # Days to hold old data. - - simulate: - date_format: '%Y-%m-%d %H:%M:%S' - start_date: '2014-12-18 00:00:00' - end_date: '2014-12-20 00:00:00' - - # List of plugins to run - plugins: - - 'statistics.RouteStatistics' - - 'statistics.OffloadedBytes' - - 'bird.Bird' - - # Used by plugin statistics.RouteStatistics. See more details on the plugin documentation. - RouteStatistics: - db_table: 'route_statistics' - png_file: '/bgp_controller/route_statistics.png' - plot_days: 1 - - # Used by plugin statistics.OffloadedBytes. See more details on the plugin documentation. - OffloadedBytes: - db_table: 'offloaded_bytes' - png_file: '/bgp_controller/offloaded_bytes.png' - plot_days: 1 - - # Used by plugin bird.Bird. See more details on the plugin documentation. - Bird: - policy_file: '/etc/bird/allow_prefixes.bird' - reload_bird: True - -Where you keep the configuration file is up to you, I will use **/etc/sir-conf.yaml**. Now, we have to adapt the backend:: - - # sir - $ sqlite3 /bgp_controller/bgpc.db < /sir/sql/sir.sql - - # Needed for the plugins I am using. Check the documentation of the plugins for more information. - $ sqlite3 /bgp_controller/bgpc.db < /sir/sql/plugins/offloaded_bytes.sql - $ sqlite3 /bgp_controller/bgpc.db < /sir/sql/plugins/route_statistics.sql - diff --git a/docs/img/bgp_controller.png b/docs/img/bgp_controller.png deleted file mode 100644 index db34f72..0000000 Binary files a/docs/img/bgp_controller.png and /dev/null differ diff --git a/docs/img/how_to_scenario.png b/docs/img/how_to_scenario.png deleted file mode 100644 index 76cd8d0..0000000 Binary files a/docs/img/how_to_scenario.png and /dev/null differ diff --git a/docs/img/offloaded_bytes.png b/docs/img/offloaded_bytes.png deleted file mode 100644 index 7b90928..0000000 Binary files a/docs/img/offloaded_bytes.png and /dev/null differ diff --git a/docs/img/route_statistics.png b/docs/img/route_statistics.png deleted file mode 100644 index ea20cab..0000000 Binary files a/docs/img/route_statistics.png and /dev/null differ diff --git a/docs/index.rst b/docs/index.rst index b71e1b4..1a5e588 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,16 +6,20 @@ SDN Internet Router (sir) ========================= -The SDN Internet Router (sir) adds some extra capabilities to your conventional router or L3 switch. It can, for -example, instruct your router which prefixes to install, allowing you to use a switch/router with a limited LPM -table to get the full BGP feed from your Service Provider or to peer with other people. +The SDN Internet Router, abbreviated SIR, is an agent that you can add to your router. The agent exposes information +that your router can't expose by itself like the BGP table, traffic per BGP prefix or traffic per ASN. This data +is provided both via a WebUI and an API to access this data. + +The agent is vendor agnostic as it gathers data using both BGP and netflow/sflow/ipfix. This means it can be attached +to any router or switch that supports those protocols. + .. toctree:: - :maxdepth: 2 + :maxdepth: 2 - users_guide/index - how_to_simple/index - backends/index - plugins/index - developers_guide/index - api/index + features/index + architecture/index + api/api_v1.0 + use_cases/index + how_to_asr/index + how_to_eos/index diff --git a/docs/plugins/equipment.rst b/docs/plugins/equipment.rst deleted file mode 100644 index cd0245e..0000000 --- a/docs/plugins/equipment.rst +++ /dev/null @@ -1,7 +0,0 @@ -Supported devices -***************** - -bird.Bird -========= - -.. autoclass:: bgp_controller.plugins.bird.Bird diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst deleted file mode 100644 index a68435e..0000000 --- a/docs/plugins/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -Plugins Documentation -********************* - -.. toctree:: - :maxdepth: 2 - - statistics - equipment - diff --git a/docs/plugins/statistics.rst b/docs/plugins/statistics.rst deleted file mode 100644 index 91e87c1..0000000 --- a/docs/plugins/statistics.rst +++ /dev/null @@ -1,12 +0,0 @@ -Statistics -********** - -statistics.RouteStatistics -========================== - -.. autoclass:: bgp_controller.plugins.statistics.RouteStatistics - -statistics.OffloadedBytes -========================== - -.. autoclass:: bgp_controller.plugins.statistics.OffloadedBytes \ No newline at end of file diff --git a/docs/use_cases/index.rst b/docs/use_cases/index.rst new file mode 100644 index 0000000..5b1cb52 --- /dev/null +++ b/docs/use_cases/index.rst @@ -0,0 +1,12 @@ +********* +Use Cases +********* + +Here are some use cases for SIR. I will elaborate more on this use cases and add new ones soon enough. If you have +any don't hesitate to let me know. + +.. toctree:: + :maxdepth: 2 + + peering_switch + sdcdn diff --git a/docs/use_cases/peering_router.dot b/docs/use_cases/peering_router.dot new file mode 100644 index 0000000..49d7e14 --- /dev/null +++ b/docs/use_cases/peering_router.dot @@ -0,0 +1,32 @@ +digraph SIR { + graph [truecolor=True, bgcolor="#ffffff5f"] + node [shape="box", style="rounded,filled", fontname="Source Code Pro", fontsize=12] + edge [fontname="Source Code Pro", fontsize=10, style=dashed] + + subgraph network { + IXP[label="Exchange Point", fillcolor="#F5F5DC"]; + Transit[fillcolor="#FFDEAD"]; + Switch[label="Switch", fillcolor="#5F9EA0", height=0.75, width=0.75]; + + Transit -> Switch[label="0.0.0.0/0", dir=none, style=solid]; + IXP -> Switch[label="lots of\nprefixes", dir=none, style=solid]; + Switch -> pmacct[label="netflow &\nBGP"] + } + + subgraph cluster_1 { + node [shape="component"] + pmacct; + SIR; + pmacct -> SIR[label="Flows aggr.\nper prefix"] + SIR -> fib_optimizer[label="TopN\nprefixes"]; + + fib_optimizer -> Switch[label="Install TopN\nprefixes"] + + } + + subgraph external { + node [shape="ellipse"]; + fib_optimizer[fillcolor="#FFB6C1"]; + + } +} diff --git a/docs/use_cases/peering_router.png b/docs/use_cases/peering_router.png new file mode 100644 index 0000000..b373de3 Binary files /dev/null and b/docs/use_cases/peering_router.png differ diff --git a/docs/use_cases/peering_switch.rst b/docs/use_cases/peering_switch.rst new file mode 100644 index 0000000..88597bd --- /dev/null +++ b/docs/use_cases/peering_switch.rst @@ -0,0 +1,25 @@ +A commodity switch as a Peering Router +====================================== + +This is a simple and interesting use case. Once you start collecting data in your peering routers you will quickly +realize that you don't use more than 30.000 prefixes daily. So why do you need a big and expensive router to hold +the full routing table? Wouldn't be better and easier (and most probably cheaper) to hold the full routing table in +the RIB and just install the routes you need in the FIB? + +With SIR you can easily see how many routes you need to offload your traffic and which routes you will need. Once +you have this information it's just a matter of instructing your switch to accept those prefixes and keep the rest in +memory. + +.. image:: peering_router.png + :align: center + :alt: peering_router + +You can do that in different ways, being SRD the most stable and simplest way. Soon I will share an app that leverages +on SIR to convert a cheap Arista 7280 switch into a peering router. + +This was the original purpose when I started SIR. Although the scope of SIR has changed (before it was a monolitic app +and now it's an agent that provides information via an aPI) you can see a presentation and a podcast about this topic +in the following links: + +* ``_ +* ``_ diff --git a/docs/use_cases/sdcdn.dot b/docs/use_cases/sdcdn.dot new file mode 100644 index 0000000..2f10ff5 --- /dev/null +++ b/docs/use_cases/sdcdn.dot @@ -0,0 +1,31 @@ +digraph SIR { + graph [truecolor=True, bgcolor="#ffffff5f"] + node [shape="ellipse", style="rounded,filled", fontname="Source Code Pro", fontsize=12] + edge [fontname="Source Code Pro", fontsize=10, style=dashed] + + aggregator[shape="doubleoctagon", fillcolor="#5F9EA0", height=0.75, width=0.75]; + hadoop[shape="component", fillcolor="#FAFAD2", height=0.75, width=0.75]; + cdn_user_dispatcher[shape="rpromoter", fillcolor="#8FBC8F"]; + u1, u2, u3, u4, u5, u6, u7, u8, u9, uN[shape="circle", fillcolor="#66CDAA"]; + u1 -> sir_lon + u2 -> sir_lon + u3 -> sir_fra + u4 -> sir_fra + u5 -> sir_ash + u6 -> sir_ash + u7 -> sir_sjc + u8 -> sir_jpn + u9 -> sir_syd + uN -> sir_syd + + sir_lon -> aggregator[label="BGP\n&\nTopN"]; + sir_fra -> aggregator[label="BGP\n&\nTopN"]; + sir_ash -> aggregator[label="BGP\n&\nTopN"]; + sir_sjc -> aggregator[label="BGP\n&\nTopN"]; + sir_jpn -> aggregator[label="BGP\n&\nTopN"]; + sir_syd -> aggregator[label="BGP\n&\nTopN"]; + + aggregator -> hadoop[label=" Aggregated Data"]; + + hadoop -> cdn_user_dispatcher[label=" Distribute users to\nthese networks"]; +} diff --git a/docs/use_cases/sdcdn.png b/docs/use_cases/sdcdn.png new file mode 100644 index 0000000..abbf121 Binary files /dev/null and b/docs/use_cases/sdcdn.png differ diff --git a/docs/use_cases/sdcdn.rst b/docs/use_cases/sdcdn.rst new file mode 100644 index 0000000..58b0b89 --- /dev/null +++ b/docs/use_cases/sdcdn.rst @@ -0,0 +1,29 @@ +A Software Defined Content Delivery Network +=========================================== + +As mentioned in the global architecture, you can aggregate the data exposed by SIR. This aggregated data can tell you +many things: + +* Which networks are connecting to yours at what times. +* How much throughput they need. +* From where you can deliver your content to those networks. + +This aggregated data could be sent to hadoop: + +.. image:: sdcdn.png + :align: center + :alt: sdcdn + +You could also add data from other sources. Things like: + +* Cost of each link. +* Latency. +* Load of each site. +* Reliability. + +Once all the data is in Hadoop you could try to analyze your global traffic pattern and metrics and redistribute +users to: + +* Minimize transit costs. +* Maximize capacity usage. +* Improve user experience. diff --git a/docs/users_guide/configuration.rst b/docs/users_guide/configuration.rst deleted file mode 100644 index 2ea428b..0000000 --- a/docs/users_guide/configuration.rst +++ /dev/null @@ -1,59 +0,0 @@ -============= -Configuration -============= - -Configuring the SDN Internet Router is very easy. There are a few global parameters and then there are some that are backend/plugin specific. For those, check the backend/plugin documentation. Below there is a configuration example:: - - history: 72 # How many days to consider for the best prefixes - max_routes: 30000 # Maximum routes allowed - packet_sampling: 10000 # Packet sampling configured on the flow agent - - # Logging options - logging_level: DEBUG - log_to_syslog: False - syslog_server_ip: 127.0.0.1 - syslog_server_port: 514 - log_to_stderr: True - - # Where pmacct stores the BGP feed - pmacct_bgp_folder: '/pmacct_data/output/bgp' - - # Backend to use and backend configuration - backend: 'sqlite.SQLite' - backend_options: - sqlite_file: '/pmacct_data/output/flows/pmacct.db' # Path to the SQLite database - retention: 7 # Days to hold old data. - - # Options for the simulator - simulate: - date_format: '%Y-%m-%d %H:%M:%S' - start_date: '2012-12-12 00:00:00' - end_date: '2015-12-20 00:00:00' - - # List of plugins to run - plugins: - - 'statistics.RouteStatistics' - - 'statistics.OffloadedBytes' - - 'bird.Bird' - - ########################################################################### - # Configuration below is specific to the plugins I am using. Check their - # documentation for more info. - ########################################################################### - - # Used by plugin statistics.RouteStatistics. See more details on the plugin documentation. - RouteStatistics: - db_table: 'route_statistics' - png_file: '/Users/dbarroso/Documents/workspace/pmacct_data/route_statistics.png' - plot_days: 2 - - # Used by plugin statistics.OffloadedBytes. See more details on the plugin documentation. - OffloadedBytes: - db_table: 'offloaded_bytes' - png_file: '/Users/dbarroso/Documents/workspace/pmacct_data/offloaded_bytes.png' - plot_days: 2 - - # Used by plugin bird.Bird. See more details on the plugin documentation. - Bird: - policy_file: '/Users/dbarroso/Documents/workspace/pmacct_data/allow_prefixes.bird' - reload_bird: False diff --git a/docs/users_guide/features.rst b/docs/users_guide/features.rst deleted file mode 100644 index 02db0fa..0000000 --- a/docs/users_guide/features.rst +++ /dev/null @@ -1,17 +0,0 @@ -======== -Features -======== - -The SDN Internet Router was designed to be extensible and pluggable from the beginning. The base system itself does not have that many features, it just provides a framework for backends and plugins to work with. - --------- -Backends --------- - -Backends provide the system a way of retrieving and storing data. In addition, they are who also provide the algorithm to select the best routes. You can only have one backend enable at a time. For more information, check the `backends documentation <../backends/index.html>`_. - -------- -Plugins -------- - -Plugins provide functionality to the system. A plugin will get all the information provided to the backend and do with it anything necessary. It could modify a BGP configuration to install prefixes by data sent/received, to prefer certain prefixes from certain peers, draw statistics of bytes/prefix, etc. For more information on available plugins and its functionality, check the `plugins documentation <../plugins/index.html>`_. diff --git a/docs/users_guide/index.rst b/docs/users_guide/index.rst deleted file mode 100644 index b3ab905..0000000 --- a/docs/users_guide/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -Users Guide -*********** - -.. toctree:: - :maxdepth: 2 - - introduction - requirements - features - configuration - operations \ No newline at end of file diff --git a/docs/users_guide/introduction.rst b/docs/users_guide/introduction.rst deleted file mode 100644 index 54339dc..0000000 --- a/docs/users_guide/introduction.rst +++ /dev/null @@ -1,19 +0,0 @@ -============ -Introduction -============ - -The BGP controller consists of two different pieces: - -* **The Internet Router** - This is the router itself. You can use any router or L3 switch you want. The only requisites are: - - - It must support *selective route download*. - - Must be able to export flow information to `pmacct `_. - -* **The BGP Controller** - The BGP controller will gather data from the Internet Router (like the BGP feed and flow statistics) and will instruct the Internet router which prefixes to install on the FIB. The BGP controller consists of two software pieces: - - - `pmacct `_ - Which will collect flows information and the BGP feed from the Internet Router to aggregate flows per BGP prefix. - - **sir** - Which will use the information generated by `pmacct `_ to calculate the TOP BGP prefixes in our network and do different stuff like telling the Internet Router which prefixes to install on the FIB. - -.. image:: ../img/bgp_controller.png - :alt: BGP Controller - :target: ../_images/bgp_controller.png \ No newline at end of file diff --git a/docs/users_guide/operations.rst b/docs/users_guide/operations.rst deleted file mode 100644 index 47fb4ce..0000000 --- a/docs/users_guide/operations.rst +++ /dev/null @@ -1,33 +0,0 @@ -================= -Operational Modes -================= - -You can run the SDN Internet router in two modes: - - - **run** - This is the normal mode of operation. - - **simulate** - This mode allows you to simulate how the SDN Internet router would behave during a period of time. - -It is important to note that each operational mode will affect how plugins work. Some plugins will not run at all in *"simulate"* mode and some others will run a particular code on each iteration of the simulation and another code on the last iteration of the simulation. Check the plugins documentation for more info. - --------- -Run mode --------- - -It will get the best prefixes from the backend within the time range *"from now to now-conf['history']"*. To run sir on this mode execute:: - - env python sir.py run -c etc/config.yaml - -------------- -Simulate mode -------------- - -This mode allows you to test how the SDN Internet Router would work during a period of time. The only requisite is that you must have data for that specific period. For example, if I want to test how my SDN Internet Router would work during a period of time I could add the following configuration options:: - - simulate: - date_format: '%Y-%m-%d %H:%M:%S' - start_date: '2014-12-18 00:00:00' - end_date: '2014-12-20 00:00:00' - -That would instruct sir to run the simulator within the period of time 2014-12-18 to 2014-12-20. To run the simulator execute:: - - env python sir.py simulate -c etc/config.yaml \ No newline at end of file diff --git a/docs/users_guide/requirements.rst b/docs/users_guide/requirements.rst deleted file mode 100644 index 1649f60..0000000 --- a/docs/users_guide/requirements.rst +++ /dev/null @@ -1,30 +0,0 @@ -============ -Requirements -============ - -------------------- -The Internet Router -------------------- - -You can use any router or L3 switch you want. The more LPM routes you can afford, the better, however, you are not bounded by that. -The Internet will require: - - * BGP - - - All BGP feature required by your network design. - - Selective Route Download. - - * Export flow statistics. Any mean supported by pmacct_. - ------------------- -The BGP Controller ------------------- - - -The BGP Controller will require: - - * pmacct_. - * sir (python2.7). - - -.. _pmacct: http://www.pmacct.net/ \ No newline at end of file diff --git a/etc/config.yaml b/etc/config.yaml deleted file mode 100644 index 7cdad49..0000000 --- a/etc/config.yaml +++ /dev/null @@ -1,45 +0,0 @@ -history: 72 -max_routes: 30000 # Maximum routes allowed -packet_sampling: 10000 # Packet sampling configured on the flow agent - -logging_level: DEBUG -log_to_syslog: False -syslog_server_ip: 127.0.0.1 -syslog_server_port: 514 -log_to_stderr: True - -# Where pmacct stores the BGP feed -pmacct_bgp_folder: '/Users/dbarroso/Documents/workspace/pmacct_data/output/bgp' - -backend: 'sqlite.SQLite' -backend_options: - sqlite_file: '/Users/dbarroso/Documents/workspace/pmacct_data/output/flows/pmacct.db' # Path to the SQLite database - retention: 7 # Days to hold old data. - -simulate: - date_format: '%Y-%m-%d %H:%M:%S' - start_date: '2014-12-18 00:00:00' - end_date: '2014-12-20 00:00:00' - -# List of plugins to run -plugins: - - 'statistics.RouteStatistics' - - 'statistics.OffloadedBytes' - - 'bird.Bird' - -# Used by plugin statistics.RouteStatistics. See more details on the plugin documentation. -RouteStatistics: - db_table: 'route_statistics' - png_file: '/Users/dbarroso/Documents/workspace/pmacct_data/route_statistics.png' - plot_days: 1 - -# Used by plugin statistics.OffloadedBytes. See more details on the plugin documentation. -OffloadedBytes: - db_table: 'offloaded_bytes' - png_file: '/Users/dbarroso/Documents/workspace/pmacct_data/offloaded_bytes.png' - plot_days: 1 - -# Used by plugin bird.Bird. See more details on the plugin documentation. -Bird: - policy_file: '/Users/dbarroso/Documents/workspace/pmacct_data/allow_prefixes.bird' - reload_bird: False diff --git a/helpers/FSHelper.py b/helpers/FSHelper.py new file mode 100644 index 0000000..102eb0e --- /dev/null +++ b/helpers/FSHelper.py @@ -0,0 +1,76 @@ +import glob +from datetime import datetime +import json +import ipaddress + + +class FSHelper: + def __init__(self, base_path='./data'): + self.base_path = base_path + files = [f.split('/')[-1] for f in glob.glob('{}/bgp-*'.format(self.base_path))] + self.neighbors = set([n.split('-')[1].replace('_', '.') for n in files]) + dates = sorted(set([n.split('-')[2].replace('.txt', '') for n in files])) + self.dates = [datetime.strptime(d, '%Y_%m_%dT%H_%M_%S') for d in dates] + + def get_available_dates(self): + return self.dates + + def get_bgp_prefixes(self, date): + date = date.replace('-', '_').replace(':', '_') + + prefixes = dict() + + for n in self.neighbors: + prefixes[n] = dict() + f = 'bgp-{}-{}'.format(n, date).replace('.', '_') + + with open('{}/{}.txt'.format(self.base_path, f)) as data_file: + prefixes[n] = data_file.read() + data_file.close() + + return prefixes + + def find_prefix(self, prefix, date): + ip_prefix_str = unicode(prefix) + search_string = '"ip_prefix": "{}.'.format(ip_prefix_str.split('.')[0]) + ipp = ipaddress.ip_network(ip_prefix_str) + + date = date.replace('-', '_').replace(':', '_') + + prefixes = dict() + + for n in self.neighbors: + prefixes[n] = list() + f = 'bgp-{}-{}'.format(n, date).replace('.', '_') + + with open('{}/{}.txt'.format(self.base_path, f)) as f: + for line in f: + if search_string in line: + data = json.loads(line) + other_ipp = ipaddress.ip_network(unicode(data['ip_prefix'])) + if ipp.overlaps(other_ipp): + if ipp.prefixlen >= other_ipp.prefixlen: + prefixes[n].append(data) + return prefixes + + def find_prefixes_asn(self, asn, date, originate_only=False): + date = date.replace('-', '_').replace(':', '_') + + prefixes = dict() + + for n in self.neighbors: + prefixes[n] = list() + f = 'bgp-{}-{}'.format(n, date).replace('.', '_') + + with open('{}/{}.txt'.format(self.base_path, f)) as f: + for line in f: + if asn in line: + data = json.loads(line) + as_path = data['as_path'].split(' ') + if originate_only: + if asn == as_path[-1]: + prefixes[n].append(data) + else: + if asn in as_path: + prefixes[n].append(data) + return prefixes diff --git a/helpers/SQLite3Helper.py b/helpers/SQLite3Helper.py new file mode 100644 index 0000000..a436eb4 --- /dev/null +++ b/helpers/SQLite3Helper.py @@ -0,0 +1,213 @@ +import sqlite3 as lite +import os +from datetime import datetime + + +def dict_factory(cursor, row): + d = {} + for idx, col in enumerate(cursor.description): + d[col[0]] = row[idx] + return d + + +class SQLite3Helper: + + def __init__(self, database): + if not os.path.isfile(database): + raise Exception("Database file doesn't exist: %s" % database) + + self.database = database + self.conn = None + + def connect(self): + self.conn = lite.connect(database=self.database) + self.conn.row_factory = dict_factory + + def close(self): + if self.conn is not None: + self.conn.close() + + def _execute_query(self, query, args=(), commit=False): + try: + cur = self.conn.cursor() + cur.execute(query, args) + + if commit: + result = self.conn.commit() + else: + result = cur.fetchall() + except lite.OperationalError: + raise Exception('The following query failed:\n%s' % query) + return result + + def aggregate_per_prefix(self, start_time, end_time, limit=0, net_masks='', exclude_net_masks=False): + """ Given a time range aggregates bytes per prefix. + + Args: + start_time: A string representing the starting time of the time range + end_time: A string representing the ending time of the time range + limit: An optional integer. If it's >0 it will limit the amount of prefixes returned. + + Returns: + A list of prefixes sorted by sum_bytes. For example: + + [ + {'key': '192.168.1.0/25', 'sum_bytes': 3000, 'as_dst': 345}, + {'key': '192.213.1.0/25', 'sum_bytes': 2000, 'as_dst': 123}, + {'key': '231.168.1.0/25', 'sum_bytes': 1000, 'as_dst': 321}, + ] + """ + if net_masks == '': + net_mask_filter = '' + elif not exclude_net_masks: + net_mask_filter = 'AND mask_dst IN ({})'.format(net_masks) + elif exclude_net_masks: + net_mask_filter = 'AND mask_dst NOT IN ({})'.format(net_masks) + + query = ''' SELECT ip_dst||'/'||mask_dst as key, SUM(bytes) as sum_bytes, as_dst + from acct + WHERE + datetime(stamp_updated) BETWEEN datetime(?) AND datetime(?, "+1 second") + {} + GROUP by ip_dst,mask_dst + ORDER BY SUM(bytes) DESC + '''.format(net_mask_filter) + + if limit > 0: + query += 'LIMIT %d' % limit + + return self._execute_query(query, [start_time, end_time]) + + def aggregate_per_as(self, start_time, end_time): + """ Given a time range aggregates bytes per ASNs. + + Args: + start_time: A string representing the starting time of the time range + end_time: A string representing the ending time of the time range + + Returns: + A list of prefixes sorted by sum_bytes. For example: + + [ + {'key': '6500', 'sum_bytes': 3000}, + {'key': '2310', 'sum_bytes': 2000}, + {'key': '8182', 'sum_bytes': 1000}, + ] + """ + + query = ''' SELECT as_dst as key, SUM(bytes) as sum_bytes + from acct + WHERE + datetime(stamp_updated) BETWEEN datetime(?) AND datetime(?, "+1 second") + GROUP by as_dst + ORDER BY SUM(bytes) DESC; + ''' + + return self._execute_query(query, [start_time, end_time]) + + def get_dates(self): + query = '''SELECT DISTINCT stamp_updated from acct ORDER BY stamp_updated ASC;''' + return [datetime.strptime(d['stamp_updated'], '%Y-%m-%d %H:%M:%S') for d in self._execute_query(query)] + + def get_dates_in_range(self, start_time, end_time): + query = ''' SELECT DISTINCT stamp_updated + from acct + WHERE + datetime(stamp_updated) BETWEEN datetime(?) AND datetime(?, "+1 second") + ; + ''' + + return [datetime.strptime(d['stamp_updated'], '%Y-%m-%d %H:%M:%S') + for d in self._execute_query(query, [start_time, end_time])] + + def get_total_traffic(self, start_time, end_time): + query = ''' SELECT SUM(bytes) as sum_bytes + FROM acct + WHERE + datetime(stamp_updated) BETWEEN datetime(?) AND datetime(?, "+1 second") + ; + ''' + + return self._execute_query(query, [start_time, end_time])[0]['sum_bytes'] + + def offloaded_bytes(self, num_prefixes, start_time, end_time): + query = ''' SELECT SUM(bytes) as sum_bytes FROM ( + SELECT ip_dst, mask_dst, SUM(bytes) AS bytes + from acct + WHERE + datetime(stamp_updated) BETWEEN datetime(:start_time) AND datetime(:end_time, "+1 second") + GROUP BY ip_dst, mask_dst + ORDER BY SUM(bytes) DESC + LIMIT %d + ); + ''' % num_prefixes + args = {'start_time': start_time, 'end_time': end_time, 'num_prefixes': num_prefixes,} + return self._execute_query(query, args)[0]['sum_bytes'] + + def timeseries_per_as(self, start_time, end_time, asn): + query = ''' SELECT SUM(bytes) as sum_bytes + from acct + WHERE + datetime(stamp_updated) BETWEEN datetime(?) AND datetime(?, "+1 second") + AND + as_dst = ? + GROUP by as_dst, stamp_updated + ORDER BY stamp_updated ASC; + ''' + + return [r['sum_bytes'] for r in self._execute_query(query, [start_time, end_time, asn])] + + def timeseries_per_prefix(self, start_time, end_time, prefix): + ip_dst, mask_dst = prefix.split('/') + query = ''' SELECT SUM(bytes) as sum_bytes + from acct + WHERE + datetime(stamp_updated) BETWEEN datetime(?) AND datetime(?, "+1 second") + AND + ip_dst = ? AND mask_dst = ? + GROUP by ip_dst, mask_dst, stamp_updated + ORDER BY stamp_updated ASC; + ''' + + return [r['sum_bytes'] for r in self._execute_query(query, [start_time, end_time, ip_dst, mask_dst])] + + def put_variables(self, name, content, category): + query = ''' INSERT INTO variables VALUES (?, ?, ?); ''' + self._execute_query(query, [name, content, category], commit=True) + + def get_variables(self): + query = ''' SELECT * FROM variables ''' + return self._execute_query(query) + + def get_variable(self, category, name): + query = ''' SELECT * FROM variables WHERE category = ? AND name = ?; ''' + return self._execute_query(query, [category, name]) + + def update_variable(self, old_name, old_category, name, content, category): + query = ''' UPDATE variables + SET name = ?, content = ?, category = ? + WHERE name = ? AND category = ?; + ''' + self._execute_query(query, [name, content, category, old_name, old_category], commit=True) + + def delete_variable(self, category, name): + query = ''' DELETE FROM variables WHERE name = ? AND category = ?; + ''' + self._execute_query(query, [name, category], commit=True) + + def get_categories(self): + query = ''' SELECT DISTINCT category FROM variables''' + return [c['category'] for c in self._execute_query(query, [])] + + def filter_variables_category(self, category): + query = ''' SELECT * FROM variables WHERE category = ?; ''' + return self._execute_query(query, [category]) + + def get_flows(self, start_time, end_time): + query = ''' SELECT ip_dst, mask_dst, bytes, packets, as_dst, stamp_updated + FROM acct + WHERE + datetime(stamp_updated) BETWEEN datetime(?) AND datetime(?, "+1 second") + ; + ''' + return self._execute_query(query, [start_time, end_time]) diff --git a/bgp_controller/plugins/__init__.py b/helpers/__init__.py similarity index 100% rename from bgp_controller/plugins/__init__.py rename to helpers/__init__.py diff --git a/helpers/api.py b/helpers/api.py new file mode 100644 index 0000000..af1937d --- /dev/null +++ b/helpers/api.py @@ -0,0 +1,13 @@ +from flask import g + +def build_api_response(result, error=False, **kwargs): + response = { + 'result': result, + 'parameters': kwargs, + 'meta': { + 'request_time': getattr(g, 'request_time')(), + 'length': len(result), + 'error': error, + }, + } + return response diff --git a/pmacct_data/__init__.py b/pmacct_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pmacct_data/api.py b/pmacct_data/api.py new file mode 100644 index 0000000..52f8ecf --- /dev/null +++ b/pmacct_data/api.py @@ -0,0 +1,23 @@ +import helpers.api +from flask import g + + +def get_dates(request): + db = getattr(g, 'db') + dates = [d.strftime('%Y-%m-%dT%H:%M:%S') for d in db.get_dates()] + return helpers.api.build_api_response(result=dates, error=False) + + +def get_flows(request): + db = getattr(g, 'db') + start_time = request.args.get('start_time') + end_time = request.args.get('end_time') + flows = db.get_flows(start_time, end_time) + return helpers.api.build_api_response(flows, error=False, start_time=start_time, end_time=end_time) + + +def get_bgp_prefixes(request): + fs = getattr(g, 'fs') + date = request.args.get('date') + bgp_prefixes = fs.get_bgp_prefixes(date) + return helpers.api.build_api_response(bgp_prefixes, error=False, date=date) diff --git a/requirements.txt b/requirements.txt index e6d599f..c544c7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ -PyYAML -pandas -matplotlib +flask +ipaddress diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..3d51935 --- /dev/null +++ b/settings.py @@ -0,0 +1,6 @@ +DATABASE = '/Users/dbarroso/Documents/workspace/spotify/sir/data/pmacct.db' +BGP_FOLDER = '/Users/dbarroso/Documents/workspace/spotify/sir/data/' +DEBUG = True +SECRET_KEY = 'development key' +BIND_IP = '127.0.0.1' +PORT= 5000 diff --git a/setup.py b/setup.py deleted file mode 100644 index 73d1919..0000000 --- a/setup.py +++ /dev/null @@ -1,23 +0,0 @@ -from setuptools import setup, find_packages -from pip.req import parse_requirements - - -# parse_requirements() returns generator of pip.req.InstallRequirement objects -install_reqs = parse_requirements('requirements.txt') - -# reqs is a list of requirement -# e.g. ['django==1.5.1', 'mezzanine==1.4.6'] -reqs = [str(ir.req) for ir in install_reqs] - -setup( - name='sir', - version='0.01', - py_modules=['sir'], - packages = find_packages(), - install_requires=reqs, - entry_points=''' - [console_scripts] - sir=sir:cli - ''', - include_package_data=True, -) \ No newline at end of file diff --git a/sir.py b/sir.py index 0f4f034..0e4de89 100644 --- a/sir.py +++ b/sir.py @@ -1,70 +1,198 @@ -from bgp_controller.bgpc import BGPController +# -*- coding: utf-8 -*- -import argparse -import yaml -import sys +# TODO Complete pySIR +# TODO Move applications outside -import logging -logger = logging.getLogger('sir') +# TODO Expose raw flows, delete flows +# TODO Expose raw BGP, delete raw BGP files +# TODO UI to Add, Edit, delete variables +# TODO metrics +# TODO Improve building the response of the API and documentation +# TODO Cache ASNs from peering db +# TODO Catch errors in API +# TODO Catch errors in logging +# TODO Catch errors in authentication??? +from helpers.SQLite3Helper import SQLite3Helper +from helpers.FSHelper import FSHelper -def configure_parser(): - parser = argparse.ArgumentParser( - description="", - ) - global_parser = argparse.ArgumentParser(add_help=False) - global_parser.add_argument( - '-c', - default='etc/config.yaml', - dest='config', - help='Configuration file. Default is config.yaml' - ) +import variables.api +import variables.views - subparsers = parser.add_subparsers() +import analytics.api +import analytics.views - parser_simulate = subparsers.add_parser( - 'simulate', - help='Connects to pmacct folder and runs a simulation', - parents=[global_parser] - ) - parser_simulate.set_defaults(action='simulate') +import api.views - parser_run = subparsers.add_parser( - 'run', - help='Runs the controller', - parents=[global_parser] - ) - parser_run.set_defaults(action='run') +import pmacct_data.api - args = parser.parse_args() - return args +from flask import Flask, request, g, jsonify, render_template -def configure_logging(config): - formatter = logging.Formatter('program=sir severity_label=%(levelname)s severity=%(levelno)s %(message)s') - logger.setLevel(config['logging_level']) +import time - if config['log_to_stderr']: - logging.basicConfig(level=config['logging_level'], stream=sys.stderr) - if config['log_to_syslog']: - handler = logging.handlers.SysLogHandler(('127.0.0.1', config['syslog_server_port'])) - handler.setFormatter(formatter) - logger.addHandler(handler) +app = Flask(__name__) +app.config.from_object('settings') +################### +################### +# BASIC ######### +################### +################### -def cli(): - args = configure_parser() - content = open(args.config, 'r') - config = yaml.load(content) +@app.before_request +def before_request(): + g.db = SQLite3Helper(app.config['DATABASE']) + g.db.connect() + g.request_start_time = time.time() + g.request_time = lambda: float("%.5f" % + (time.time() - g.request_start_time)) + g.fs = FSHelper(app.config['BGP_FOLDER']) - configure_logging(config) - bgp_controller = BGPController(config) +@app.teardown_request +def teardown_request(exception): + db = getattr(g, 'db', None) + if db is not None: + db.close() - if args.action == 'simulate': - bgp_controller.simulate() - elif args.action == 'run': - bgp_controller.run() -if __name__ == "__main__": - cli() \ No newline at end of file +@app.route('/', strict_slashes=False) +def start_page(): + return render_template('basic/start_page.html') + +################### +################### +# ANALYTICS ##### +################### +################### + + +@app.route('/analytics', strict_slashes=False) +def analytics_view_help(): + return analytics.views.start_page(request) + + +@app.route('/analytics/offloaded_traffic', methods=['GET', 'POST']) +def analytics_view_offloaded_traffic(): + return analytics.views.offloaded_traffic(request) + + +@app.route('/analytics/aggregate_per_as', methods=['GET', 'POST']) +def analytics_view_aggregate_per_as(): + return analytics.views.aggregate(request, 'as') + + +@app.route('/analytics/aggregate_per_prefix', methods=['GET', 'POST']) +def analytics_view_aggregate_per_prefix(): + return analytics.views.aggregate(request, 'prefix') + + +@app.route('/analytics/simulate', methods=['GET', 'POST']) +def analytics_view_simulate(): + return analytics.views.simulate(request) + + +@app.route('/api/v1.0/analytics/top_prefixes', methods=['GET']) +def analytics_api_top_prefixes(): + return jsonify(analytics.api.top_prefixes(request)) + + +@app.route('/api/v1.0/analytics/top_asns', methods=['GET']) +def analytics_api_top_asns(): + return jsonify(analytics.api.top_asns(request)) + + +@app.route('/api/v1.0/analytics/find_prefix//', methods=['GET']) +def analytics_api_find_prefix(prefix, pl): + return jsonify(analytics.api.find_prefix(request, u'{}/{}'.format(prefix, pl))) + + +@app.route('/analytics/find_prefix', methods=['GET', 'POST']) +def analytics_view_find_prefix(): + return analytics.views.find_prefix(request) + + +@app.route('/api/v1.0/analytics/find_prefixes_asn/', methods=['GET']) +def analytics_api_find_prefixes_asn(asn): + return jsonify(analytics.api.find_prefixes_asn(request, asn)) + + +@app.route('/analytics/find_prefixes_asn', methods=['GET', 'POST']) +def analytics_view_find_prefix_asn(): + return analytics.views.find_prefix_asn(request) + + +################### +################### +# API ########### +################### +################### + + +@app.route('/api/documentation', strict_slashes=False) +def api_help(): + return api.views.start_page(request) + + +################### +################### +# VARIABLES ##### +################### +################### + + +@app.route('/variables/browse', methods=['GET']) +def browse_view_variables(): + return variables.views.browse_variables(request) + +''' +@app.route('/variables/edit//', methods=['GET', 'POST', 'DELETE']) +def edit_variable(category, name): + return variables.views.edit_variable(request, category, name) +''' + + +@app.route('/api/v1.0/variables', methods=['GET', 'POST']) +def variables_api_variables(): + return jsonify(variables.api.variables(request)) + + +@app.route('/api/v1.0/variables/categories', methods=['GET']) +def variables_api_category(): + return jsonify(variables.api.variables_category(request)) + + +@app.route('/api/v1.0/variables/categories/', methods=['GET']) +def variables_api_filter_by_category(category): + return jsonify(variables.api.variables_filter_by_category(request, category)) + + +@app.route('/api/v1.0/variables/categories//', methods=['GET', 'PUT', 'DELETE']) +def variables_api_name(category, name): + return jsonify(variables.api.api_variables_name(request, category, name)) + +################### +################### +# PMACCT_DATA ### +################### +################### + + +@app.route('/api/v1.0/pmacct/dates', methods=['GET']) +def pmacct_data_api_get_dates(): + return jsonify(pmacct_data.api.get_dates(request)) + + +@app.route('/api/v1.0/pmacct/flows', methods=['GET']) +def pmacct_data_api_get_flows(): + return jsonify(pmacct_data.api.get_flows(request)) + + +@app.route('/api/v1.0/pmacct/bgp_prefixes', methods=['GET']) +def pmacct_data_api_get_bgp_prefixes(): + return jsonify(pmacct_data.api.get_bgp_prefixes(request)) + + +if __name__ == '__main__': + app.run(app.config['BIND_IP'], app.config['PORT']) diff --git a/sql/plugins/offloaded_bytes.sql b/sql/plugins/offloaded_bytes.sql deleted file mode 100644 index 007f6ef..0000000 --- a/sql/plugins/offloaded_bytes.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE `offloaded_bytes` ( - time DATETIME NOT NULL, - total_bytes INT NOT NULL, - offloaded INT NOT NULL, - percentage INT NOT NULL, - PRIMARY KEY(time) -); \ No newline at end of file diff --git a/sql/plugins/route_statistics.sql b/sql/plugins/route_statistics.sql deleted file mode 100644 index 0d0e36b..0000000 --- a/sql/plugins/route_statistics.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE route_statistics -( - time TEXT PRIMARY KEY NOT NULL, - total INTEGER NOT NULL, - kept INTEGER NOT NULL, - removed INTEGER NOT NULL, - added INTEGER NOT NULL -); diff --git a/sql/pmacct.sql b/sql/pmacct.sql deleted file mode 100644 index aad083d..0000000 --- a/sql/pmacct.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE `acct` ( - `mac_src` CHAR(17) NOT NULL DEFAULT '0:0:0:0:0:0', - `mac_dst` CHAR(17) NOT NULL DEFAULT '0:0:0:0:0:0', - `ip_src` CHAR(15) NOT NULL DEFAULT '0.0.0.0', - `ip_dst` CHAR(15) NOT NULL DEFAULT '0.0.0.0', - `mask_dst` INTEGER(1) NOT NULL DEFAULT 0, - `src_port` INT(4) NOT NULL DEFAULT 0, - `dst_port` INT(4) NOT NULL DEFAULT 0, - `ip_proto` CHAR(6) NOT NULL DEFAULT 0, - `packets` INT NOT NULL, - `bytes` BIGINT NOT NULL, - `stamp_inserted` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00', - `stamp_updated` DATETIME, - PRIMARY KEY(mac_src,mac_dst,ip_src,ip_dst,mask_dst,src_port,dst_port,ip_proto,stamp_inserted) -); \ No newline at end of file diff --git a/sql/reset_data.sql b/sql/reset_data.sql deleted file mode 100644 index e5dd5fc..0000000 --- a/sql/reset_data.sql +++ /dev/null @@ -1,3 +0,0 @@ -DELETE FROM best_prefixes; -DELETE FROM offloaded_bytes; -DELETE FROM route_statistics; \ No newline at end of file diff --git a/sql/sir.sql b/sql/sir.sql deleted file mode 100644 index 6b48900..0000000 --- a/sql/sir.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE best_prefixes -( - ip_dst TEXT NOT NULL, - mask_dst INTEGER NOT NULL, - packets INTEGER NOT NULL, - bytes INTEGER NOT NULL, - stamp_updated TEXT NOT NULL, - PRIMARY KEY (ip_dst, mask_dst, stamp_updated) -); diff --git a/static/highlight.pack.js b/static/highlight.pack.js new file mode 100644 index 0000000..24d2300 --- /dev/null +++ b/static/highlight.pack.js @@ -0,0 +1 @@ +!function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/no-?highlight|plain|text/.test(e)}function i(e){var n,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=/\blang(?:uage)?-([\w-]+)\b/.exec(i))return E(t[1])?t[1]:"no-highlight";for(i=i.split(/\s+/),n=0,r=i.length;r>n;n++)if(E(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){f+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,f="",l=[];e.length||r.length;){var g=i();if(f+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){l.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);l.reverse().forEach(o)}else"start"==g[0].event?l.push(g[0].node):l.pop(),c(g.splice(0,1)[0])}return f+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var f=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=f.length?t(f.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function p(){if(!L.k)return n(B);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(B);r;){e+=n(B.substr(t,r.index-t));var a=g(L,r);a?(y+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(B)}return e+n(B.substr(t))}function d(){if(L.sL&&!x[L.sL])return n(B);var e=L.sL?f(L.sL,B,!0,M[L.sL]):l(B);return L.r>0&&(y+=e.r),"continuous"==L.subLanguageMode&&(M[L.sL]=e.top),h(e.language,e.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,"",!0):"";e.rB?(k+=r,B=""):e.eB?(k+=n(t)+r,B=""):(k+=r,B=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(B+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(B+=t),k+=b();do L.cN&&(k+=""),y+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),B="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return B+=t,t.length||1}var N=E(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,L=i||N,M={},k="";for(R=L;R!=N;R=R.parent)R.cN&&(k=h(R.cN,"",!0)+k);var B="",y=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),R=L;R.parent;R=R.parent)R.cN&&(k+="");return{r:y,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function l(e,t){t=t||w.languages||Object.keys(x);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(E(n)){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return w.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,w.tabReplace)})),w.useBR&&(e=e.replace(/\n/g,"
")),e}function h(e,n,t){var r=n?R[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=i(e);if(!a(n)){var t;w.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?f(n,r,!0):l(r),s=u(t);if(s.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){w=o(w,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){R[e]=n})}function N(){return Object.keys(x)}function E(e){return x[e]||x[R[e]]}var w={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},x={},R={};return e.highlight=f,e.highlightAuto=l,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=E,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="\\b(0[xX][a-fA-F0-9]+|(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",bK:"TODO FIXME NOTE BUG XXX",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("json",function(e){var t={literal:"true false null"},i=[e.QSM,e.CNM],l={cN:"value",e:",",eW:!0,eE:!0,c:i,k:t},c={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:l}],i:"\\S"},n={b:"\\[",e:"\\]",c:[e.inherit(l,{cN:null})],i:"\\S"};return i.splice(i.length,0,c,n),{c:i,k:t,i:"\\S"}});hljs.registerLanguage("http",function(t){return{aliases:["https"],i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:!0}}]}});hljs.registerLanguage("python",function(e){var r={cN:"prompt",b:/^(>>>|\.\.\.) /},b={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[r],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[r],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},e.ASM,e.QSM]},l={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},c={cN:"params",b:/\(/,e:/\)/,c:["self",r,l,b]};return{aliases:["py","gyp"],k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[r,l,b,e.HCM,{v:[{cN:"function",bK:"def",r:10},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,c]},{cN:"decorator",b:/@/,e:/$/},{b:/\b(print|exec)\(/}]}}); \ No newline at end of file diff --git a/static/highlight_styles/agate.css b/static/highlight_styles/agate.css new file mode 100644 index 0000000..50f8489 --- /dev/null +++ b/static/highlight_styles/agate.css @@ -0,0 +1,137 @@ +/*! + * Agate by Taufik Nurrohman + * ---------------------------------------------------- + * + * #ade5fc + * #a2fca2 + * #c6b4f0 + * #d36363 + * #fcc28c + * #fc9b9b + * #ffa + * #fff + * #333 + * #62c8f3 + * #888 + * + */ + +.hljs { + display: block; + overflow-x: auto; + padding: .5em; + background: #333; + color: white; + -webkit-text-size-adjust: none; +} + +.asciidoc .hljs-title, +.hljs-label, +.hljs-tag .hljs-title, +.hljs-prompt, +.http .hljs-request { + font-weight: bold; +} + +.hljs-change, +.hljs-code { + font-style: italic; +} + +.hljs-tag, +.ini .hljs-title { + color: #62c8f3; +} + +.hljs-id, +.hljs-cbracket, +.hljs-tag .hljs-value { + color: #ade5fc; +} + +.hljs-string, +.hljs-bullet { + color: #a2fca2; +} + +.hljs-type, +.hljs-variable, +.hljs-name, +.actionscript .hljs-title, +.aspectj .hljs-annotation, +.aspectj .hljs-title, +.hljs-attribute, +.hljs-change, +.hljs-blockquote, +.hljs-built_in { + color: #ffa; +} + +.hljs-number, +.hljs-hexcolor, +.hljs-link_label, +.hljs-link_reference { + color: #d36363; +} + +.hljs-keyword, +.hljs-literal, +.hljs-constant, +.css .hljs-tag, +.hljs-typename, +.hljs-winutils { + color: #fcc28c; +} + +.hljs-comment, +.hljs-cdata, +.hljs-preprocessor, +.hljs-annotation, +.hljs-decorator, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula, +.hljs-header, +.hljs-horizontal_rule, +.hljs-code { + color: #888; +} + +.hljs-regexp, +.hljs-attr_selector { + color: #c6b4f0; +} + +.hljs-important, +.hljs-doctype, +.hljs-pi, +.hljs-chunk, +.actionscript .hljs-type, +.hljs-shebang, +.hljs-pragma, +.http .hljs-attribute { + color: #fc9b9b; +} + +.hljs-deletion { + background-color: #fc9b9b; + color: #333; +} + +.hljs-addition { + background-color: #a2fca2; + color: #333; +} + +.hljs a, +.hljs-tag .hljs-attribute { + color: inherit; +} + +.hljs a:focus, +.hljs a:hover { + color: inherit; + text-decoration: underline; +} diff --git a/static/highlight_styles/androidstudio.css b/static/highlight_styles/androidstudio.css new file mode 100644 index 0000000..603ddd9 --- /dev/null +++ b/static/highlight_styles/androidstudio.css @@ -0,0 +1,53 @@ +/* +Date: 24 Fev 2015 +Author: Pedro Oliveira +*/ + +.hljs { + color: #a9b7c6; + background: #282b2e; + display: block; + overflow-x: auto; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.hljs-number { + color: #6897BB; +} + +.hljs-keyword, +.hljs-deletion { + color: #cc7832; +} + +.hljs-comment { + color: #808080; +} + +.hljs-annotation { + color: #bbb529; +} + +.hljs-string, +.hljs-addition { + color: #6A8759; +} + +.hljs-function .hljs-title, +.hljs-change { + color: #ffc66d; +} + +.hljs-tag .hljs-title, +.hljs-doctype { + color: #e8bf6a; +} + +.hljs-tag .hljs-attribute { + color: #bababa; +} + +.hljs-tag .hljs-value { + color: #a5c261; +} diff --git a/static/highlight_styles/arta.css b/static/highlight_styles/arta.css new file mode 100644 index 0000000..0a336f6 --- /dev/null +++ b/static/highlight_styles/arta.css @@ -0,0 +1,138 @@ +/* +Date: 17.V.2011 +Author: pumbur +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #222; + -webkit-text-size-adjust: none; +} + +.profile .hljs-header *, +.ini .hljs-title, +.nginx .hljs-title { + color: #fff; +} + +.hljs-comment, +.hljs-preprocessor, +.hljs-preprocessor .hljs-title, +.hljs-pragma, +.hljs-shebang, +.profile .hljs-summary, +.diff, +.hljs-pi, +.hljs-doctype, +.hljs-tag, +.css .hljs-rule, +.tex .hljs-special { + color: #444; +} + +.hljs-string, +.hljs-symbol, +.diff .hljs-change, +.hljs-regexp, +.xml .hljs-attribute, +.smalltalk .hljs-char, +.xml .hljs-value, +.ini .hljs-value, +.clojure .hljs-attribute, +.coffeescript .hljs-attribute { + color: #ffcc33; +} + +.hljs-number, +.hljs-addition { + color: #00cc66; +} + +.hljs-built_in, +.hljs-literal, +.hljs-type, +.hljs-typename, +.go .hljs-constant, +.ini .hljs-keyword, +.lua .hljs-title, +.perl .hljs-variable, +.php .hljs-variable, +.mel .hljs-variable, +.django .hljs-variable, +.css .funtion, +.smalltalk .method, +.hljs-hexcolor, +.hljs-important, +.hljs-flow, +.hljs-inheritance, +.hljs-name, +.parser3 .hljs-variable { + color: #32aaee; +} + +.hljs-keyword, +.hljs-tag .hljs-title, +.css .hljs-tag, +.css .hljs-class, +.css .hljs-id, +.css .hljs-pseudo, +.css .hljs-attr_selector, +.hljs-winutils, +.tex .hljs-command, +.hljs-request, +.hljs-status { + color: #6644aa; +} + +.hljs-title, +.ruby .hljs-constant, +.vala .hljs-constant, +.hljs-parent, +.hljs-deletion, +.hljs-template_tag, +.css .hljs-keyword, +.objectivec .hljs-class .hljs-id, +.smalltalk .hljs-class, +.lisp .hljs-keyword, +.apache .hljs-tag, +.nginx .hljs-variable, +.hljs-envvar, +.bash .hljs-variable, +.go .hljs-built_in, +.vbscript .hljs-built_in, +.lua .hljs-built_in, +.rsl .hljs-built_in, +.tail, +.avrasm .hljs-label, +.tex .hljs-formula, +.tex .hljs-formula * { + color: #bb1166; +} + +.hljs-doctag, +.profile .hljs-header, +.ini .hljs-title, +.apache .hljs-tag, +.parser3 .hljs-title { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.6; +} + +.hljs, +.hljs-subst, +.diff .hljs-chunk, +.css .hljs-value, +.css .hljs-attribute { + color: #aaa; +} diff --git a/static/highlight_styles/ascetic.css b/static/highlight_styles/ascetic.css new file mode 100644 index 0000000..4f19afe --- /dev/null +++ b/static/highlight_styles/ascetic.css @@ -0,0 +1,52 @@ +/* + +Original style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; + -webkit-text-size-adjust: none; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-filter .hljs-argument, +.hljs-addition, +.hljs-change, +.hljs-name, +.apache .hljs-tag, +.apache .hljs-cbracket, +.nginx .hljs-built_in, +.tex .hljs-formula { + color: #888; +} + +.hljs-comment, +.hljs-shebang, +.hljs-doctype, +.hljs-pi, +.hljs-deletion, +.apache .hljs-sqbracket { + color: #ccc; +} + +.hljs-keyword, +.hljs-tag .hljs-title, +.ini .hljs-title, +.lisp .hljs-title, +.http .hljs-title, +.nginx .hljs-title, +.css .hljs-tag, +.hljs-winutils, +.hljs-flow, +.apache .hljs-tag, +.tex .hljs-command, +.hljs-request, +.hljs-status { + font-weight: bold; +} diff --git a/static/highlight_styles/atelier-cave.dark.css b/static/highlight_styles/atelier-cave.dark.css new file mode 100644 index 0000000..f907bee --- /dev/null +++ b/static/highlight_styles/atelier-cave.dark.css @@ -0,0 +1,113 @@ +/* Base16 Atelier Cave Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Cave Comment */ +.hljs-comment { + color: #7e7887; +} + +/* Atelier-Cave Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #be4678; +} + +/* Atelier-Cave Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #aa573c; +} + +/* Atelier-Cave Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #a06e3b; +} + +/* Atelier-Cave Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #2a9292; +} + +/* Atelier-Cave Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #398bc6; +} + +/* Atelier-Cave Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #576ddb; +} + +/* Atelier-Cave Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #955ae7; +} + +.diff .hljs-deletion, +.diff .hljs-addition { + color: #19171c; + display: inline-block; + width: 100%; +} + +.diff .hljs-deletion { + background-color: #be4678; +} + +.diff .hljs-addition { + background-color: #2a9292; +} + +.diff .hljs-change { + color: #576ddb; +} + +.hljs { + display: block; + overflow-x: auto; + background: #19171c; + color: #8b8792; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-cave.light.css b/static/highlight_styles/atelier-cave.light.css new file mode 100644 index 0000000..5df8b4c --- /dev/null +++ b/static/highlight_styles/atelier-cave.light.css @@ -0,0 +1,113 @@ +/* Base16 Atelier Cave Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Cave Comment */ +.hljs-comment { + color: #655f6d; +} + +/* Atelier-Cave Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #be4678; +} + +/* Atelier-Cave Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #aa573c; +} + +/* Atelier-Cave Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #a06e3b; +} + +/* Atelier-Cave Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #2a9292; +} + +/* Atelier-Cave Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #398bc6; +} + +/* Atelier-Cave Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #576ddb; +} + +/* Atelier-Cave Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #955ae7; +} + +.diff .hljs-deletion, +.diff .hljs-addition { + color: #19171c; + display: inline-block; + width: 100%; +} + +.diff .hljs-deletion { + background-color: #be4678; +} + +.diff .hljs-addition { + background-color: #2a9292; +} + +.diff .hljs-change { + color: #576ddb; +} + +.hljs { + display: block; + overflow-x: auto; + background: #efecf4; + color: #585260; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-dune.dark.css b/static/highlight_styles/atelier-dune.dark.css new file mode 100644 index 0000000..88ea51a --- /dev/null +++ b/static/highlight_styles/atelier-dune.dark.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Dune Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Dune Comment */ +.hljs-comment { + color: #999580; +} + +/* Atelier-Dune Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d73737; +} + +/* Atelier-Dune Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #b65611; +} + +/* Atelier-Dune Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #ae9513; +} + +/* Atelier-Dune Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #60ac39; +} + +/* Atelier-Dune Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #1fad83; +} + +/* Atelier-Dune Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #6684e1; +} + +/* Atelier-Dune Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b854d4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #20201d; + color: #a6a28c; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-dune.light.css b/static/highlight_styles/atelier-dune.light.css new file mode 100644 index 0000000..87ffd80 --- /dev/null +++ b/static/highlight_styles/atelier-dune.light.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Dune Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Dune Comment */ +.hljs-comment { + color: #7d7a68; +} + +/* Atelier-Dune Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d73737; +} + +/* Atelier-Dune Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #b65611; +} + +/* Atelier-Dune Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #ae9513; +} + +/* Atelier-Dune Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #60ac39; +} + +/* Atelier-Dune Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #1fad83; +} + +/* Atelier-Dune Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #6684e1; +} + +/* Atelier-Dune Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b854d4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #fefbec; + color: #6e6b5e; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-estuary.dark.css b/static/highlight_styles/atelier-estuary.dark.css new file mode 100644 index 0000000..65d0f7f --- /dev/null +++ b/static/highlight_styles/atelier-estuary.dark.css @@ -0,0 +1,113 @@ +/* Base16 Atelier Estuary Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/estuary) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Estuary Comment */ +.hljs-comment { + color: #878573; +} + +/* Atelier-Estuary Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ba6236; +} + +/* Atelier-Estuary Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #ae7313; +} + +/* Atelier-Estuary Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #a5980d; +} + +/* Atelier-Estuary Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #7d9726; +} + +/* Atelier-Estuary Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #5b9d48; +} + +/* Atelier-Estuary Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #36a166; +} + +/* Atelier-Estuary Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #5f9182; +} + +.diff .hljs-deletion, +.diff .hljs-addition { + color: #22221b; + display: inline-block; + width: 100%; +} + +.diff .hljs-deletion { + background-color: #ba6236; +} + +.diff .hljs-addition { + background-color: #7d9726; +} + +.diff .hljs-change { + color: #36a166; +} + +.hljs { + display: block; + overflow-x: auto; + background: #22221b; + color: #929181; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-estuary.light.css b/static/highlight_styles/atelier-estuary.light.css new file mode 100644 index 0000000..d2f70c1 --- /dev/null +++ b/static/highlight_styles/atelier-estuary.light.css @@ -0,0 +1,113 @@ +/* Base16 Atelier Estuary Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/estuary) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Estuary Comment */ +.hljs-comment { + color: #6c6b5a; +} + +/* Atelier-Estuary Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ba6236; +} + +/* Atelier-Estuary Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #ae7313; +} + +/* Atelier-Estuary Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #a5980d; +} + +/* Atelier-Estuary Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #7d9726; +} + +/* Atelier-Estuary Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #5b9d48; +} + +/* Atelier-Estuary Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #36a166; +} + +/* Atelier-Estuary Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #5f9182; +} + +.diff .hljs-deletion, +.diff .hljs-addition { + color: #22221b; + display: inline-block; + width: 100%; +} + +.diff .hljs-deletion { + background-color: #ba6236; +} + +.diff .hljs-addition { + background-color: #7d9726; +} + +.diff .hljs-change { + color: #36a166; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f4f3ec; + color: #5f5e4e; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-forest.dark.css b/static/highlight_styles/atelier-forest.dark.css new file mode 100644 index 0000000..0e4294b --- /dev/null +++ b/static/highlight_styles/atelier-forest.dark.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Forest Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Forest Comment */ +.hljs-comment { + color: #9c9491; +} + +/* Atelier-Forest Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #f22c40; +} + +/* Atelier-Forest Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #df5320; +} + +/* Atelier-Forest Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #c38418; +} + +/* Atelier-Forest Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #7b9726; +} + +/* Atelier-Forest Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #3d97b8; +} + +/* Atelier-Forest Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #407ee7; +} + +/* Atelier-Forest Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #6666ea; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1b1918; + color: #a8a19f; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-forest.light.css b/static/highlight_styles/atelier-forest.light.css new file mode 100644 index 0000000..e08e3dc --- /dev/null +++ b/static/highlight_styles/atelier-forest.light.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Forest Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Forest Comment */ +.hljs-comment { + color: #766e6b; +} + +/* Atelier-Forest Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #f22c40; +} + +/* Atelier-Forest Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #df5320; +} + +/* Atelier-Forest Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #c38418; +} + +/* Atelier-Forest Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #7b9726; +} + +/* Atelier-Forest Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #3d97b8; +} + +/* Atelier-Forest Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #407ee7; +} + +/* Atelier-Forest Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #6666ea; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f1efee; + color: #68615e; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-heath.dark.css b/static/highlight_styles/atelier-heath.dark.css new file mode 100644 index 0000000..a063feb --- /dev/null +++ b/static/highlight_styles/atelier-heath.dark.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Heath Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Heath Comment */ +.hljs-comment { + color: #9e8f9e; +} + +/* Atelier-Heath Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ca402b; +} + +/* Atelier-Heath Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #a65926; +} + +/* Atelier-Heath Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #bb8a35; +} + +/* Atelier-Heath Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #918b3b; +} + +/* Atelier-Heath Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #159393; +} + +/* Atelier-Heath Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #516aec; +} + +/* Atelier-Heath Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #7b59c0; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1b181b; + color: #ab9bab; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-heath.light.css b/static/highlight_styles/atelier-heath.light.css new file mode 100644 index 0000000..6c56d04 --- /dev/null +++ b/static/highlight_styles/atelier-heath.light.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Heath Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Heath Comment */ +.hljs-comment { + color: #776977; +} + +/* Atelier-Heath Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ca402b; +} + +/* Atelier-Heath Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #a65926; +} + +/* Atelier-Heath Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #bb8a35; +} + +/* Atelier-Heath Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #918b3b; +} + +/* Atelier-Heath Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #159393; +} + +/* Atelier-Heath Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #516aec; +} + +/* Atelier-Heath Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #7b59c0; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f7f3f7; + color: #695d69; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-lakeside.dark.css b/static/highlight_styles/atelier-lakeside.dark.css new file mode 100644 index 0000000..852f72e --- /dev/null +++ b/static/highlight_styles/atelier-lakeside.dark.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Lakeside Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Lakeside Comment */ +.hljs-comment { + color: #7195a8; +} + +/* Atelier-Lakeside Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d22d72; +} + +/* Atelier-Lakeside Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #935c25; +} + +/* Atelier-Lakeside Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #8a8a0f; +} + +/* Atelier-Lakeside Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #568c3b; +} + +/* Atelier-Lakeside Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #2d8f6f; +} + +/* Atelier-Lakeside Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #257fad; +} + +/* Atelier-Lakeside Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #6b6bb8; +} + +.hljs { + display: block; + overflow-x: auto; + background: #161b1d; + color: #7ea2b4; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-lakeside.light.css b/static/highlight_styles/atelier-lakeside.light.css new file mode 100644 index 0000000..06f208c --- /dev/null +++ b/static/highlight_styles/atelier-lakeside.light.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Lakeside Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Lakeside Comment */ +.hljs-comment { + color: #5a7b8c; +} + +/* Atelier-Lakeside Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d22d72; +} + +/* Atelier-Lakeside Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #935c25; +} + +/* Atelier-Lakeside Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #8a8a0f; +} + +/* Atelier-Lakeside Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #568c3b; +} + +/* Atelier-Lakeside Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #2d8f6f; +} + +/* Atelier-Lakeside Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #257fad; +} + +/* Atelier-Lakeside Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #6b6bb8; +} + +.hljs { + display: block; + overflow-x: auto; + background: #ebf8ff; + color: #516d7b; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-plateau.dark.css b/static/highlight_styles/atelier-plateau.dark.css new file mode 100644 index 0000000..10b8f8e --- /dev/null +++ b/static/highlight_styles/atelier-plateau.dark.css @@ -0,0 +1,113 @@ +/* Base16 Atelier Plateau Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/plateau) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Plateau Comment */ +.hljs-comment { + color: #7e7777; +} + +/* Atelier-Plateau Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ca4949; +} + +/* Atelier-Plateau Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #b45a3c; +} + +/* Atelier-Plateau Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #a06e3b; +} + +/* Atelier-Plateau Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #4b8b8b; +} + +/* Atelier-Plateau Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #5485b6; +} + +/* Atelier-Plateau Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #7272ca; +} + +/* Atelier-Plateau Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #8464c4; +} + +.diff .hljs-deletion, +.diff .hljs-addition { + color: #1b1818; + display: inline-block; + width: 100%; +} + +.diff .hljs-deletion { + background-color: #ca4949; +} + +.diff .hljs-addition { + background-color: #4b8b8b; +} + +.diff .hljs-change { + color: #7272ca; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1b1818; + color: #8a8585; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-plateau.light.css b/static/highlight_styles/atelier-plateau.light.css new file mode 100644 index 0000000..1c19cf6 --- /dev/null +++ b/static/highlight_styles/atelier-plateau.light.css @@ -0,0 +1,113 @@ +/* Base16 Atelier Plateau Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/plateau) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Plateau Comment */ +.hljs-comment { + color: #655d5d; +} + +/* Atelier-Plateau Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ca4949; +} + +/* Atelier-Plateau Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #b45a3c; +} + +/* Atelier-Plateau Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #a06e3b; +} + +/* Atelier-Plateau Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #4b8b8b; +} + +/* Atelier-Plateau Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #5485b6; +} + +/* Atelier-Plateau Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #7272ca; +} + +/* Atelier-Plateau Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #8464c4; +} + +.diff .hljs-deletion, +.diff .hljs-addition { + color: #1b1818; + display: inline-block; + width: 100%; +} + +.diff .hljs-deletion { + background-color: #ca4949; +} + +.diff .hljs-addition { + background-color: #4b8b8b; +} + +.diff .hljs-change { + color: #7272ca; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f4ecec; + color: #585050; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-savanna.dark.css b/static/highlight_styles/atelier-savanna.dark.css new file mode 100644 index 0000000..444e2de --- /dev/null +++ b/static/highlight_styles/atelier-savanna.dark.css @@ -0,0 +1,113 @@ +/* Base16 Atelier Savanna Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/savanna) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Savanna Comment */ +.hljs-comment { + color: #78877d; +} + +/* Atelier-Savanna Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #b16139; +} + +/* Atelier-Savanna Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #9f713c; +} + +/* Atelier-Savanna Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #a07e3b; +} + +/* Atelier-Savanna Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #489963; +} + +/* Atelier-Savanna Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #1c9aa0; +} + +/* Atelier-Savanna Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #478c90; +} + +/* Atelier-Savanna Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #55859b; +} + +.diff .hljs-deletion, +.diff .hljs-addition { + color: #171c19; + display: inline-block; + width: 100%; +} + +.diff .hljs-deletion { + background-color: #b16139; +} + +.diff .hljs-addition { + background-color: #489963; +} + +.diff .hljs-change { + color: #478c90; +} + +.hljs { + display: block; + overflow-x: auto; + background: #171c19; + color: #87928a; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-savanna.light.css b/static/highlight_styles/atelier-savanna.light.css new file mode 100644 index 0000000..5eeb294 --- /dev/null +++ b/static/highlight_styles/atelier-savanna.light.css @@ -0,0 +1,113 @@ +/* Base16 Atelier Savanna Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/savanna) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Savanna Comment */ +.hljs-comment { + color: #5f6d64; +} + +/* Atelier-Savanna Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #b16139; +} + +/* Atelier-Savanna Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #9f713c; +} + +/* Atelier-Savanna Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #a07e3b; +} + +/* Atelier-Savanna Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #489963; +} + +/* Atelier-Savanna Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #1c9aa0; +} + +/* Atelier-Savanna Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #478c90; +} + +/* Atelier-Savanna Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #55859b; +} + +.diff .hljs-deletion, +.diff .hljs-addition { + color: #171c19; + display: inline-block; + width: 100%; +} + +.diff .hljs-deletion { + background-color: #b16139; +} + +.diff .hljs-addition { + background-color: #489963; +} + +.diff .hljs-change { + color: #478c90; +} + +.hljs { + display: block; + overflow-x: auto; + background: #ecf4ee; + color: #526057; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-seaside.dark.css b/static/highlight_styles/atelier-seaside.dark.css new file mode 100644 index 0000000..0bcfd1b --- /dev/null +++ b/static/highlight_styles/atelier-seaside.dark.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Seaside Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Seaside Comment */ +.hljs-comment { + color: #809980; +} + +/* Atelier-Seaside Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #e6193c; +} + +/* Atelier-Seaside Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #87711d; +} + +/* Atelier-Seaside Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #98981b; +} + +/* Atelier-Seaside Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #29a329; +} + +/* Atelier-Seaside Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #1999b3; +} + +/* Atelier-Seaside Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #3d62f5; +} + +/* Atelier-Seaside Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #ad2bee; +} + +.hljs { + display: block; + overflow-x: auto; + background: #131513; + color: #8ca68c; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-seaside.light.css b/static/highlight_styles/atelier-seaside.light.css new file mode 100644 index 0000000..6023d4b --- /dev/null +++ b/static/highlight_styles/atelier-seaside.light.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Seaside Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Seaside Comment */ +.hljs-comment { + color: #687d68; +} + +/* Atelier-Seaside Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #e6193c; +} + +/* Atelier-Seaside Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #87711d; +} + +/* Atelier-Seaside Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #98981b; +} + +/* Atelier-Seaside Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #29a329; +} + +/* Atelier-Seaside Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #1999b3; +} + +/* Atelier-Seaside Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #3d62f5; +} + +/* Atelier-Seaside Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #ad2bee; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f4fbf4; + color: #5e6e5e; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-sulphurpool.dark.css b/static/highlight_styles/atelier-sulphurpool.dark.css new file mode 100644 index 0000000..dda8af3 --- /dev/null +++ b/static/highlight_styles/atelier-sulphurpool.dark.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Sulphurpool Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Sulphurpool Comment */ +.hljs-comment { + color: #898ea4; +} + +/* Atelier-Sulphurpool Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #c94922; +} + +/* Atelier-Sulphurpool Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #c76b29; +} + +/* Atelier-Sulphurpool Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #c08b30; +} + +/* Atelier-Sulphurpool Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #ac9739; +} + +/* Atelier-Sulphurpool Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #22a2c9; +} + +/* Atelier-Sulphurpool Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #3d8fd1; +} + +/* Atelier-Sulphurpool Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #6679cc; +} + +.hljs { + display: block; + overflow-x: auto; + background: #202746; + color: #979db4; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/atelier-sulphurpool.light.css b/static/highlight_styles/atelier-sulphurpool.light.css new file mode 100644 index 0000000..2149b4c --- /dev/null +++ b/static/highlight_styles/atelier-sulphurpool.light.css @@ -0,0 +1,94 @@ +/* Base16 Atelier Sulphurpool Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Sulphurpool Comment */ +.hljs-comment { + color: #6b7394; +} + +/* Atelier-Sulphurpool Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #c94922; +} + +/* Atelier-Sulphurpool Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #c76b29; +} + +/* Atelier-Sulphurpool Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #c08b30; +} + +/* Atelier-Sulphurpool Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #ac9739; +} + +/* Atelier-Sulphurpool Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #22a2c9; +} + +/* Atelier-Sulphurpool Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #3d8fd1; +} + +/* Atelier-Sulphurpool Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #6679cc; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f5f7ff; + color: #5e6687; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/brown_paper.css b/static/highlight_styles/brown_paper.css new file mode 100644 index 0000000..f694a0a --- /dev/null +++ b/static/highlight_styles/brown_paper.css @@ -0,0 +1,103 @@ +/* + +Brown Paper style from goldblog.com.ua (c) Zaripov Yura + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background:#b7a68e url(./brown_papersq.png); + -webkit-text-size-adjust: none; +} + +.hljs-keyword, +.hljs-literal, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.nginx .hljs-title, +.tex .hljs-special, +.hljs-request, +.hljs-status { + color:#005599; + font-weight:bold; +} + +.hljs, +.hljs-subst, +.hljs-tag .hljs-keyword { + color: #363c69; +} + +.hljs-string, +.hljs-title, +.hljs-type, +.hljs-tag .hljs-value, +.css .hljs-rule .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.ruby .hljs-string, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-number, +.hljs-name { + color: #2c009f; +} + +.hljs-comment, +.hljs-annotation, +.hljs-decorator, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-formula { + color: #802022; +} + +.hljs-keyword, +.hljs-literal, +.css .hljs-id, +.hljs-doctag, +.hljs-title, +.hljs-type, +.vbscript .hljs-built_in, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-command { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.8; +} diff --git a/static/highlight_styles/brown_papersq.png b/static/highlight_styles/brown_papersq.png new file mode 100644 index 0000000..3813903 Binary files /dev/null and b/static/highlight_styles/brown_papersq.png differ diff --git a/static/highlight_styles/codepen-embed.css b/static/highlight_styles/codepen-embed.css new file mode 100644 index 0000000..48cb69a --- /dev/null +++ b/static/highlight_styles/codepen-embed.css @@ -0,0 +1,97 @@ +/* + codepen.io Embed Theme + Author: Justin Perry + Original theme - https://github.com/chriskempson/tomorrow-theme +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #222; + color: #fff; + font-family: Menlo, Monaco, 'Andale Mono', 'Lucida Console', 'Courier New', monospace; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.hljs-title { + color: #777; +} + +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .constant, +.xml .tag .title, +.xml .pi, +.xml .doctype, +.html .doctype { + color: #ab875d; +} + +.css .value { + color: #cd6a51; +} + +.css .value .function, +.css .value .string { + color: #a67f59; +} + +.css .value .number { + color: #9b869c; +} + +.css .id, +.css .class, +.css-pseudo, +.css .selector, +.css .tag { + color: #dfc48c; +} + +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #ab875d; +} + +.ruby .class .title, +.css .rules .attribute { + color: #9b869b; +} + +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .symbol, +.xml .cdata { + color: #8f9c6c; +} + +.css .hexcolor { + color: #cd6a51; +} + +.function, +.python .decorator, +.python .title, +.ruby .function .title, +.ruby .title .keyword, +.perl .sub, +.javascript .title, +.coffeescript .title { + color: #fff; +} + +.hljs-keyword, +.javascript .function { + color: #8f9c6c; +} diff --git a/static/highlight_styles/color-brewer.css b/static/highlight_styles/color-brewer.css new file mode 100644 index 0000000..6be4738 --- /dev/null +++ b/static/highlight_styles/color-brewer.css @@ -0,0 +1,165 @@ +/* + +Colorbrewer theme +Original: https://github.com/mbostock/colorbrewer-theme (c) Mike Bostock +Ported by Fabrício Tavares de Oliveira + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fff; + -webkit-text-size-adjust: none; +} + +.hljs, +.hljs-subst, +.hljs-tag .hljs-title, +.nginx .hljs-title { + color: #000; +} + +.hljs-string, +.hljs-title, +.hljs-constant, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rule .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.haml .hljs-symbol, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-addition, +.hljs-flow, +.hljs-stream, +.bash .hljs-variable, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.tex .hljs-special, +.erlang_repl .hljs-function_or_atom, +.asciidoc .hljs-header, +.markdown .hljs-header, +.coffeescript .hljs-attribute, +.hljs-name { + color: #756bb1; +} + +.smartquote, +.hljs-comment, +.hljs-annotation, +.diff .hljs-header, +.hljs-chunk, +.asciidoc .hljs-blockquote, +.markdown .hljs-blockquote { + color: #636363; +} + +.hljs-number, +.hljs-date, +.hljs-regexp, +.hljs-literal, +.hljs-hexcolor, +.smalltalk .hljs-symbol, +.smalltalk .hljs-char, +.go .hljs-constant, +.hljs-change, +.lasso .hljs-variable, +.makefile .hljs-variable, +.asciidoc .hljs-bullet, +.markdown .hljs-bullet, +.asciidoc .hljs-link_url, +.markdown .hljs-link_url { + color: #31a354; +} + +.hljs-label, +.ruby .hljs-string, +.hljs-decorator, +.hljs-filter .hljs-argument, +.hljs-localvars, +.hljs-array, +.hljs-attr_selector, +.hljs-important, +.hljs-pseudo, +.hljs-pi, +.haml .hljs-bullet, +.hljs-doctype, +.hljs-deletion, +.hljs-envvar, +.hljs-shebang, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.hljs-list .hljs-built_in, +.tex .hljs-formula, +.erlang_repl .hljs-reserved, +.hljs-prompt, +.asciidoc .hljs-link_label, +.markdown .hljs-link_label, +.vhdl .hljs-attribute, +.clojure .hljs-attribute, +.asciidoc .hljs-attribute, +.lasso .hljs-attribute, +.coffeescript .hljs-property, +.hljs-phony { + color: #88f; +} + + + +.hljs-keyword, +.hljs-id, +.hljs-title, +.hljs-built_in, +.css .hljs-tag, +.hljs-doctag, +.smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.hljs-type, +.hljs-typename, +.tex .hljs-command, +.asciidoc .hljs-strong, +.markdown .hljs-strong, +.hljs-request, +.hljs-status { + color: #3182bd; +} + +.asciidoc .hljs-emphasis, +.markdown .hljs-emphasis { + font-style: italic; +} + +.nginx .hljs-built_in { + font-weight: normal; +} + +.coffeescript .javascript, +.javascript .xml, +.lasso .markup, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} + +.css .hljs-attribute, +.html .hljs-attribute { + color: #e6550d; +} + +.css .hljs-class, +.html .hljs-tag, +.html .hljs-title { + color: #3182bd; +} diff --git a/static/highlight_styles/dark.css b/static/highlight_styles/dark.css new file mode 100644 index 0000000..be3c8d7 --- /dev/null +++ b/static/highlight_styles/dark.css @@ -0,0 +1,103 @@ +/* + +Dark style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #444; + -webkit-text-size-adjust: none; +} + +.hljs-keyword, +.hljs-literal, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.nginx .hljs-title, +.tex .hljs-special { + color: white; +} + +.hljs, +.hljs-subst { + color: #ddd; +} + +.hljs-string, +.hljs-title, +.hljs-type, +.ini .hljs-title, +.hljs-tag .hljs-value, +.css .hljs-rule .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.ruby .hljs-string, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.hljs-prompt, +.coffeescript .hljs-attribute, +.hljs-name { + color: #d88; +} + +.hljs-comment, +.hljs-annotation, +.hljs-decorator, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #777; +} + +.hljs-keyword, +.hljs-literal, +.hljs-title, +.css .hljs-id, +.hljs-doctag, +.hljs-type, +.vbscript .hljs-built_in, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-special, +.hljs-request, +.hljs-status { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/darkula.css b/static/highlight_styles/darkula.css new file mode 100644 index 0000000..1c6853b --- /dev/null +++ b/static/highlight_styles/darkula.css @@ -0,0 +1,152 @@ +/* + +Darkula color scheme from the JetBrains family of IDEs + +*/ + + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #2b2b2b; + -webkit-text-size-adjust: none; +} + +.hljs, +.hljs-tag, +.hljs-title, +.css .hljs-rule, +.css .hljs-value, +.aspectj .hljs-function, +.css .hljs-function .hljs-preprocessor, +.hljs-pragma { + color: #bababa; +} + +.hljs-strongemphasis, +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-blockquote, +.hljs-horizontal_rule, +.hljs-number, +.hljs-regexp, +.alias .hljs-keyword, +.hljs-literal, +.hljs-hexcolor { + color: #6896ba; +} + +.hljs-tag .hljs-value, +.hljs-code, +.css .hljs-class, +.hljs-class .hljs-title:last-child { + color: #a6e22e; +} + +.hljs-link_url { + font-size: 80%; +} + +.hljs-emphasis, +.hljs-strongemphasis, +.hljs-class .hljs-title:last-child, +.hljs-typename { + font-style: italic; +} + +.hljs-keyword, +.ruby .hljs-class .hljs-keyword:first-child, +.ruby .hljs-function .hljs-keyword, +.hljs-function, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.nginx .hljs-title, +.tex .hljs-special, +.hljs-header, +.hljs-attribute, +.hljs-symbol, +.hljs-symbol .hljs-string, +.hljs-tag .hljs-title, +.hljs-value, +.alias .hljs-keyword:first-child, +.css .hljs-tag, +.css .unit, +.css .hljs-important { + color: #cb7832; +} + +.hljs-function .hljs-keyword, +.hljs-class .hljs-keyword:first-child, +.hljs-aspect .hljs-keyword:first-child, +.hljs-constant, +.hljs-typename, +.css .hljs-attribute { + color: #cb7832; +} + +.hljs-variable, +.hljs-params, +.hljs-class .hljs-title, +.hljs-aspect .hljs-title { + color: #b9b9b9; +} + +.hljs-string, +.css .hljs-id, +.hljs-subst, +.hljs-type, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.hljs-prompt, +.hljs-link_label, +.hljs-link_url, +.hljs-name { + color: #e0c46c; +} + +.hljs-comment, +.hljs-annotation, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #7f7f7f; +} + +.hljs-decorator { + color: #bab429; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata, +.xml .php, +.php .xml { + opacity: 0.5; +} diff --git a/static/highlight_styles/default.css b/static/highlight_styles/default.css new file mode 100644 index 0000000..b0ac8b8 --- /dev/null +++ b/static/highlight_styles/default.css @@ -0,0 +1,155 @@ +/* + +Original style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #f0f0f0; + -webkit-text-size-adjust: none; +} + +.hljs, +.hljs-subst, +.hljs-tag .hljs-title, +.nginx .hljs-title { + color: black; +} + +.hljs-string, +.hljs-title, +.hljs-constant, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rule .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.hljs-name, +.haml .hljs-symbol, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-addition, +.hljs-flow, +.hljs-stream, +.bash .hljs-variable, +.pf .hljs-variable, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.tex .hljs-special, +.erlang_repl .hljs-function_or_atom, +.asciidoc .hljs-header, +.markdown .hljs-header, +.coffeescript .hljs-attribute, +.tp .hljs-variable { + color: #800; +} + +.smartquote, +.hljs-comment, +.hljs-annotation, +.diff .hljs-header, +.hljs-chunk, +.asciidoc .hljs-blockquote, +.markdown .hljs-blockquote { + color: #888; +} + +.hljs-number, +.hljs-date, +.hljs-regexp, +.hljs-literal, +.hljs-hexcolor, +.smalltalk .hljs-symbol, +.smalltalk .hljs-char, +.go .hljs-constant, +.hljs-change, +.lasso .hljs-variable, +.makefile .hljs-variable, +.asciidoc .hljs-bullet, +.markdown .hljs-bullet, +.asciidoc .hljs-link_url, +.markdown .hljs-link_url { + color: #080; +} + +.hljs-label, +.ruby .hljs-string, +.hljs-decorator, +.hljs-filter .hljs-argument, +.hljs-localvars, +.hljs-array, +.hljs-attr_selector, +.hljs-important, +.hljs-pseudo, +.hljs-pi, +.haml .hljs-bullet, +.hljs-doctype, +.hljs-deletion, +.hljs-envvar, +.hljs-shebang, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-formula, +.erlang_repl .hljs-reserved, +.hljs-prompt, +.asciidoc .hljs-link_label, +.markdown .hljs-link_label, +.vhdl .hljs-attribute, +.clojure .hljs-attribute, +.asciidoc .hljs-attribute, +.lasso .hljs-attribute, +.coffeescript .hljs-property, +.hljs-phony { + color: #88f; +} + +.hljs-keyword, +.hljs-id, +.hljs-title, +.hljs-built_in, +.css .hljs-tag, +.hljs-doctag, +.smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.pf .hljs-variable, +.apache .hljs-tag, +.hljs-type, +.hljs-typename, +.tex .hljs-command, +.asciidoc .hljs-strong, +.markdown .hljs-strong, +.hljs-request, +.hljs-status, +.tp .hljs-data, +.tp .hljs-io { + font-weight: bold; +} + +.asciidoc .hljs-emphasis, +.markdown .hljs-emphasis, +.tp .hljs-units { + font-style: italic; +} + +.nginx .hljs-built_in { + font-weight: normal; +} + +.coffeescript .javascript, +.javascript .xml, +.lasso .markup, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/docco.css b/static/highlight_styles/docco.css new file mode 100644 index 0000000..0f11123 --- /dev/null +++ b/static/highlight_styles/docco.css @@ -0,0 +1,134 @@ +/* +Docco style used in http://jashkenas.github.com/docco/ converted by Simon Madine (@thingsinjars) +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #000; + background: #f8f8ff; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.diff .hljs-header { + color: #408080; + font-style: italic; +} + +.hljs-keyword, +.assignment, +.hljs-literal, +.css .rule .hljs-keyword, +.hljs-winutils, +.javascript .hljs-title, +.lisp .hljs-title, +.hljs-subst { + color: #954121; +} + +.hljs-number, +.hljs-hexcolor { + color: #40a070; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-doctag, +.tex .hljs-formula, +.hljs-name { + color: #219161; +} + +.hljs-title, +.hljs-id { + color: #19469d; +} +.hljs-params { + color: #00f; +} + +.javascript .hljs-title, +.lisp .hljs-title, +.hljs-subst { + font-weight: normal; +} + +.hljs-class .hljs-title, +.haskell .hljs-label, +.tex .hljs-command { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-rule .hljs-property, +.django .hljs-tag .hljs-keyword { + color: #000080; + font-weight: normal; +} + +.hljs-attribute, +.hljs-variable, +.instancevar, +.lisp .hljs-body { + color: #008080; +} + +.hljs-regexp { + color: #b68; +} + +.hljs-class { + color: #458; + font-weight: bold; +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-symbol .hljs-keyword, +.ruby .hljs-symbol .keymethods, +.lisp .hljs-keyword, +.tex .hljs-special, +.input_number { + color: #990073; +} + +.builtin, +.constructor, +.hljs-built_in, +.lisp .hljs-title { + color: #0086b3; +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-doctype, +.hljs-shebang, +.hljs-cdata { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.diff .hljs-change { + background: #0086b3; +} + +.hljs-chunk { + color: #aaa; +} + +.tex .hljs-formula { + opacity: 0.5; +} diff --git a/static/highlight_styles/far.css b/static/highlight_styles/far.css new file mode 100644 index 0000000..34ed6f5 --- /dev/null +++ b/static/highlight_styles/far.css @@ -0,0 +1,110 @@ +/* + +FAR Style (c) MajestiC + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000080; + -webkit-text-size-adjust: none; +} + +.hljs, +.hljs-subst { + color: #0ff; +} + +.hljs-string, +.ruby .hljs-string, +.haskell .hljs-type, +.hljs-tag .hljs-value, +.hljs-rule .hljs-value, +.hljs-rule .hljs-value .hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-built_in, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-addition, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.coffeescript .hljs-attribute { + color: #ff0; +} + +.hljs-keyword, +.css .hljs-id, +.hljs-title, +.hljs-type, +.vbscript .hljs-built_in, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.xml .hljs-tag .hljs-title, +.hljs-winutils, +.hljs-flow, +.hljs-change, +.hljs-envvar, +.bash .hljs-variable, +.tex .hljs-special, +.hljs-name { + color: #fff; +} + +.hljs-comment, +.hljs-doctag, +.hljs-annotation, +.hljs-deletion, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #888; +} + +.hljs-number, +.hljs-date, +.hljs-regexp, +.hljs-literal, +.smalltalk .hljs-symbol, +.smalltalk .hljs-char, +.clojure .hljs-attribute { + color: #0f0; +} + +.hljs-decorator, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.xml .hljs-pi, +.diff .hljs-header, +.hljs-chunk, +.hljs-shebang, +.nginx .hljs-built_in, +.hljs-prompt { + color: #008080; +} + +.hljs-keyword, +.css .hljs-id, +.hljs-title, +.hljs-type, +.vbscript .hljs-built_in, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.hljs-winutils, +.hljs-flow, +.apache .hljs-tag, +.nginx .hljs-built_in, +.tex .hljs-command, +.tex .hljs-special, +.hljs-request, +.hljs-status { + font-weight: bold; +} diff --git a/static/highlight_styles/foundation.css b/static/highlight_styles/foundation.css new file mode 100644 index 0000000..09c2ff2 --- /dev/null +++ b/static/highlight_styles/foundation.css @@ -0,0 +1,135 @@ +/* +Description: Foundation 4 docs style for highlight.js +Author: Dan Allen +Website: http://foundation.zurb.com/docs/ +Version: 1.0 +Date: 2013-04-02 +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #eee; + -webkit-text-size-adjust: none; +} + +.hljs-header, +.hljs-decorator, +.hljs-annotation { + color: #000077; +} + +.hljs-horizontal_rule, +.hljs-link_url, +.hljs-emphasis, +.hljs-attribute { + color: #070; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-link_label, +.hljs-strong, +.hljs-value, +.hljs-string, +.scss .hljs-value .hljs-string { + color: #d14; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-blockquote, +.hljs-comment { + color: #998; + font-style: italic; +} + +.asciidoc .hljs-title, +.hljs-function .hljs-title { + color: #900; +} + +.hljs-class { + color: #458; +} + +.hljs-id, +.hljs-pseudo, +.hljs-constant, +.hljs-hexcolor { + color: teal; +} + +.hljs-variable { + color: #336699; +} + +.hljs-bullet { + color: #997700; +} + +.hljs-pi, +.hljs-doctype { + color: #3344bb; +} + +.hljs-code, +.hljs-number { + color: #099; +} + +.hljs-important { + color: #f00; +} + +.smartquote, +.hljs-label { + color: #970; +} + +.hljs-preprocessor, +.hljs-pragma { + color: #579; +} + +.hljs-reserved, +.hljs-keyword, +.scss .hljs-value { + color: #000; +} + +.hljs-regexp { + background-color: #fff0ff; + color: #880088; +} + +.hljs-symbol { + color: #990073; +} + +.hljs-symbol .hljs-string { + color: #a60; +} + +.hljs-tag { + color: #007700; +} + +.hljs-at_rule, +.hljs-at_rule .hljs-keyword { + color: #088; +} + +.hljs-at_rule .hljs-preprocessor { + color: #808; +} + +.scss .hljs-tag, +.scss .hljs-attribute { + color: #339; +} diff --git a/static/highlight_styles/github-gist.css b/static/highlight_styles/github-gist.css new file mode 100644 index 0000000..231301a --- /dev/null +++ b/static/highlight_styles/github-gist.css @@ -0,0 +1,211 @@ +/** + * GitHub Gist Theme + * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro + */ + +.hljs { + display: block; + background:white; + padding: 0.5em; + color: #333333; + overflow-x: auto; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.bash .hljs-shebang, +.java .hljs-javadoc, +.javascript .hljs-javadoc { + color: #969896; +} + +.hljs-string, +.apache .hljs-sqbracket, +.coffeescript .hljs-subst, +.coffeescript .hljs-regexp, +.cpp .hljs-preprocessor, +.c .hljs-preprocessor, +.javascript .hljs-regexp, +.json .hljs-attribute, +.makefile .hljs-variable, +.markdown .hljs-value, +.markdown .hljs-link_label, +.markdown .hljs-strong, +.markdown .hljs-emphasis, +.markdown .hljs-blockquote, +.nginx .hljs-regexp, +.nginx .hljs-number, +.objectivec .hljs-preprocessor .hljs-title, +.perl .hljs-regexp, +.php .hljs-regexp, +.xml .hljs-value, +.less .hljs-built_in, +.scss .hljs-built_in { + color: #df5000; +} + +.hljs-keyword, +.css .hljs-at_rule, +.css .hljs-important, +.http .hljs-request, +.ini .hljs-setting, +.java .hljs-javadoctag, +.javascript .hljs-tag, +.javascript .hljs-javadoctag, +.nginx .hljs-title, +.objectivec .hljs-preprocessor, +.php .hljs-phpdoc, +.sql .hljs-built_in, +.less .hljs-tag, +.less .hljs-at_rule, +.scss .hljs-tag, +.scss .hljs-at_rule, +.scss .hljs-important, +.stylus .hljs-at_rule, +.go .hljs-typename, +.swift .hljs-preprocessor { + color: #a71d5d; +} + +.apache .hljs-common, +.apache .hljs-cbracket, +.apache .hljs-keyword, +.bash .hljs-literal, +.bash .hljs-built_in, +.coffeescript .hljs-literal, +.coffeescript .hljs-built_in, +.coffeescript .hljs-number, +.cpp .hljs-number, +.cpp .hljs-built_in, +.c .hljs-number, +.c .hljs-built_in, +.cs .hljs-number, +.cs .hljs-built_in, +.css .hljs-attribute, +.css .hljs-hexcolor, +.css .hljs-number, +.css .hljs-function, +.http .hljs-literal, +.http .hljs-attribute, +.java .hljs-number, +.javascript .hljs-built_in, +.javascript .hljs-literal, +.javascript .hljs-number, +.json .hljs-number, +.makefile .hljs-keyword, +.markdown .hljs-link_reference, +.nginx .hljs-built_in, +.objectivec .hljs-literal, +.objectivec .hljs-number, +.objectivec .hljs-built_in, +.php .hljs-literal, +.php .hljs-number, +.python .hljs-number, +.ruby .hljs-prompt, +.ruby .hljs-constant, +.ruby .hljs-number, +.ruby .hljs-subst .hljs-keyword, +.ruby .hljs-symbol, +.sql .hljs-number, +.puppet .hljs-function, +.less .hljs-number, +.less .hljs-hexcolor, +.less .hljs-function, +.less .hljs-attribute, +.scss .hljs-preprocessor, +.scss .hljs-number, +.scss .hljs-hexcolor, +.scss .hljs-function, +.scss .hljs-attribute, +.stylus .hljs-number, +.stylus .hljs-hexcolor, +.stylus .hljs-attribute, +.stylus .hljs-params, +.go .hljs-built_in, +.go .hljs-constant, +.swift .hljs-built_in, +.swift .hljs-number { + color: #0086b3; +} + +.apache .hljs-tag, +.cs .hljs-xmlDocTag, +.css .hljs-tag, +.xml .hljs-title, +.stylus .hljs-tag { + color: #63a35c; +} + +.bash .hljs-variable, +.cs .hljs-preprocessor, +.cs .hljs-preprocessor .hljs-keyword, +.css .hljs-attr_selector, +.css .hljs-value, +.ini .hljs-value, +.ini .hljs-keyword, +.javascript .hljs-tag .hljs-title, +.makefile .hljs-constant, +.nginx .hljs-variable, +.xml .hljs-tag, +.scss .hljs-variable { + color: #333333; +} + +.bash .hljs-title, +.coffeescript .hljs-title, +.cpp .hljs-title, +.c .hljs-title, +.cs .hljs-title, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo, +.ini .hljs-title, +.java .hljs-title, +.javascript .hljs-title, +.makefile .hljs-title, +.objectivec .hljs-title, +.perl .hljs-sub, +.php .hljs-title, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-parent, +.ruby .hljs-title, +.xml .hljs-attribute, +.puppet .hljs-title, +.less .hljs-id, +.less .hljs-pseudo, +.less .hljs-class, +.scss .hljs-id, +.scss .hljs-pseudo, +.scss .hljs-class, +.stylus .hljs-class, +.stylus .hljs-id, +.stylus .hljs-pseudo, +.stylus .hljs-title, +.swift .hljs-title, +.diff .hljs-chunk { + color: #795da3; +} + +.coffeescript .hljs-reserved, +.coffeescript .hljs-attribute { + color: #1d3e81; +} + +.diff .hljs-chunk { + font-weight: bold; +} + +.diff .hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.diff .hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.markdown .hljs-link_url { + text-decoration: underline; +} diff --git a/static/highlight_styles/github.css b/static/highlight_styles/github.css new file mode 100644 index 0000000..791537e --- /dev/null +++ b/static/highlight_styles/github.css @@ -0,0 +1,123 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.diff .hljs-header { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.css .rule .hljs-keyword, +.hljs-winutils, +.nginx .hljs-title, +.hljs-subst, +.hljs-request, +.hljs-status { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-hexcolor, +.ruby .hljs-constant { + color: #008080; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-doctag, +.tex .hljs-formula { + color: #d14; +} + +.hljs-title, +.hljs-id, +.scss .hljs-preprocessor { + color: #900; + font-weight: bold; +} + +.hljs-list .hljs-keyword, +.hljs-subst { + font-weight: normal; +} + +.hljs-class .hljs-title, +.hljs-type, +.vhdl .hljs-literal, +.tex .hljs-command { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-rule .hljs-property, +.django .hljs-tag .hljs-keyword { + color: #000080; + font-weight: normal; +} + +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body, +.hljs-name { + color: #008080; +} + +.hljs-regexp { + color: #009926; +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.lisp .hljs-keyword, +.clojure .hljs-keyword, +.scheme .hljs-keyword, +.tex .hljs-special, +.hljs-prompt { + color: #990073; +} + +.hljs-built_in { + color: #0086b3; +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-doctype, +.hljs-shebang, +.hljs-cdata { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.diff .hljs-change { + background: #0086b3; +} + +.hljs-chunk { + color: #aaa; +} diff --git a/static/highlight_styles/googlecode.css b/static/highlight_styles/googlecode.css new file mode 100644 index 0000000..dad5bb0 --- /dev/null +++ b/static/highlight_styles/googlecode.css @@ -0,0 +1,144 @@ +/* + +Google Code style (c) Aahan Krish + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; + -webkit-text-size-adjust: none; +} + +.hljs-comment { + color: #800; +} + +.hljs-keyword, +.method, +.hljs-list .hljs-keyword, +.nginx .hljs-title, +.hljs-tag .hljs-title, +.setting .hljs-value, +.hljs-winutils, +.tex .hljs-command, +.http .hljs-title, +.hljs-request, +.hljs-status { + color: #008; +} + +.hljs-envvar, +.tex .hljs-special { + color: #660; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.hljs-regexp, +.coffeescript .hljs-attribute { + color: #080; +} + +.hljs-sub .hljs-identifier, +.hljs-pi, +.hljs-tag, +.hljs-tag .hljs-keyword, +.hljs-decorator, +.ini .hljs-title, +.hljs-shebang, +.hljs-prompt, +.hljs-hexcolor, +.hljs-rule .hljs-value, +.hljs-literal, +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-number, +.css .hljs-function, +.clojure .hljs-attribute { + color: #066; +} + +.hljs-class .hljs-title, +.smalltalk .hljs-class, +.hljs-doctag, +.hljs-type, +.hljs-typename, +.hljs-tag .hljs-attribute, +.hljs-doctype, +.hljs-class .hljs-id, +.hljs-built_in, +.setting, +.hljs-params, +.hljs-variable, +.hljs-name { + color: #606; +} + +.css .hljs-tag, +.hljs-rule .hljs-property, +.hljs-pseudo, +.hljs-subst { + color: #000; +} + +.css .hljs-class, +.css .hljs-id { + color: #9b703f; +} + +.hljs-value .hljs-important { + color: #ff7700; + font-weight: bold; +} + +.hljs-rule .hljs-keyword { + color: #c5af75; +} + +.hljs-annotation, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #9b859d; +} + +.hljs-preprocessor, +.hljs-preprocessor *, +.hljs-pragma { + color: #444; +} + +.tex .hljs-formula { + background-color: #eee; + font-style: italic; +} + +.diff .hljs-header, +.hljs-chunk { + color: #808080; + font-weight: bold; +} + +.diff .hljs-change { + background-color: #bccff9; +} + +.hljs-addition { + background-color: #baeeba; +} + +.hljs-deletion { + background-color: #ffc8bd; +} + +.hljs-comment .hljs-doctag { + font-weight: bold; +} diff --git a/static/highlight_styles/hybrid.css b/static/highlight_styles/hybrid.css new file mode 100644 index 0000000..3019ffe --- /dev/null +++ b/static/highlight_styles/hybrid.css @@ -0,0 +1,164 @@ +/* + +vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid) + +*/ + +/*background color*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #1d1f21; + -webkit-text-size-adjust: none; +} + +/*selection color*/ +.hljs::selection, +.hljs span::selection { + background: #373b41; +} + +.hljs::-moz-selection, +.hljs span::-moz-selection { + background: #373b41; +} + +/*foreground color*/ +.hljs, +.hljs-setting .hljs-value, +.hljs-expression .hljs-variable, +.hljs-expression .hljs-begin-block, +.hljs-expression .hljs-end-block, +.hljs-class .hljs-params, +.hljs-function .hljs-params, +.hljs-at_rule .hljs-preprocessor { + color: #c5c8c6; +} + +/*color: fg_yellow*/ +.hljs-title, +.hljs-function .hljs-title, +.hljs-keyword .hljs-common, +.hljs-class .hljs-title, +.hljs-decorator, +.hljs-tag .hljs-title, +.hljs-header, +.hljs-sub, +.hljs-function { + color: #f0c674; +} + +/*color: fg_comment*/ +.hljs-comment, +.hljs-output .hljs-value, +.hljs-pi, +.hljs-shebang, +.hljs-doctype { + color: #707880; +} + +/*color: fg_red*/ +.hljs-number, +.hljs-symbol, +.hljs-literal, +.hljs-deletion, +.hljs-link_url, +.hljs-symbol .hljs-string, +.hljs-argument, +.hljs-hexcolor, +.hljs-input .hljs-prompt, +.hljs-char { + color: #cc6666 +} + +/*color: fg_green*/ +.hljs-string, +.hljs-special, +.hljs-doctag, +.hljs-addition, +.hljs-important, +.hljs-tag .hljs-value, +.hljs-at.rule .hljs-keyword, +.hljs-regexp, +.hljs-attr_selector { + color: #b5bd68; +} + +/*color: fg_purple*/ +.hljs-variable, +.hljs-property, +.hljs-envar, +.hljs-code, +.hljs-expression, +.hljs-localvars, +.hljs-id, +.hljs-variable .hljs-filter, +.hljs-variable .hljs-filter .hljs-keyword, +.hljs-template_tag .hljs-filter .hljs-keyword, +.hljs-name { + color: #b294bb; +} + +/*color: fg_blue*/ +.hljs-statement, +.hljs-label, +.hljs-keyword, +.hljs-xmlDocTag, +.hljs-function .hljs-keyword, +.hljs-chunk, +.hljs-cdata, +.hljs-link_label, +.hljs-bullet, +.hljs-class .hljs-keyword, +.hljs-smartquote, +.hljs-method, +.hljs-list .hljs-title, +.hljs-tag { + color: #81a2be; +} + +/*color: fg_aqua*/ +.hljs-pseudo, +.hljs-exception, +.hljs-annotation, +.hljs-subst, +.hljs-change, +.hljs-cbracket, +.hljs-operator, +.hljs-horizontal_rule, +.hljs-preprocessor .hljs-keyword, +.hljs-typedef, +.hljs-template_tag, +.hljs-variable, +.hljs-variable .hljs-filter .hljs-argument, +.hljs-at_rule, +.hljs-at_rule .hljs-string, +.hljs-at_rule .hljs-keyword { + color: #8abeb7; +} + +/*color: fg_orange*/ +.hljs-type, +.hljs-typename, +.hljs-inheritance .hljs-parent, +.hljs-constant, +.hljs-built_in, +.hljs-setting, +.hljs-structure, +.hljs-link_reference, +.hljs-attribute, +.hljs-blockquote, +.hljs-quoted, +.hljs-class, +.hljs-header { + color: #de935f; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/static/highlight_styles/idea.css b/static/highlight_styles/idea.css new file mode 100644 index 0000000..3a343ad --- /dev/null +++ b/static/highlight_styles/idea.css @@ -0,0 +1,122 @@ +/* + +Intellij Idea-like styling (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #000; + background: #fff; + -webkit-text-size-adjust: none; +} + +.hljs-subst, +.hljs-title, +.json .hljs-value { + font-weight: normal; + color: #000; +} + +.hljs-comment, +.diff .hljs-header { + color: #808080; + font-style: italic; +} + +.hljs-annotation, +.hljs-decorator, +.hljs-preprocessor, +.hljs-pragma, +.hljs-doctype, +.hljs-pi, +.hljs-chunk, +.hljs-shebang, +.apache .hljs-cbracket, +.hljs-prompt, +.http .hljs-title { + color: #808000; +} + +.hljs-tag, +.hljs-pi { + background: #efefef; +} + +.hljs-tag .hljs-title, +.hljs-id, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-literal, +.hljs-keyword, +.hljs-hexcolor, +.css .hljs-function, +.ini .hljs-title, +.css .hljs-class, +.hljs-list .hljs-keyword, +.nginx .hljs-title, +.tex .hljs-command, +.hljs-request, +.hljs-status { + font-weight: bold; + color: #000080; +} + +.hljs-attribute, +.hljs-rule .hljs-keyword, +.hljs-number, +.hljs-date, +.hljs-regexp, +.tex .hljs-special { + font-weight: bold; + color: #0000ff; +} + +.hljs-number, +.hljs-regexp { + font-weight: normal; +} + +.hljs-string, +.hljs-value, +.hljs-filter .hljs-argument, +.css .hljs-function .hljs-params, +.apache .hljs-tag { + color: #008000; + font-weight: bold; +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-char, +.tex .hljs-formula { + color: #000; + background: #d0eded; + font-style: italic; +} + +.hljs-doctag { + text-decoration: underline; +} + +.hljs-variable, +.hljs-envvar, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.hljs-name { + color: #660e7a; +} + +.hljs-addition { + background: #baeeba; +} + +.hljs-deletion { + background: #ffc8bd; +} + +.diff .hljs-change { + background: #bccff9; +} diff --git a/static/highlight_styles/ir_black.css b/static/highlight_styles/ir_black.css new file mode 100644 index 0000000..f0d1e45 --- /dev/null +++ b/static/highlight_styles/ir_black.css @@ -0,0 +1,106 @@ +/* + IR_Black style (c) Vasily Mikhailitchenko +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000; + color: #f8f8f8; + -webkit-text-size-adjust: none; +} + +.hljs-shebang, +.hljs-comment { + color: #7c7c7c; +} + +.hljs-keyword, +.hljs-tag, +.tex .hljs-command, +.hljs-request, +.hljs-status, +.clojure .hljs-attribute { + color: #96cbfe; +} + +.hljs-sub .hljs-keyword, +.method, +.hljs-list .hljs-title, +.nginx .hljs-title { + color: #ffffb6; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.coffeescript .hljs-attribute { + color: #a8ff60; +} + +.hljs-subst { + color: #daefa3; +} + +.hljs-regexp { + color: #e9c062; +} + +.hljs-title, +.hljs-sub .hljs-identifier, +.hljs-pi, +.hljs-decorator, +.tex .hljs-special, +.hljs-type, +.hljs-constant, +.smalltalk .hljs-class, +.hljs-doctag, +.nginx .hljs-built_in { + color: #ffffb6; +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-number, +.hljs-variable, +.vbscript, +.hljs-literal, +.hljs-name { + color: #c6c5fe; +} + +.css .hljs-tag { + color: #96cbfe; +} + +.css .hljs-rule .hljs-property, +.css .hljs-id { + color: #ffffb6; +} + +.css .hljs-class { + color: #fff; +} + +.hljs-hexcolor { + color: #c6c5fe; +} + +.hljs-number { + color:#ff73fd; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.7; +} diff --git a/static/highlight_styles/kimbie.dark.css b/static/highlight_styles/kimbie.dark.css new file mode 100644 index 0000000..f60e47d --- /dev/null +++ b/static/highlight_styles/kimbie.dark.css @@ -0,0 +1,97 @@ +/* + Name: Kimbie (dark) + Author: Jan T. Sott + License: Creative Commons Attribution-ShareAlike 4.0 Unported License + URL: https://github.com/idleberg/Kimbie-highlight.js +*/ + +/* Kimbie Comment */ +.hljs-comment, +.hljs-title { + color: #d6baad; +} + +/* Kimbie Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #dc3958; +} + +/* Kimbie Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f79a32; +} + +/* Kimbie Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #f06431; +} + +/* Kimbie Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #889b4a; +} + +/* Kimbie Aqua */ +.css .hljs-hexcolor { + color: #088649; +} + +/* Kimbie Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #8ab1b0; +} + +/* Kimbie Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #98676a; +} + +.hljs { + display: block; + overflow-x: auto; + background: #221a0f; + color: #d3af86; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/kimbie.light.css b/static/highlight_styles/kimbie.light.css new file mode 100644 index 0000000..57fc776 --- /dev/null +++ b/static/highlight_styles/kimbie.light.css @@ -0,0 +1,97 @@ +/* + Name: Kimbie (light) + Author: Jan T. Sott + License: Creative Commons Attribution-ShareAlike 4.0 Unported License + URL: https://github.com/idleberg/Kimbie-highlight.js +*/ + +/* Kimbie Comment */ +.hljs-comment, +.hljs-title { + color: #a57a4c; +} + +/* Kimbie Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #dc3958; +} + +/* Kimbie Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f79a32; +} + +/* Kimbie Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #f06431; +} + +/* Kimbie Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #889b4a; +} + +/* Kimbie Aqua */ +.css .hljs-hexcolor { + color: #088649; +} + +/* Kimbie Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #8ab1b0; +} + +/* Kimbie Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #98676a; +} + +.hljs { + display: block; + overflow-x: auto; + background: #fbebd4; + color: #84613d; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/magula.css b/static/highlight_styles/magula.css new file mode 100644 index 0000000..13d366b --- /dev/null +++ b/static/highlight_styles/magula.css @@ -0,0 +1,120 @@ +/* +Description: Magula style for highligh.js +Author: Ruslan Keba +Website: http://rukeba.com/ +Version: 1.0 +Date: 2009-01-03 +Music: Aphex Twin / Xtal +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background-color: #f4f4f4; + -webkit-text-size-adjust: none; +} + +.hljs, +.hljs-subst { + color: black; +} + +.hljs-string, +.hljs-title, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rule .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-addition, +.hljs-flow, +.hljs-stream, +.bash .hljs-variable, +.apache .hljs-cbracket, +.coffeescript .hljs-attribute { + color: #050; +} + +.hljs-comment, +.hljs-annotation, +.diff .hljs-header, +.hljs-chunk { + color: #777; +} + +.hljs-number, +.hljs-date, +.hljs-regexp, +.hljs-literal, +.hljs-name, +.smalltalk .hljs-symbol, +.smalltalk .hljs-char, +.hljs-change, +.tex .hljs-special { + color: #800; +} + +.hljs-label, +.ruby .hljs-string, +.hljs-decorator, +.hljs-filter .hljs-argument, +.hljs-localvars, +.hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-envvar, +.hljs-shebang, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-formula, +.hljs-prompt, +.clojure .hljs-attribute { + color: #00e; +} + +.hljs-keyword, +.hljs-id, +.hljs-doctag, +.hljs-title, +.hljs-built_in, +.smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.xml .hljs-tag, +.tex .hljs-command, +.hljs-request, +.hljs-status { + font-weight: bold; + color: navy; +} + +.nginx .hljs-built_in { + font-weight: normal; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} + +/* --- */ +.apache .hljs-tag { + font-weight: bold; + color: blue; +} + diff --git a/static/highlight_styles/mono-blue.css b/static/highlight_styles/mono-blue.css new file mode 100644 index 0000000..525cccc --- /dev/null +++ b/static/highlight_styles/mono-blue.css @@ -0,0 +1,68 @@ +/* + Five-color theme from a single blue hue. +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #eaeef3; + -webkit-text-size-adjust: none; +} + +.hljs, +.hljs-list .hljs-built_in { + color: #00193a; +} + +.hljs-keyword, +.hljs-title, +.hljs-important, +.hljs-request, +.hljs-header, +.hljs-doctag { + font-weight: bold; +} + +.hljs-comment, +.hljs-chunk { + color: #738191; +} + +.hljs-string, +.hljs-title, +.hljs-parent, +.hljs-built_in, +.hljs-literal, +.hljs-filename, +.hljs-value, +.hljs-addition, +.hljs-tag, +.hljs-argument, +.hljs-link_label, +.hljs-blockquote, +.hljs-header, +.hljs-name { + color: #0048ab; +} + +.hljs-decorator, +.hljs-prompt, +.hljs-subst, +.hljs-symbol, +.hljs-doctype, +.hljs-regexp, +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-attribute, +.hljs-attr_selector, +.hljs-xmlDocTag, +.hljs-deletion, +.hljs-shebang, +.hljs-string .hljs-variable, +.hljs-link_url, +.hljs-bullet, +.hljs-sqbracket, +.hljs-phony { + color: #4c81c9; +} diff --git a/static/highlight_styles/monokai.css b/static/highlight_styles/monokai.css new file mode 100644 index 0000000..5f20abf --- /dev/null +++ b/static/highlight_styles/monokai.css @@ -0,0 +1,126 @@ +/* +Monokai style - ported by Luigi Maselli - http://grigio.org +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #272822; + -webkit-text-size-adjust: none; +} + +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-keyword, +.hljs-literal, +.hljs-strong, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.nginx .hljs-title, +.tex .hljs-special { + color: #f92672; +} + +.hljs { + color: #ddd; +} + +.hljs .hljs-constant, +.asciidoc .hljs-code, +.markdown .hljs-code { + color: #66d9ef; +} + +.hljs-code, +.hljs-class .hljs-title, +.hljs-header { + color: white; +} + +.hljs-link_label, +.hljs-attribute, +.hljs-symbol, +.hljs-symbol .hljs-string, +.hljs-value, +.hljs-regexp { + color: #bf79db; +} + +.hljs-link_url, +.hljs-tag .hljs-value, +.hljs-string, +.hljs-bullet, +.hljs-subst, +.hljs-title, +.hljs-emphasis, +.hljs-type, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.hljs-prompt, +.hljs-name { + color: #a6e22e; +} + +.hljs-comment, +.hljs-annotation, +.smartquote, +.hljs-blockquote, +.hljs-horizontal_rule, +.hljs-decorator, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #75715e; +} + +.hljs-keyword, +.hljs-literal, +.css .hljs-id, +.hljs-doctag, +.hljs-title, +.hljs-header, +.hljs-type, +.vbscript .hljs-built_in, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-special, +.hljs-request, +.hljs-status { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/monokai_sublime.css b/static/highlight_styles/monokai_sublime.css new file mode 100644 index 0000000..4ae4aeb --- /dev/null +++ b/static/highlight_styles/monokai_sublime.css @@ -0,0 +1,154 @@ +/* + +Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #23241f; + -webkit-text-size-adjust: none; +} + +.hljs, +.hljs-tag, +.css .hljs-rule, +.css .hljs-value, +.aspectj .hljs-function, +.css .hljs-function +.hljs-preprocessor, +.hljs-pragma { + color: #f8f8f2; +} + +.hljs-strongemphasis, +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-blockquote, +.hljs-horizontal_rule, +.hljs-number, +.hljs-regexp, +.alias .hljs-keyword, +.hljs-literal, +.hljs-hexcolor { + color: #ae81ff; +} + +.hljs-tag .hljs-value, +.hljs-code, +.hljs-title, +.css .hljs-class, +.hljs-class .hljs-title:last-child { + color: #a6e22e; +} + +.hljs-link_url { + font-size: 80%; +} + +.hljs-strong, +.hljs-strongemphasis { + font-weight: bold; +} + +.hljs-emphasis, +.hljs-strongemphasis, +.hljs-class .hljs-title:last-child, +.hljs-typename { + font-style: italic; +} + +.hljs-keyword, +.ruby .hljs-class .hljs-keyword:first-child, +.ruby .hljs-function .hljs-keyword, +.hljs-function, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.nginx .hljs-title, +.tex .hljs-special, +.hljs-header, +.hljs-attribute, +.hljs-symbol, +.hljs-symbol .hljs-string, +.hljs-tag .hljs-title, +.hljs-value, +.alias .hljs-keyword:first-child, +.css .hljs-tag, +.css .unit, +.css .hljs-important { + color: #f92672; +} + +.hljs-function .hljs-keyword, +.hljs-class .hljs-keyword:first-child, +.hljs-aspect .hljs-keyword:first-child, +.hljs-constant, +.hljs-typename, +.hljs-name, +.css .hljs-attribute { + color: #66d9ef; +} + +.hljs-variable, +.hljs-params, +.hljs-class .hljs-title, +.hljs-aspect .hljs-title { + color: #f8f8f2; +} + +.hljs-string, +.css .hljs-id, +.hljs-subst, +.hljs-type, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.hljs-prompt, +.hljs-link_label, +.hljs-link_url { + color: #e6db74; +} + +.hljs-comment, +.hljs-annotation, +.hljs-decorator, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #75715e; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata, +.xml .php, +.php .xml { + opacity: 0.5; +} diff --git a/static/highlight_styles/obsidian.css b/static/highlight_styles/obsidian.css new file mode 100644 index 0000000..3886f83 --- /dev/null +++ b/static/highlight_styles/obsidian.css @@ -0,0 +1,152 @@ +/** + * Obsidian style + * ported by Alexander Marenin (http://github.com/ioncreature) + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #282b2e; + -webkit-text-size-adjust: none; +} + +.hljs-keyword, +.hljs-literal, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.nginx .hljs-title, +.css .hljs-id, +.tex .hljs-special { + color: #93c763; +} + +.hljs-number { + color: #ffcd22; +} + +.hljs { + color: #e0e2e4; +} + +.css .hljs-tag, +.css .hljs-pseudo { + color: #d0d2b5; +} + +.hljs-attribute, +.hljs .hljs-constant { + color: #668bb0; +} + +.xml .hljs-attribute { + color: #b3b689; +} + +.xml .hljs-tag .hljs-value { + color: #e8e2b7; +} + +.hljs-code, +.hljs-class .hljs-title, +.hljs-header { + color: white; +} + +.hljs-class, +.hljs-hexcolor { + color: #93c763; +} + +.hljs-regexp { + color: #d39745; +} + +.hljs-at_rule, +.hljs-at_rule .hljs-keyword { + color: #a082bd; +} + +.hljs-doctype { + color: #557182; +} + +.hljs-link_url, +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-bullet, +.hljs-subst, +.hljs-emphasis, +.hljs-type, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.hljs-prompt, +.hljs-name { + color: #8cbbad; +} + +.hljs-string { + color: #ec7600; +} + +.hljs-comment, +.hljs-annotation, +.hljs-blockquote, +.hljs-horizontal_rule, +.hljs-decorator, +.hljs-pi, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket, +.tex .hljs-formula { + color: #818e96; +} + +.hljs-keyword, +.hljs-literal, +.css .hljs-id, +.hljs-doctag, +.hljs-title, +.hljs-header, +.hljs-type, +.vbscript .hljs-built_in, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-special, +.hljs-request, +.hljs-at_rule .hljs-keyword, +.hljs-status { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/paraiso.dark.css b/static/highlight_styles/paraiso.dark.css new file mode 100644 index 0000000..0f7eedb --- /dev/null +++ b/static/highlight_styles/paraiso.dark.css @@ -0,0 +1,96 @@ +/* + Paraíso (dark) + Created by Jan T. Sott (http://github.com/idleberg) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) +*/ + +/* Paraíso Comment */ +.hljs-comment, +.hljs-title { + color: #8d8687; +} + +/* Paraíso Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ef6155; +} + +/* Paraíso Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f99b15; +} + +/* Paraíso Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #fec418; +} + +/* Paraíso Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #48b685; +} + +/* Paraíso Aqua */ +.css .hljs-hexcolor { + color: #5bc4bf; +} + +/* Paraíso Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #06b6ef; +} + +/* Paraíso Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #815ba4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2f1e2e; + color: #a39e9b; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/paraiso.light.css b/static/highlight_styles/paraiso.light.css new file mode 100644 index 0000000..1562007 --- /dev/null +++ b/static/highlight_styles/paraiso.light.css @@ -0,0 +1,96 @@ +/* + Paraíso (light) + Created by Jan T. Sott (http://github.com/idleberg) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) +*/ + +/* Paraíso Comment */ +.hljs-comment, +.hljs-title { + color: #776e71; +} + +/* Paraíso Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.hljs-name, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ef6155; +} + +/* Paraíso Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f99b15; +} + +/* Paraíso Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #fec418; +} + +/* Paraíso Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #48b685; +} + +/* Paraíso Aqua */ +.css .hljs-hexcolor { + color: #5bc4bf; +} + +/* Paraíso Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #06b6ef; +} + +/* Paraíso Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #815ba4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #e7e9db; + color: #4f424c; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/pojoaque.css b/static/highlight_styles/pojoaque.css new file mode 100644 index 0000000..6e7310d --- /dev/null +++ b/static/highlight_styles/pojoaque.css @@ -0,0 +1,106 @@ +/* + +Pojoaque Style by Jason Tate +http://web-cms-designs.com/ftopict-10-pojoaque-style-for-highlight-js-code-highlighter.html +Based on Solarized Style from http://ethanschoonover.com/solarized + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #dccf8f; + background: url(./pojoaque.jpg) repeat scroll left top #181914; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.diff .hljs-header, +.hljs-doctype, +.lisp .hljs-string { + color: #586e75; + font-style: italic; +} + +.hljs-keyword, +.css .rule .hljs-keyword, +.hljs-winutils, +.javascript .hljs-title, +.method, +.hljs-addition, +.css .hljs-tag, +.hljs-list .hljs-keyword, +.nginx .hljs-title { + color: #b64926; +} + +.hljs-number, +.hljs-command, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-doctag, +.tex .hljs-formula, +.hljs-regexp, +.hljs-hexcolor { + color: #468966; +} + +.hljs-title, +.hljs-localvars, +.hljs-function .hljs-title, +.hljs-chunk, +.hljs-decorator, +.hljs-built_in, +.hljs-identifier, +.hljs-name, +.hljs-id { + color: #ffb03b; +} + +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body, +.smalltalk .hljs-number, +.hljs-constant, +.hljs-class .hljs-title, +.hljs-parent, +.hljs-type { + color: #b58900; +} + +.css .hljs-attribute { + color: #b89859; +} + +.css .hljs-number, +.css .hljs-hexcolor { + color: #dccf8f; +} + +.css .hljs-class { + color: #d3a60c; +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-shebang, +.hljs-symbol, +.hljs-symbol .hljs-string, +.diff .hljs-change, +.hljs-special, +.hljs-attr_selector, +.hljs-important, +.hljs-subst, +.hljs-cdata { + color: #cb4b16; +} + +.hljs-deletion { + color: #dc322f; +} + +.tex .hljs-formula { + background: #073642; +} diff --git a/static/highlight_styles/pojoaque.jpg b/static/highlight_styles/pojoaque.jpg new file mode 100644 index 0000000..9c07d4a Binary files /dev/null and b/static/highlight_styles/pojoaque.jpg differ diff --git a/static/highlight_styles/railscasts.css b/static/highlight_styles/railscasts.css new file mode 100644 index 0000000..50507df --- /dev/null +++ b/static/highlight_styles/railscasts.css @@ -0,0 +1,184 @@ +/* + +Railscasts-like style (c) Visoft, Inc. (Damien White) + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #232323; + color: #e6e1dc; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.hljs-shebang { + color: #bc9458; + font-style: italic; +} + +.hljs-keyword, +.ruby .hljs-function .hljs-keyword, +.hljs-request, +.hljs-status, +.nginx .hljs-title, +.method, +.hljs-list .hljs-title { + color: #c26230; +} + +.hljs-string, +.hljs-number, +.hljs-regexp, +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.tex .hljs-command, +.asciidoc .hljs-link_label, +.markdown .hljs-link_label { + color: #a5c261; +} + +.hljs-subst { + color: #519f50; +} + +.hljs-tag, +.hljs-tag .hljs-keyword, +.hljs-tag .hljs-title, +.hljs-doctype, +.hljs-sub .hljs-identifier, +.hljs-pi, +.input_number { + color: #e8bf6a; +} + +.hljs-identifier { + color: #d0d0ff; +} + +.hljs-class .hljs-title, +.hljs-type, +.smalltalk .hljs-class, +.hljs-doctag { + text-decoration: none; +} + +.hljs-constant, +.hljs-name { + color: #da4939; +} + + +.hljs-symbol, +.hljs-built_in, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-symbol .hljs-identifier, +.asciidoc .hljs-link_url, +.markdown .hljs-link_url, +.hljs-attribute { + color: #6d9cbe; +} + +.asciidoc .hljs-link_url, +.markdown .hljs-link_url { + text-decoration: underline; +} + + + +.hljs-params, +.hljs-variable, +.clojure .hljs-attribute { + color: #d0d0ff; +} + +.css .hljs-tag, +.hljs-rule .hljs-property, +.hljs-pseudo, +.tex .hljs-special { + color: #cda869; +} + +.css .hljs-class { + color: #9b703f; +} + +.hljs-rule .hljs-keyword { + color: #c5af75; +} + +.hljs-rule .hljs-value { + color: #cf6a4c; +} + +.css .hljs-id { + color: #8b98ab; +} + +.hljs-annotation, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #9b859d; +} + +.hljs-preprocessor, +.hljs-preprocessor *, +.hljs-pragma { + color: #8996a8 !important; +} + +.hljs-hexcolor, +.css .hljs-value .hljs-number { + color: #a5c261; +} + +.hljs-title, +.hljs-decorator, +.css .hljs-function { + color: #ffc66d; +} + +.diff .hljs-header, +.hljs-chunk { + background-color: #2f33ab; + color: #e6e1dc; + display: inline-block; + width: 100%; +} + +.diff .hljs-change { + background-color: #4a410d; + color: #f8f8f8; + display: inline-block; + width: 100%; +} + +.hljs-addition { + background-color: #144212; + color: #e6e1dc; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #600; + color: #e6e1dc; + display: inline-block; + width: 100%; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.7; +} diff --git a/static/highlight_styles/rainbow.css b/static/highlight_styles/rainbow.css new file mode 100644 index 0000000..ac536a7 --- /dev/null +++ b/static/highlight_styles/rainbow.css @@ -0,0 +1,107 @@ +/* + +Style with support for rainbow parens + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #474949; + color: #d1d9e1; + -webkit-text-size-adjust: none; +} + + +.hljs-body, +.hljs-collection { + color: #d1d9e1; +} + +.hljs-comment, +.diff .hljs-header, +.hljs-doctype, +.lisp .hljs-string { + color: #969896; + font-style: italic; +} + +.hljs-keyword, +.clojure .hljs-attribute, +.hljs-winutils, +.javascript .hljs-title, +.hljs-addition, +.css .hljs-tag { + color: #cc99cc; +} + +.hljs-number { color: #f99157; } + +.hljs-command, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-doctag, +.tex .hljs-formula, +.hljs-regexp, +.hljs-hexcolor { + color: #8abeb7; +} + +.hljs-title, +.hljs-localvars, +.hljs-function .hljs-title, +.hljs-chunk, +.hljs-decorator, +.hljs-built_in, +.hljs-identifier { + color: #b5bd68; +} + +.hljs-class .hljs-keyword { + color: #f2777a; +} + +.hljs-variable, +.smalltalk .hljs-number, +.hljs-constant, +.hljs-class .hljs-title, +.hljs-parent, +.haskell .hljs-label, +.hljs-id, +.hljs-name { + color: #ffcc66; +} + +.hljs-tag .hljs-title, +.hljs-rule .hljs-property, +.django .hljs-tag .hljs-keyword { + font-weight: bold; +} + +.hljs-attribute { + color: #81a2be; +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-shebang, +.hljs-symbol, +.hljs-symbol .hljs-string, +.diff .hljs-change, +.hljs-special, +.hljs-attr_selector, +.hljs-important, +.hljs-subst, +.hljs-cdata { + color: #f99157; +} + +.hljs-deletion { + color: #dc322f; +} + +.tex .hljs-formula { + background: #eee8d5; +} diff --git a/static/highlight_styles/school_book.css b/static/highlight_styles/school_book.css new file mode 100644 index 0000000..49371d9 --- /dev/null +++ b/static/highlight_styles/school_book.css @@ -0,0 +1,111 @@ +/* + +School Book style from goldblog.com.ua (c) Zaripov Yura + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 15px 0.5em 0.5em 30px; + font-size: 11px !important; + line-height:16px !important; + -webkit-text-size-adjust: none; +} + +pre{ + background:#f6f6ae url(./school_book.png); + border-top: solid 2px #d2e8b9; + border-bottom: solid 1px #d2e8b9; +} + +.hljs-keyword, +.hljs-literal, +.hljs-change, +.hljs-winutils, +.hljs-flow, +.nginx .hljs-title, +.tex .hljs-special { + color:#005599; + font-weight:bold; +} + +.hljs, +.hljs-subst, +.hljs-tag .hljs-keyword { + color: #3e5915; +} + +.hljs-string, +.hljs-title, +.hljs-type, +.hljs-tag .hljs-value, +.css .hljs-rule .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-class .hljs-parent, +.hljs-built_in, +.django .hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.ruby .hljs-string, +.django .hljs-filter .hljs-argument, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-addition, +.hljs-stream, +.hljs-envvar, +.apache .hljs-tag, +.apache .hljs-cbracket, +.nginx .hljs-built_in, +.tex .hljs-command, +.coffeescript .hljs-attribute, +.hljs-name { + color: #2c009f; +} + +.hljs-comment, +.hljs-annotation, +.hljs-decorator, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-shebang, +.apache .hljs-sqbracket { + color: #e60415; +} + +.hljs-keyword, +.hljs-literal, +.css .hljs-id, +.hljs-doctag, +.hljs-title, +.hljs-type, +.vbscript .hljs-built_in, +.rsl .hljs-built_in, +.smalltalk .hljs-class, +.xml .hljs-tag .hljs-title, +.diff .hljs-header, +.hljs-chunk, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.tex .hljs-command, +.hljs-request, +.hljs-status { + font-weight: bold; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/school_book.png b/static/highlight_styles/school_book.png new file mode 100644 index 0000000..956e979 Binary files /dev/null and b/static/highlight_styles/school_book.png differ diff --git a/static/highlight_styles/solarized_dark.css b/static/highlight_styles/solarized_dark.css new file mode 100644 index 0000000..f1bf4ec --- /dev/null +++ b/static/highlight_styles/solarized_dark.css @@ -0,0 +1,107 @@ +/* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #002b36; + color: #839496; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.diff .hljs-header, +.hljs-doctype, +.hljs-pi, +.lisp .hljs-string { + color: #586e75; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-winutils, +.method, +.hljs-addition, +.css .hljs-tag, +.hljs-request, +.hljs-status, +.nginx .hljs-title { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-command, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-rule .hljs-value, +.hljs-doctag, +.tex .hljs-formula, +.hljs-regexp, +.hljs-hexcolor, +.hljs-link_url { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-localvars, +.hljs-chunk, +.hljs-decorator, +.hljs-built_in, +.hljs-identifier, +.vhdl .hljs-literal, +.hljs-id, +.css .hljs-function, +.hljs-name { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body, +.smalltalk .hljs-number, +.hljs-constant, +.hljs-class .hljs-title, +.hljs-parent, +.hljs-type, +.hljs-link_reference { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-preprocessor, +.hljs-preprocessor .hljs-keyword, +.hljs-pragma, +.hljs-shebang, +.hljs-symbol, +.hljs-symbol .hljs-string, +.diff .hljs-change, +.hljs-special, +.hljs-attr_selector, +.hljs-subst, +.hljs-cdata, +.css .hljs-pseudo, +.hljs-header { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-deletion, +.hljs-important { + color: #dc322f; +} + +/* Solarized Violet */ +.hljs-link_label { + color: #6c71c4; +} + +.tex .hljs-formula { + background: #073642; +} diff --git a/static/highlight_styles/solarized_light.css b/static/highlight_styles/solarized_light.css new file mode 100644 index 0000000..04adb21 --- /dev/null +++ b/static/highlight_styles/solarized_light.css @@ -0,0 +1,107 @@ +/* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fdf6e3; + color: #657b83; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.diff .hljs-header, +.hljs-doctype, +.hljs-pi, +.lisp .hljs-string { + color: #93a1a1; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-winutils, +.method, +.hljs-addition, +.css .hljs-tag, +.hljs-request, +.hljs-status, +.nginx .hljs-title { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-command, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-rule .hljs-value, +.hljs-doctag, +.tex .hljs-formula, +.hljs-regexp, +.hljs-hexcolor, +.hljs-link_url { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-localvars, +.hljs-chunk, +.hljs-decorator, +.hljs-built_in, +.hljs-identifier, +.vhdl .hljs-literal, +.hljs-id, +.css .hljs-function, +.hljs-name { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body, +.smalltalk .hljs-number, +.hljs-constant, +.hljs-class .hljs-title, +.hljs-parent, +.hljs-type, +.hljs-link_reference { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-preprocessor, +.hljs-preprocessor .hljs-keyword, +.hljs-pragma, +.hljs-shebang, +.hljs-symbol, +.hljs-symbol .hljs-string, +.diff .hljs-change, +.hljs-special, +.hljs-attr_selector, +.hljs-subst, +.hljs-cdata, +.css .hljs-pseudo, +.hljs-header { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-deletion, +.hljs-important { + color: #dc322f; +} + +/* Solarized Violet */ +.hljs-link_label { + color: #6c71c4; +} + +.tex .hljs-formula { + background: #eee8d5; +} diff --git a/static/highlight_styles/sunburst.css b/static/highlight_styles/sunburst.css new file mode 100644 index 0000000..4f468f1 --- /dev/null +++ b/static/highlight_styles/sunburst.css @@ -0,0 +1,161 @@ +/* + +Sunburst-like style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000; + color: #f8f8f8; + -webkit-text-size-adjust: none; +} + +.hljs-comment { + color: #aeaeae; + font-style: italic; +} + +.hljs-keyword, +.ruby .hljs-function .hljs-keyword, +.hljs-request, +.hljs-status, +.nginx .hljs-title { + color: #e28964; +} + +.hljs-function .hljs-keyword, +.hljs-sub .hljs-keyword, +.method, +.hljs-list .hljs-title { + color: #99cf50; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.tex .hljs-command, +.coffeescript .hljs-attribute, +.hljs-name { + color: #65b042; +} + +.hljs-subst { + color: #daefa3; +} + +.hljs-regexp { + color: #e9c062; +} + +.hljs-title, +.hljs-sub .hljs-identifier, +.hljs-pi, +.hljs-tag, +.hljs-tag .hljs-keyword, +.hljs-decorator, +.hljs-shebang, +.hljs-prompt { + color: #89bdff; +} + +.hljs-class .hljs-title, +.hljs-type, +.smalltalk .hljs-class, +.hljs-doctag { + text-decoration: underline; +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-number { + color: #3387cc; +} + +.hljs-params, +.hljs-variable, +.clojure .hljs-attribute { + color: #3e87e3; +} + +.css .hljs-tag, +.hljs-rule .hljs-property, +.hljs-pseudo, +.tex .hljs-special { + color: #cda869; +} + +.css .hljs-class { + color: #9b703f; +} + +.hljs-rule .hljs-keyword { + color: #c5af75; +} + +.hljs-rule .hljs-value { + color: #cf6a4c; +} + +.css .hljs-id { + color: #8b98ab; +} + +.hljs-annotation, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #9b859d; +} + +.hljs-preprocessor, +.hljs-pragma { + color: #8996a8; +} + +.hljs-hexcolor, +.css .hljs-value .hljs-number { + color: #dd7b3b; +} + +.css .hljs-function { + color: #dad085; +} + +.diff .hljs-header, +.hljs-chunk, +.tex .hljs-formula { + background-color: #0e2231; + color: #f8f8f8; + font-style: italic; +} + +.diff .hljs-change { + background-color: #4a410d; + color: #f8f8f8; +} + +.hljs-addition { + background-color: #253b22; + color: #f8f8f8; +} + +.hljs-deletion { + background-color: #420e09; + color: #f8f8f8; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/tomorrow-night-blue.css b/static/highlight_styles/tomorrow-night-blue.css new file mode 100644 index 0000000..96e5227 --- /dev/null +++ b/static/highlight_styles/tomorrow-night-blue.css @@ -0,0 +1,96 @@ +/* Tomorrow Night Blue Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment { + color: #7285b7; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #ff9da4; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #ffc58f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #ffeead; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.hljs-name, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #d1f1a9; +} + +/* Tomorrow Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #99ffff; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #bbdaff; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #ebbbff; +} + +.hljs { + display: block; + overflow-x: auto; + background: #002451; + color: white; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/tomorrow-night-bright.css b/static/highlight_styles/tomorrow-night-bright.css new file mode 100644 index 0000000..00604fb --- /dev/null +++ b/static/highlight_styles/tomorrow-night-bright.css @@ -0,0 +1,95 @@ +/* Tomorrow Night Bright Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #d54e53; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #e78c45; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #e7c547; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.hljs-name, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #b9ca4a; +} + +/* Tomorrow Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #70c0b1; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #7aa6da; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #c397d8; +} + +.hljs { + display: block; + overflow-x: auto; + background: black; + color: #eaeaea; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/tomorrow-night-eighties.css b/static/highlight_styles/tomorrow-night-eighties.css new file mode 100644 index 0000000..1f2791f --- /dev/null +++ b/static/highlight_styles/tomorrow-night-eighties.css @@ -0,0 +1,95 @@ +/* Tomorrow Night Eighties Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment { + color: #999999; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #f2777a; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f99157; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #ffcc66; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.hljs-name, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #99cc99; +} + +/* Tomorrow Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #66cccc; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #6699cc; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #cc99cc; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2d2d2d; + color: #cccccc; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/tomorrow-night.css b/static/highlight_styles/tomorrow-night.css new file mode 100644 index 0000000..9788e08 --- /dev/null +++ b/static/highlight_styles/tomorrow-night.css @@ -0,0 +1,96 @@ +/* Tomorrow Night Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #cc6666; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #de935f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #f0c674; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.hljs-name, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #b5bd68; +} + +/* Tomorrow Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #8abeb7; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #81a2be; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b294bb; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1d1f21; + color: #c5c8c6; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/tomorrow.css b/static/highlight_styles/tomorrow.css new file mode 100644 index 0000000..fb2e161 --- /dev/null +++ b/static/highlight_styles/tomorrow.css @@ -0,0 +1,93 @@ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment { + color: #8e908c; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #c82829; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #f5871f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #eab700; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.hljs-name, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #718c00; +} + +/* Tomorrow Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #3e999f; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #4271ae; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #8959a8; +} + +.hljs { + display: block; + overflow-x: auto; + background: white; + color: #4d4d4c; + padding: 0.5em; + -webkit-text-size-adjust: none; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/static/highlight_styles/vs.css b/static/highlight_styles/vs.css new file mode 100644 index 0000000..25d4308 --- /dev/null +++ b/static/highlight_styles/vs.css @@ -0,0 +1,92 @@ +/* + +Visual Studio-like style based on original C# coloring by Jason Diamond + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.hljs-annotation, +.diff .hljs-header, +.hljs-chunk, +.apache .hljs-cbracket { + color: #008000; +} + +.hljs-keyword, +.hljs-id, +.hljs-built_in, +.smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.tex .hljs-command, +.hljs-request, +.hljs-status, +.nginx .hljs-title, +.xml .hljs-tag, +.xml .hljs-tag .hljs-value { + color: #00f; +} + +.hljs-string, +.hljs-title, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rule .hljs-value, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-template_tag, +.django .hljs-variable, +.hljs-addition, +.hljs-flow, +.hljs-stream, +.apache .hljs-tag, +.hljs-date, +.tex .hljs-formula, +.coffeescript .hljs-attribute, +.hljs-name { + color: #a31515; +} + +.ruby .hljs-string, +.hljs-decorator, +.hljs-filter .hljs-argument, +.hljs-localvars, +.hljs-array, +.hljs-attr_selector, +.hljs-pseudo, +.hljs-pi, +.hljs-doctype, +.hljs-deletion, +.hljs-envvar, +.hljs-shebang, +.hljs-preprocessor, +.hljs-pragma, +.userType, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-special, +.hljs-prompt { + color: #2b91af; +} + +.hljs-doctag, +.hljs-xmlDocTag { + color: #808080; +} + +.hljs-type, +.hljs-typename { font-weight: bold; } + +.vhdl .hljs-string { color: #666666; } +.vhdl .hljs-literal { color: #a31515; } +.vhdl .hljs-attribute { color: #00b0e8; } + +.xml .hljs-attribute { color: #f00; } diff --git a/static/highlight_styles/xcode.css b/static/highlight_styles/xcode.css new file mode 100644 index 0000000..21bf1ff --- /dev/null +++ b/static/highlight_styles/xcode.css @@ -0,0 +1,154 @@ +/* + +XCode style (c) Angel Garcia + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fff; + color: black; + -webkit-text-size-adjust: none; +} + +.hljs-comment { + color: #006a00; +} + +.hljs-keyword, +.hljs-literal, +.nginx .hljs-title { + color: #aa0d91; +} +.method, +.hljs-list .hljs-title, +.hljs-tag .hljs-title, +.setting .hljs-value, +.hljs-winutils, +.tex .hljs-command, +.http .hljs-title, +.hljs-request, +.hljs-status, +.hljs-name { + color: #008; +} + +.hljs-envvar, +.tex .hljs-special { + color: #660; +} + +.hljs-string { + color: #c41a16; +} +.hljs-tag .hljs-value, +.hljs-cdata, +.hljs-filter .hljs-argument, +.hljs-attr_selector, +.apache .hljs-cbracket, +.hljs-date, +.hljs-regexp { + color: #080; +} + +.hljs-sub .hljs-identifier, +.hljs-pi, +.hljs-tag, +.hljs-tag .hljs-keyword, +.hljs-decorator, +.ini .hljs-title, +.hljs-shebang, +.hljs-prompt, +.hljs-hexcolor, +.hljs-rule .hljs-value, +.hljs-symbol, +.hljs-symbol .hljs-string, +.hljs-number, +.css .hljs-function, +.hljs-function .hljs-title, +.coffeescript .hljs-attribute { + color: #1c00cf; +} + +.hljs-class .hljs-title, +.smalltalk .hljs-class, +.hljs-type, +.hljs-typename, +.hljs-tag .hljs-attribute, +.hljs-doctype, +.hljs-class .hljs-id, +.hljs-built_in, +.setting, +.hljs-params, +.clojure .hljs-attribute { + color: #5c2699; +} + +.hljs-variable { + color: #3f6e74; +} +.css .hljs-tag, +.hljs-rule .hljs-property, +.hljs-pseudo, +.hljs-subst { + color: #000; +} + +.css .hljs-class, +.css .hljs-id { + color: #9b703f; +} + +.hljs-value .hljs-important { + color: #ff7700; + font-weight: bold; +} + +.hljs-rule .hljs-keyword { + color: #c5af75; +} + +.hljs-annotation, +.apache .hljs-sqbracket, +.nginx .hljs-built_in { + color: #9b859d; +} + +.hljs-preprocessor, +.hljs-preprocessor *, +.hljs-pragma { + color: #643820; +} + +.tex .hljs-formula { + background-color: #eee; + font-style: italic; +} + +.diff .hljs-header, +.hljs-chunk { + color: #808080; + font-weight: bold; +} + +.diff .hljs-change { + background-color: #bccff9; +} + +.hljs-addition { + background-color: #baeeba; +} + +.hljs-deletion { + background-color: #ffc8bd; +} + +.hljs-comment .hljs-doctag { + font-weight: bold; +} + +.method .hljs-id { + color: #000; +} diff --git a/static/highlight_styles/zenburn.css b/static/highlight_styles/zenburn.css new file mode 100644 index 0000000..c7f5bc5 --- /dev/null +++ b/static/highlight_styles/zenburn.css @@ -0,0 +1,118 @@ +/* + +Zenburn style from voldmar.ru (c) Vladimir Epifanov +based on dark.css by Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #3f3f3f; + color: #dcdcdc; + -webkit-text-size-adjust: none; +} + +.hljs-keyword, +.hljs-tag, +.css .hljs-class, +.css .hljs-id, +.lisp .hljs-title, +.nginx .hljs-title, +.hljs-request, +.hljs-status, +.clojure .hljs-attribute { + color: #e3ceab; +} + +.django .hljs-template_tag, +.django .hljs-variable, +.django .hljs-filter .hljs-argument { + color: #dcdcdc; +} + +.hljs-number, +.hljs-date { + color: #8cd0d3; +} + +.dos .hljs-envvar, +.dos .hljs-stream, +.hljs-variable, +.apache .hljs-sqbracket, +.hljs-name { + color: #efdcbc; +} + +.dos .hljs-flow, +.diff .hljs-change, +.python .exception, +.python .hljs-built_in, +.hljs-literal, +.tex .hljs-special { + color: #efefaf; +} + +.diff .hljs-chunk, +.hljs-subst { + color: #8f8f8f; +} + +.dos .hljs-keyword, +.hljs-decorator, +.hljs-title, +.hljs-type, +.diff .hljs-header, +.ruby .hljs-class .hljs-parent, +.apache .hljs-tag, +.nginx .hljs-built_in, +.tex .hljs-command, +.hljs-prompt { + color: #efef8f; +} + +.dos .hljs-winutils, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.ruby .hljs-string { + color: #dca3a3; +} + +.diff .hljs-deletion, +.hljs-string, +.hljs-tag .hljs-value, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.smalltalk .hljs-class, +.smalltalk .hljs-localvars, +.smalltalk .hljs-array, +.css .hljs-rule .hljs-value, +.hljs-attr_selector, +.hljs-pseudo, +.apache .hljs-cbracket, +.tex .hljs-formula, +.coffeescript .hljs-attribute { + color: #cc9393; +} + +.hljs-shebang, +.diff .hljs-addition, +.hljs-comment, +.hljs-annotation, +.hljs-pi, +.hljs-doctype { + color: #7f9f7f; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} + diff --git a/static/scripts/Chart.js b/static/scripts/Chart.js new file mode 100644 index 0000000..ab677e6 --- /dev/null +++ b/static/scripts/Chart.js @@ -0,0 +1,3477 @@ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 1.0.2 + * + * Copyright 2015 Nick Downie + * Released under the MIT license + * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md + */ + + +(function(){ + + "use strict"; + + //Declare root variable - window in the browser, global on the server + var root = this, + previous = root.Chart; + + //Occupy the global variable of Chart, and create a simple base class + var Chart = function(context){ + var chart = this; + this.canvas = context.canvas; + + this.ctx = context; + + //Variables global to the chart + var computeDimension = function(element,dimension) + { + if (element['offset'+dimension]) + { + return element['offset'+dimension]; + } + else + { + return document.defaultView.getComputedStyle(element).getPropertyValue(dimension); + } + } + + var width = this.width = computeDimension(context.canvas,'Width'); + var height = this.height = computeDimension(context.canvas,'Height'); + + // Firefox requires this to work correctly + context.canvas.width = width; + context.canvas.height = height; + + var width = this.width = context.canvas.width; + var height = this.height = context.canvas.height; + this.aspectRatio = this.width / this.height; + //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. + helpers.retinaScale(this); + + return this; + }; + //Globally expose the defaults to allow for user updating/changing + Chart.defaults = { + global: { + // Boolean - Whether to animate the chart + animation: true, + + // Number - Number of animation steps + animationSteps: 60, + + // String - Animation easing effect + animationEasing: "easeOutQuart", + + // Boolean - If we should show the scale at all + showScale: true, + + // Boolean - If we want to override with a hard coded scale + scaleOverride: false, + + // ** Required if scaleOverride is true ** + // Number - The number of steps in a hard coded scale + scaleSteps: null, + // Number - The value jump in the hard coded scale + scaleStepWidth: null, + // Number - The scale starting value + scaleStartValue: null, + + // String - Colour of the scale line + scaleLineColor: "rgba(0,0,0,.1)", + + // Number - Pixel width of the scale line + scaleLineWidth: 1, + + // Boolean - Whether to show labels on the scale + scaleShowLabels: true, + + // Interpolated JS string - can access value + scaleLabel: "<%=value%>", + + // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there + scaleIntegersOnly: true, + + // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value + scaleBeginAtZero: false, + + // String - Scale label font declaration for the scale label + scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + + // Number - Scale label font size in pixels + scaleFontSize: 12, + + // String - Scale label font weight style + scaleFontStyle: "normal", + + // String - Scale label font colour + scaleFontColor: "#666", + + // Boolean - whether or not the chart should be responsive and resize when the browser does. + responsive: false, + + // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container + maintainAspectRatio: true, + + // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove + showTooltips: true, + + // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function + customTooltips: false, + + // Array - Array of string names to attach tooltip events + tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"], + + // String - Tooltip background colour + tooltipFillColor: "rgba(0,0,0,0.8)", + + // String - Tooltip label font declaration for the scale label + tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + + // Number - Tooltip label font size in pixels + tooltipFontSize: 14, + + // String - Tooltip font weight style + tooltipFontStyle: "normal", + + // String - Tooltip label font colour + tooltipFontColor: "#fff", + + // String - Tooltip title font declaration for the scale label + tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + + // Number - Tooltip title font size in pixels + tooltipTitleFontSize: 14, + + // String - Tooltip title font weight style + tooltipTitleFontStyle: "bold", + + // String - Tooltip title font colour + tooltipTitleFontColor: "#fff", + + // Number - pixel width of padding around tooltip text + tooltipYPadding: 6, + + // Number - pixel width of padding around tooltip text + tooltipXPadding: 6, + + // Number - Size of the caret on the tooltip + tooltipCaretSize: 8, + + // Number - Pixel radius of the tooltip border + tooltipCornerRadius: 6, + + // Number - Pixel offset from point x to tooltip edge + tooltipXOffset: 10, + + // String - Template string for single tooltips + tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>", + + // String - Template string for single tooltips + multiTooltipTemplate: "<%= value %>", + + // String - Colour behind the legend colour block + multiTooltipKeyBackground: '#fff', + + // Function - Will fire on animation progression. + onAnimationProgress: function(){}, + + // Function - Will fire on animation completion. + onAnimationComplete: function(){} + + } + }; + + //Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; + + //Global Chart helpers object for utility methods and classes + var helpers = Chart.helpers = {}; + + //-- Basic js utility methods + var each = helpers.each = function(loopable,callback,self){ + var additionalArgs = Array.prototype.slice.call(arguments, 3); + // Check to see if null or undefined firstly. + if (loopable){ + if (loopable.length === +loopable.length){ + var i; + for (i=0; i= 0; i--) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)){ + return currentItem; + } + } + }, + inherits = helpers.inherits = function(extensions){ + //Basic javascript inheritance based on the model created in Backbone.js + var parent = this; + var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); }; + + var Surrogate = function(){ this.constructor = ChartElement;}; + Surrogate.prototype = parent.prototype; + ChartElement.prototype = new Surrogate(); + + ChartElement.extend = inherits; + + if (extensions) extend(ChartElement.prototype, extensions); + + ChartElement.__super__ = parent.prototype; + + return ChartElement; + }, + noop = helpers.noop = function(){}, + uid = helpers.uid = (function(){ + var id=0; + return function(){ + return "chart-" + id++; + }; + })(), + warn = helpers.warn = function(str){ + //Method for warning of errors + if (window.console && typeof window.console.warn == "function") console.warn(str); + }, + amd = helpers.amd = (typeof define == 'function' && define.amd), + //-- Math methods + isNumber = helpers.isNumber = function(n){ + return !isNaN(parseFloat(n)) && isFinite(n); + }, + max = helpers.max = function(array){ + return Math.max.apply( Math, array ); + }, + min = helpers.min = function(array){ + return Math.min.apply( Math, array ); + }, + cap = helpers.cap = function(valueToCap,maxValue,minValue){ + if(isNumber(maxValue)) { + if( valueToCap > maxValue ) { + return maxValue; + } + } + else if(isNumber(minValue)){ + if ( valueToCap < minValue ){ + return minValue; + } + } + return valueToCap; + }, + getDecimalPlaces = helpers.getDecimalPlaces = function(num){ + if (num%1!==0 && isNumber(num)){ + return num.toString().split(".")[1].length; + } + else { + return 0; + } + }, + toRadians = helpers.radians = function(degrees){ + return degrees * (Math.PI/180); + }, + // Gets the angle from vertical upright to the point about a centre. + getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){ + var distanceFromXCenter = anglePoint.x - centrePoint.x, + distanceFromYCenter = anglePoint.y - centrePoint.y, + radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + + + var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter); + + //If the segment is in the top left quadrant, we need to add another rotation to the angle + if (distanceFromXCenter < 0 && distanceFromYCenter < 0){ + angle += Math.PI*2; + } + + return { + angle: angle, + distance: radialDistanceFromCenter + }; + }, + aliasPixel = helpers.aliasPixel = function(pixelWidth){ + return (pixelWidth % 2 === 0) ? 0 : 0.5; + }, + splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){ + //Props to Rob Spencer at scaled innovation for his post on splining between points + //http://scaledinnovation.com/analytics/splines/aboutSplines.html + var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)), + d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)), + fa=t*d01/(d01+d12),// scaling factor for triangle Ta + fb=t*d12/(d01+d12); + return { + inner : { + x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x), + y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y) + }, + outer : { + x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x), + y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y) + } + }; + }, + calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){ + return Math.floor(Math.log(val) / Math.LN10); + }, + calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){ + + //Set a minimum step of two - a point at the top of the graph, and a point at the base + var minSteps = 2, + maxSteps = Math.floor(drawingSize/(textSize * 1.5)), + skipFitting = (minSteps >= maxSteps); + + var maxValue = max(valuesArray), + minValue = min(valuesArray); + + // We need some degree of seperation here to calculate the scales if all the values are the same + // Adding/minusing 0.5 will give us a range of 1. + if (maxValue === minValue){ + maxValue += 0.5; + // So we don't end up with a graph with a negative start value if we've said always start from zero + if (minValue >= 0.5 && !startFromZero){ + minValue -= 0.5; + } + else{ + // Make up a whole number above the values + maxValue += 0.5; + } + } + + var valueRange = Math.abs(maxValue - minValue), + rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange), + graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), + graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), + graphRange = graphMax - graphMin, + stepValue = Math.pow(10, rangeOrderOfMagnitude), + numberOfSteps = Math.round(graphRange / stepValue); + + //If we have more space on the graph we'll use it to give more definition to the data + while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) { + if(numberOfSteps > maxSteps){ + stepValue *=2; + numberOfSteps = Math.round(graphRange/stepValue); + // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps. + if (numberOfSteps % 1 !== 0){ + skipFitting = true; + } + } + //We can fit in double the amount of scale points on the scale + else{ + //If user has declared ints only, and the step value isn't a decimal + if (integersOnly && rangeOrderOfMagnitude >= 0){ + //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float + if(stepValue/2 % 1 === 0){ + stepValue /=2; + numberOfSteps = Math.round(graphRange/stepValue); + } + //If it would make it a float break out of the loop + else{ + break; + } + } + //If the scale doesn't have to be an int, make the scale more granular anyway. + else{ + stepValue /=2; + numberOfSteps = Math.round(graphRange/stepValue); + } + + } + } + + if (skipFitting){ + numberOfSteps = minSteps; + stepValue = graphRange / numberOfSteps; + } + + return { + steps : numberOfSteps, + stepValue : stepValue, + min : graphMin, + max : graphMin + (numberOfSteps * stepValue) + }; + + }, + /* jshint ignore:start */ + // Blows up jshint errors based on the new Function constructor + //Templating methods + //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ + template = helpers.template = function(templateString, valuesObject){ + + // If templateString is function rather than string-template - call the function for valuesObject + + if(templateString instanceof Function){ + return templateString(valuesObject); + } + + var cache = {}; + function tmpl(str, data){ + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + var fn = !/\W/.test(str) ? + cache[str] = cache[str] : + + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + + // Convert the template into pure JavaScript + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');" + ); + + // Provide some basic currying to the user + return data ? fn( data ) : fn; + } + return tmpl(templateString,valuesObject); + }, + /* jshint ignore:end */ + generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){ + var labelsArray = new Array(numberOfSteps); + if (labelTemplateString){ + each(labelsArray,function(val,index){ + labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))}); + }); + } + return labelsArray; + }, + //--Animation methods + //Easing functions adapted from Robert Penner's easing equations + //http://www.robertpenner.com/easing/ + easingEffects = helpers.easingEffects = { + linear: function (t) { + return t; + }, + easeInQuad: function (t) { + return t * t; + }, + easeOutQuad: function (t) { + return -1 * t * (t - 2); + }, + easeInOutQuad: function (t) { + if ((t /= 1 / 2) < 1) return 1 / 2 * t * t; + return -1 / 2 * ((--t) * (t - 2) - 1); + }, + easeInCubic: function (t) { + return t * t * t; + }, + easeOutCubic: function (t) { + return 1 * ((t = t / 1 - 1) * t * t + 1); + }, + easeInOutCubic: function (t) { + if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t; + return 1 / 2 * ((t -= 2) * t * t + 2); + }, + easeInQuart: function (t) { + return t * t * t * t; + }, + easeOutQuart: function (t) { + return -1 * ((t = t / 1 - 1) * t * t * t - 1); + }, + easeInOutQuart: function (t) { + if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t; + return -1 / 2 * ((t -= 2) * t * t * t - 2); + }, + easeInQuint: function (t) { + return 1 * (t /= 1) * t * t * t * t; + }, + easeOutQuint: function (t) { + return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); + }, + easeInOutQuint: function (t) { + if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t; + return 1 / 2 * ((t -= 2) * t * t * t * t + 2); + }, + easeInSine: function (t) { + return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; + }, + easeOutSine: function (t) { + return 1 * Math.sin(t / 1 * (Math.PI / 2)); + }, + easeInOutSine: function (t) { + return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); + }, + easeInExpo: function (t) { + return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); + }, + easeOutExpo: function (t) { + return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); + }, + easeInOutExpo: function (t) { + if (t === 0) return 0; + if (t === 1) return 1; + if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1)); + return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); + }, + easeInCirc: function (t) { + if (t >= 1) return t; + return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); + }, + easeOutCirc: function (t) { + return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); + }, + easeInOutCirc: function (t) { + if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1); + return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + easeInElastic: function (t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) return 0; + if ((t /= 1) == 1) return 1; + if (!p) p = 1 * 0.3; + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); + }, + easeOutElastic: function (t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) return 0; + if ((t /= 1) == 1) return 1; + if (!p) p = 1 * 0.3; + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; + }, + easeInOutElastic: function (t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) return 0; + if ((t /= 1 / 2) == 2) return 1; + if (!p) p = 1 * (0.3 * 1.5); + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function (t) { + var s = 1.70158; + return 1 * (t /= 1) * t * ((s + 1) * t - s); + }, + easeOutBack: function (t) { + var s = 1.70158; + return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); + }, + easeInOutBack: function (t) { + var s = 1.70158; + if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); + return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + easeInBounce: function (t) { + return 1 - easingEffects.easeOutBounce(1 - t); + }, + easeOutBounce: function (t) { + if ((t /= 1) < (1 / 2.75)) { + return 1 * (7.5625 * t * t); + } else if (t < (2 / 2.75)) { + return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); + } else if (t < (2.5 / 2.75)) { + return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); + } else { + return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); + } + }, + easeInOutBounce: function (t) { + if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5; + return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; + } + }, + //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + requestAnimFrame = helpers.requestAnimFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, 1000 / 60); + }; + })(), + cancelAnimFrame = helpers.cancelAnimFrame = (function(){ + return window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.mozCancelAnimationFrame || + window.oCancelAnimationFrame || + window.msCancelAnimationFrame || + function(callback) { + return window.clearTimeout(callback, 1000 / 60); + }; + })(), + animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){ + + var currentStep = 0, + easingFunction = easingEffects[easingString] || easingEffects.linear; + + var animationFrame = function(){ + currentStep++; + var stepDecimal = currentStep/totalSteps; + var easeDecimal = easingFunction(stepDecimal); + + callback.call(chartInstance,easeDecimal,stepDecimal, currentStep); + onProgress.call(chartInstance,easeDecimal,stepDecimal); + if (currentStep < totalSteps){ + chartInstance.animationFrame = requestAnimFrame(animationFrame); + } else{ + onComplete.apply(chartInstance); + } + }; + requestAnimFrame(animationFrame); + }, + //-- DOM methods + getRelativePosition = helpers.getRelativePosition = function(evt){ + var mouseX, mouseY; + var e = evt.originalEvent || evt, + canvas = evt.currentTarget || evt.srcElement, + boundingRect = canvas.getBoundingClientRect(); + + if (e.touches){ + mouseX = e.touches[0].clientX - boundingRect.left; + mouseY = e.touches[0].clientY - boundingRect.top; + + } + else{ + mouseX = e.clientX - boundingRect.left; + mouseY = e.clientY - boundingRect.top; + } + + return { + x : mouseX, + y : mouseY + }; + + }, + addEvent = helpers.addEvent = function(node,eventType,method){ + if (node.addEventListener){ + node.addEventListener(eventType,method); + } else if (node.attachEvent){ + node.attachEvent("on"+eventType, method); + } else { + node["on"+eventType] = method; + } + }, + removeEvent = helpers.removeEvent = function(node, eventType, handler){ + if (node.removeEventListener){ + node.removeEventListener(eventType, handler, false); + } else if (node.detachEvent){ + node.detachEvent("on"+eventType,handler); + } else{ + node["on" + eventType] = noop; + } + }, + bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){ + // Create the events object if it's not already present + if (!chartInstance.events) chartInstance.events = {}; + + each(arrayOfEvents,function(eventName){ + chartInstance.events[eventName] = function(){ + handler.apply(chartInstance, arguments); + }; + addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]); + }); + }, + unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) { + each(arrayOfEvents, function(handler,eventName){ + removeEvent(chartInstance.chart.canvas, eventName, handler); + }); + }, + getMaximumWidth = helpers.getMaximumWidth = function(domNode){ + var container = domNode.parentNode; + // TODO = check cross browser stuff with this. + return container.clientWidth; + }, + getMaximumHeight = helpers.getMaximumHeight = function(domNode){ + var container = domNode.parentNode; + // TODO = check cross browser stuff with this. + return container.clientHeight; + }, + getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support + retinaScale = helpers.retinaScale = function(chart){ + var ctx = chart.ctx, + width = chart.canvas.width, + height = chart.canvas.height; + + if (window.devicePixelRatio) { + ctx.canvas.style.width = width + "px"; + ctx.canvas.style.height = height + "px"; + ctx.canvas.height = height * window.devicePixelRatio; + ctx.canvas.width = width * window.devicePixelRatio; + ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + } + }, + //-- Canvas methods + clear = helpers.clear = function(chart){ + chart.ctx.clearRect(0,0,chart.width,chart.height); + }, + fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){ + return fontStyle + " " + pixelSize+"px " + fontFamily; + }, + longestText = helpers.longestText = function(ctx,font,arrayOfStrings){ + ctx.font = font; + var longest = 0; + each(arrayOfStrings,function(string){ + var textWidth = ctx.measureText(string).width; + longest = (textWidth > longest) ? textWidth : longest; + }); + return longest; + }, + drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){ + ctx.beginPath(); + ctx.moveTo(x + radius, y); + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); + }; + + + //Store a reference to each instance - allowing us to globally resize chart instances on window resize. + //Destroy method on the chart will remove the instance of the chart from this reference. + Chart.instances = {}; + + Chart.Type = function(data,options,chart){ + this.options = options; + this.chart = chart; + this.id = uid(); + //Add the chart instance to the global namespace + Chart.instances[this.id] = this; + + // Initialize is always called when a chart type is created + // By default it is a no op, but it should be extended + if (options.responsive){ + this.resize(); + } + this.initialize.call(this,data); + }; + + //Core methods that'll be a part of every chart type + extend(Chart.Type.prototype,{ + initialize : function(){return this;}, + clear : function(){ + clear(this.chart); + return this; + }, + stop : function(){ + // Stops any current animation loop occuring + cancelAnimFrame(this.animationFrame); + return this; + }, + resize : function(callback){ + this.stop(); + var canvas = this.chart.canvas, + newWidth = getMaximumWidth(this.chart.canvas), + newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas); + + canvas.width = this.chart.width = newWidth; + canvas.height = this.chart.height = newHeight; + + retinaScale(this.chart); + + if (typeof callback === "function"){ + callback.apply(this, Array.prototype.slice.call(arguments, 1)); + } + return this; + }, + reflow : noop, + render : function(reflow){ + if (reflow){ + this.reflow(); + } + if (this.options.animation && !reflow){ + helpers.animationLoop( + this.draw, + this.options.animationSteps, + this.options.animationEasing, + this.options.onAnimationProgress, + this.options.onAnimationComplete, + this + ); + } + else{ + this.draw(); + this.options.onAnimationComplete.call(this); + } + return this; + }, + generateLegend : function(){ + return template(this.options.legendTemplate,this); + }, + destroy : function(){ + this.clear(); + unbindEvents(this, this.events); + var canvas = this.chart.canvas; + + // Reset canvas height/width attributes starts a fresh with the canvas context + canvas.width = this.chart.width; + canvas.height = this.chart.height; + + // < IE9 doesn't support removeProperty + if (canvas.style.removeProperty) { + canvas.style.removeProperty('width'); + canvas.style.removeProperty('height'); + } else { + canvas.style.removeAttribute('width'); + canvas.style.removeAttribute('height'); + } + + delete Chart.instances[this.id]; + }, + showTooltip : function(ChartElements, forceRedraw){ + // Only redraw the chart if we've actually changed what we're hovering on. + if (typeof this.activeElements === 'undefined') this.activeElements = []; + + var isChanged = (function(Elements){ + var changed = false; + + if (Elements.length !== this.activeElements.length){ + changed = true; + return changed; + } + + each(Elements, function(element, index){ + if (element !== this.activeElements[index]){ + changed = true; + } + }, this); + return changed; + }).call(this, ChartElements); + + if (!isChanged && !forceRedraw){ + return; + } + else{ + this.activeElements = ChartElements; + } + this.draw(); + if(this.options.customTooltips){ + this.options.customTooltips(false); + } + if (ChartElements.length > 0){ + // If we have multiple datasets, show a MultiTooltip for all of the data points at that index + if (this.datasets && this.datasets.length > 1) { + var dataArray, + dataIndex; + + for (var i = this.datasets.length - 1; i >= 0; i--) { + dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments; + dataIndex = indexOf(dataArray, ChartElements[0]); + if (dataIndex !== -1){ + break; + } + } + var tooltipLabels = [], + tooltipColors = [], + medianPosition = (function(index) { + + // Get all the points at that particular index + var Elements = [], + dataCollection, + xPositions = [], + yPositions = [], + xMax, + yMax, + xMin, + yMin; + helpers.each(this.datasets, function(dataset){ + dataCollection = dataset.points || dataset.bars || dataset.segments; + if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){ + Elements.push(dataCollection[dataIndex]); + } + }); + + helpers.each(Elements, function(element) { + xPositions.push(element.x); + yPositions.push(element.y); + + + //Include any colour information about the element + tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element)); + tooltipColors.push({ + fill: element._saved.fillColor || element.fillColor, + stroke: element._saved.strokeColor || element.strokeColor + }); + + }, this); + + yMin = min(yPositions); + yMax = max(yPositions); + + xMin = min(xPositions); + xMax = max(xPositions); + + return { + x: (xMin > this.chart.width/2) ? xMin : xMax, + y: (yMin + yMax)/2 + }; + }).call(this, dataIndex); + + new Chart.MultiTooltip({ + x: medianPosition.x, + y: medianPosition.y, + xPadding: this.options.tooltipXPadding, + yPadding: this.options.tooltipYPadding, + xOffset: this.options.tooltipXOffset, + fillColor: this.options.tooltipFillColor, + textColor: this.options.tooltipFontColor, + fontFamily: this.options.tooltipFontFamily, + fontStyle: this.options.tooltipFontStyle, + fontSize: this.options.tooltipFontSize, + titleTextColor: this.options.tooltipTitleFontColor, + titleFontFamily: this.options.tooltipTitleFontFamily, + titleFontStyle: this.options.tooltipTitleFontStyle, + titleFontSize: this.options.tooltipTitleFontSize, + cornerRadius: this.options.tooltipCornerRadius, + labels: tooltipLabels, + legendColors: tooltipColors, + legendColorBackground : this.options.multiTooltipKeyBackground, + title: ChartElements[0].label, + chart: this.chart, + ctx: this.chart.ctx, + custom: this.options.customTooltips + }).draw(); + + } else { + each(ChartElements, function(Element) { + var tooltipPosition = Element.tooltipPosition(); + new Chart.Tooltip({ + x: Math.round(tooltipPosition.x), + y: Math.round(tooltipPosition.y), + xPadding: this.options.tooltipXPadding, + yPadding: this.options.tooltipYPadding, + fillColor: this.options.tooltipFillColor, + textColor: this.options.tooltipFontColor, + fontFamily: this.options.tooltipFontFamily, + fontStyle: this.options.tooltipFontStyle, + fontSize: this.options.tooltipFontSize, + caretHeight: this.options.tooltipCaretSize, + cornerRadius: this.options.tooltipCornerRadius, + text: template(this.options.tooltipTemplate, Element), + chart: this.chart, + custom: this.options.customTooltips + }).draw(); + }, this); + } + } + return this; + }, + toBase64Image : function(){ + return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); + } + }); + + Chart.Type.extend = function(extensions){ + + var parent = this; + + var ChartType = function(){ + return parent.apply(this,arguments); + }; + + //Copy the prototype object of the this class + ChartType.prototype = clone(parent.prototype); + //Now overwrite some of the properties in the base class with the new extensions + extend(ChartType.prototype, extensions); + + ChartType.extend = Chart.Type.extend; + + if (extensions.name || parent.prototype.name){ + + var chartName = extensions.name || parent.prototype.name; + //Assign any potential default values of the new chart type + + //If none are defined, we'll use a clone of the chart type this is being extended from. + //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart + //doesn't define some defaults of their own. + + var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {}; + + Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults); + + Chart.types[chartName] = ChartType; + + //Register this new chart type in the Chart prototype + Chart.prototype[chartName] = function(data,options){ + var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {}); + return new ChartType(data,config,this); + }; + } else{ + warn("Name not provided for this chart, so it hasn't been registered"); + } + return parent; + }; + + Chart.Element = function(configuration){ + extend(this,configuration); + this.initialize.apply(this,arguments); + this.save(); + }; + extend(Chart.Element.prototype,{ + initialize : function(){}, + restore : function(props){ + if (!props){ + extend(this,this._saved); + } else { + each(props,function(key){ + this[key] = this._saved[key]; + },this); + } + return this; + }, + save : function(){ + this._saved = clone(this); + delete this._saved._saved; + return this; + }, + update : function(newProps){ + each(newProps,function(value,key){ + this._saved[key] = this[key]; + this[key] = value; + },this); + return this; + }, + transition : function(props,ease){ + each(props,function(value,key){ + this[key] = ((value - this._saved[key]) * ease) + this._saved[key]; + },this); + return this; + }, + tooltipPosition : function(){ + return { + x : this.x, + y : this.y + }; + }, + hasValue: function(){ + return isNumber(this.value); + } + }); + + Chart.Element.extend = inherits; + + + Chart.Point = Chart.Element.extend({ + display: true, + inRange: function(chartX,chartY){ + var hitDetectionRange = this.hitDetectionRadius + this.radius; + return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2)); + }, + draw : function(){ + if (this.display){ + var ctx = this.ctx; + ctx.beginPath(); + + ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2); + ctx.closePath(); + + ctx.strokeStyle = this.strokeColor; + ctx.lineWidth = this.strokeWidth; + + ctx.fillStyle = this.fillColor; + + ctx.fill(); + ctx.stroke(); + } + + + //Quick debug for bezier curve splining + //Highlights control points and the line between them. + //Handy for dev - stripped in the min version. + + // ctx.save(); + // ctx.fillStyle = "black"; + // ctx.strokeStyle = "black" + // ctx.beginPath(); + // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2); + // ctx.fill(); + + // ctx.beginPath(); + // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2); + // ctx.fill(); + + // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y); + // ctx.lineTo(this.x, this.y); + // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y); + // ctx.stroke(); + + // ctx.restore(); + + + + } + }); + + Chart.Arc = Chart.Element.extend({ + inRange : function(chartX,chartY){ + + var pointRelativePosition = helpers.getAngleFromPoint(this, { + x: chartX, + y: chartY + }); + + //Check if within the range of the open/close angle + var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle), + withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius); + + return (betweenAngles && withinRadius); + //Ensure within the outside of the arc centre, but inside arc outer + }, + tooltipPosition : function(){ + var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2), + rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius; + return { + x : this.x + (Math.cos(centreAngle) * rangeFromCentre), + y : this.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + draw : function(animationPercent){ + + var easingDecimal = animationPercent || 1; + + var ctx = this.ctx; + + ctx.beginPath(); + + ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle); + + ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true); + + ctx.closePath(); + ctx.strokeStyle = this.strokeColor; + ctx.lineWidth = this.strokeWidth; + + ctx.fillStyle = this.fillColor; + + ctx.fill(); + ctx.lineJoin = 'bevel'; + + if (this.showStroke){ + ctx.stroke(); + } + } + }); + + Chart.Rectangle = Chart.Element.extend({ + draw : function(){ + var ctx = this.ctx, + halfWidth = this.width/2, + leftX = this.x - halfWidth, + rightX = this.x + halfWidth, + top = this.base - (this.base - this.y), + halfStroke = this.strokeWidth / 2; + + // Canvas doesn't allow us to stroke inside the width so we can + // adjust the sizes to fit if we're setting a stroke on the line + if (this.showStroke){ + leftX += halfStroke; + rightX -= halfStroke; + top += halfStroke; + } + + ctx.beginPath(); + + ctx.fillStyle = this.fillColor; + ctx.strokeStyle = this.strokeColor; + ctx.lineWidth = this.strokeWidth; + + // It'd be nice to keep this class totally generic to any rectangle + // and simply specify which border to miss out. + ctx.moveTo(leftX, this.base); + ctx.lineTo(leftX, top); + ctx.lineTo(rightX, top); + ctx.lineTo(rightX, this.base); + ctx.fill(); + if (this.showStroke){ + ctx.stroke(); + } + }, + height : function(){ + return this.base - this.y; + }, + inRange : function(chartX,chartY){ + return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base); + } + }); + + Chart.Tooltip = Chart.Element.extend({ + draw : function(){ + + var ctx = this.chart.ctx; + + ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); + + this.xAlign = "center"; + this.yAlign = "above"; + + //Distance between the actual element.y position and the start of the tooltip caret + var caretPadding = this.caretPadding = 2; + + var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding, + tooltipRectHeight = this.fontSize + 2*this.yPadding, + tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding; + + if (this.x + tooltipWidth/2 >this.chart.width){ + this.xAlign = "left"; + } else if (this.x - tooltipWidth/2 < 0){ + this.xAlign = "right"; + } + + if (this.y - tooltipHeight < 0){ + this.yAlign = "below"; + } + + + var tooltipX = this.x - tooltipWidth/2, + tooltipY = this.y - tooltipHeight; + + ctx.fillStyle = this.fillColor; + + // Custom Tooltips + if(this.custom){ + this.custom(this); + } + else{ + switch(this.yAlign) + { + case "above": + //Draw a caret above the x/y + ctx.beginPath(); + ctx.moveTo(this.x,this.y - caretPadding); + ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight)); + ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight)); + ctx.closePath(); + ctx.fill(); + break; + case "below": + tooltipY = this.y + caretPadding + this.caretHeight; + //Draw a caret below the x/y + ctx.beginPath(); + ctx.moveTo(this.x, this.y + caretPadding); + ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight); + ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight); + ctx.closePath(); + ctx.fill(); + break; + } + + switch(this.xAlign) + { + case "left": + tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight); + break; + case "right": + tooltipX = this.x - (this.cornerRadius + this.caretHeight); + break; + } + + drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius); + + ctx.fill(); + + ctx.fillStyle = this.textColor; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2); + } + } + }); + + Chart.MultiTooltip = Chart.Element.extend({ + initialize : function(){ + this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); + + this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily); + + this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5; + + this.ctx.font = this.titleFont; + + var titleWidth = this.ctx.measureText(this.title).width, + //Label has a legend square as well so account for this. + labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3, + longestTextWidth = max([labelWidth,titleWidth]); + + this.width = longestTextWidth + (this.xPadding*2); + + + var halfHeight = this.height/2; + + //Check to ensure the height will fit on the canvas + if (this.y - halfHeight < 0 ){ + this.y = halfHeight; + } else if (this.y + halfHeight > this.chart.height){ + this.y = this.chart.height - halfHeight; + } + + //Decide whether to align left or right based on position on canvas + if (this.x > this.chart.width/2){ + this.x -= this.xOffset + this.width; + } else { + this.x += this.xOffset; + } + + + }, + getLineHeight : function(index){ + var baseLineHeight = this.y - (this.height/2) + this.yPadding, + afterTitleIndex = index-1; + + //If the index is zero, we're getting the title + if (index === 0){ + return baseLineHeight + this.titleFontSize/2; + } else{ + return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5; + } + + }, + draw : function(){ + // Custom Tooltips + if(this.custom){ + this.custom(this); + } + else{ + drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius); + var ctx = this.ctx; + ctx.fillStyle = this.fillColor; + ctx.fill(); + ctx.closePath(); + + ctx.textAlign = "left"; + ctx.textBaseline = "middle"; + ctx.fillStyle = this.titleTextColor; + ctx.font = this.titleFont; + + ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0)); + + ctx.font = this.font; + helpers.each(this.labels,function(label,index){ + ctx.fillStyle = this.textColor; + ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1)); + + //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas) + //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); + //Instead we'll make a white filled block to put the legendColour palette over. + + ctx.fillStyle = this.legendColorBackground; + ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); + + ctx.fillStyle = this.legendColors[index].fill; + ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); + + + },this); + } + } + }); + + Chart.Scale = Chart.Element.extend({ + initialize : function(){ + this.fit(); + }, + buildYLabels : function(){ + this.yLabels = []; + + var stepDecimalPlaces = getDecimalPlaces(this.stepValue); + + for (var i=0; i<=this.steps; i++){ + this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)})); + } + this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0; + }, + addXLabel : function(label){ + this.xLabels.push(label); + this.valuesCount++; + this.fit(); + }, + removeXLabel : function(){ + this.xLabels.shift(); + this.valuesCount--; + this.fit(); + }, + // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use + fit: function(){ + // First we need the width of the yLabels, assuming the xLabels aren't rotated + + // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation + this.startPoint = (this.display) ? this.fontSize : 0; + this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels + + // Apply padding settings to the start and end point. + this.startPoint += this.padding; + this.endPoint -= this.padding; + + // Cache the starting height, so can determine if we need to recalculate the scale yAxis + var cachedHeight = this.endPoint - this.startPoint, + cachedYLabelWidth; + + // Build the current yLabels so we have an idea of what size they'll be to start + /* + * This sets what is returned from calculateScaleRange as static properties of this class: + * + this.steps; + this.stepValue; + this.min; + this.max; + * + */ + this.calculateYRange(cachedHeight); + + // With these properties set we can now build the array of yLabels + // and also the width of the largest yLabel + this.buildYLabels(); + + this.calculateXLabelRotation(); + + while((cachedHeight > this.endPoint - this.startPoint)){ + cachedHeight = this.endPoint - this.startPoint; + cachedYLabelWidth = this.yLabelWidth; + + this.calculateYRange(cachedHeight); + this.buildYLabels(); + + // Only go through the xLabel loop again if the yLabel width has changed + if (cachedYLabelWidth < this.yLabelWidth){ + this.calculateXLabelRotation(); + } + } + + }, + calculateXLabelRotation : function(){ + //Get the width of each grid by calculating the difference + //between x offsets between 0 and 1. + + this.ctx.font = this.font; + + var firstWidth = this.ctx.measureText(this.xLabels[0]).width, + lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width, + firstRotated, + lastRotated; + + + this.xScalePaddingRight = lastWidth/2 + 3; + this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10; + + this.xLabelRotation = 0; + if (this.display){ + var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels), + cosRotation, + firstRotatedWidth; + this.xLabelWidth = originalLabelWidth; + //Allow 3 pixels x2 padding either side for label readability + var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6; + + //Max label rotate should be 90 - also act as a loop counter + while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){ + cosRotation = Math.cos(toRadians(this.xLabelRotation)); + + firstRotated = cosRotation * firstWidth; + lastRotated = cosRotation * lastWidth; + + // We're right aligning the text now. + if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){ + this.xScalePaddingLeft = firstRotated + this.fontSize / 2; + } + this.xScalePaddingRight = this.fontSize/2; + + + this.xLabelRotation++; + this.xLabelWidth = cosRotation * originalLabelWidth; + + } + if (this.xLabelRotation > 0){ + this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3; + } + } + else{ + this.xLabelWidth = 0; + this.xScalePaddingRight = this.padding; + this.xScalePaddingLeft = this.padding; + } + + }, + // Needs to be overidden in each Chart type + // Otherwise we need to pass all the data into the scale class + calculateYRange: noop, + drawingArea: function(){ + return this.startPoint - this.endPoint; + }, + calculateY : function(value){ + var scalingFactor = this.drawingArea() / (this.min - this.max); + return this.endPoint - (scalingFactor * (value - this.min)); + }, + calculateX : function(index){ + var isRotated = (this.xLabelRotation > 0), + // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding, + innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight), + valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1), + valueOffset = (valueWidth * index) + this.xScalePaddingLeft; + + if (this.offsetGridLines){ + valueOffset += (valueWidth/2); + } + + return Math.round(valueOffset); + }, + update : function(newProps){ + helpers.extend(this, newProps); + this.fit(); + }, + draw : function(){ + var ctx = this.ctx, + yLabelGap = (this.endPoint - this.startPoint) / this.steps, + xStart = Math.round(this.xScalePaddingLeft); + if (this.display){ + ctx.fillStyle = this.textColor; + ctx.font = this.font; + each(this.yLabels,function(labelString,index){ + var yLabelCenter = this.endPoint - (yLabelGap * index), + linePositionY = Math.round(yLabelCenter), + drawHorizontalLine = this.showHorizontalLines; + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + if (this.showLabels){ + ctx.fillText(labelString,xStart - 10,yLabelCenter); + } + + // This is X axis, so draw it + if (index === 0 && !drawHorizontalLine){ + drawHorizontalLine = true; + } + + if (drawHorizontalLine){ + ctx.beginPath(); + } + + if (index > 0){ + // This is a grid line in the centre, so drop that + ctx.lineWidth = this.gridLineWidth; + ctx.strokeStyle = this.gridLineColor; + } else { + // This is the first line on the scale + ctx.lineWidth = this.lineWidth; + ctx.strokeStyle = this.lineColor; + } + + linePositionY += helpers.aliasPixel(ctx.lineWidth); + + if(drawHorizontalLine){ + ctx.moveTo(xStart, linePositionY); + ctx.lineTo(this.width, linePositionY); + ctx.stroke(); + ctx.closePath(); + } + + ctx.lineWidth = this.lineWidth; + ctx.strokeStyle = this.lineColor; + ctx.beginPath(); + ctx.moveTo(xStart - 5, linePositionY); + ctx.lineTo(xStart, linePositionY); + ctx.stroke(); + ctx.closePath(); + + },this); + + each(this.xLabels,function(label,index){ + var xPos = this.calculateX(index) + aliasPixel(this.lineWidth), + // Check to see if line/bar here and decide where to place the line + linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth), + isRotated = (this.xLabelRotation > 0), + drawVerticalLine = this.showVerticalLines; + + // This is Y axis, so draw it + if (index === 0 && !drawVerticalLine){ + drawVerticalLine = true; + } + + if (drawVerticalLine){ + ctx.beginPath(); + } + + if (index > 0){ + // This is a grid line in the centre, so drop that + ctx.lineWidth = this.gridLineWidth; + ctx.strokeStyle = this.gridLineColor; + } else { + // This is the first line on the scale + ctx.lineWidth = this.lineWidth; + ctx.strokeStyle = this.lineColor; + } + + if (drawVerticalLine){ + ctx.moveTo(linePos,this.endPoint); + ctx.lineTo(linePos,this.startPoint - 3); + ctx.stroke(); + ctx.closePath(); + } + + + ctx.lineWidth = this.lineWidth; + ctx.strokeStyle = this.lineColor; + + + // Small lines at the bottom of the base grid line + ctx.beginPath(); + ctx.moveTo(linePos,this.endPoint); + ctx.lineTo(linePos,this.endPoint + 5); + ctx.stroke(); + ctx.closePath(); + + ctx.save(); + ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8); + ctx.rotate(toRadians(this.xLabelRotation)*-1); + ctx.font = this.font; + ctx.textAlign = (isRotated) ? "right" : "center"; + ctx.textBaseline = (isRotated) ? "middle" : "top"; + ctx.fillText(label, 0, 0); + ctx.restore(); + },this); + + } + } + + }); + + Chart.RadialScale = Chart.Element.extend({ + initialize: function(){ + this.size = min([this.height, this.width]); + this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2); + }, + calculateCenterOffset: function(value){ + // Take into account half font size + the yPadding of the top value + var scalingFactor = this.drawingArea / (this.max - this.min); + + return (value - this.min) * scalingFactor; + }, + update : function(){ + if (!this.lineArc){ + this.setScaleSize(); + } else { + this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2); + } + this.buildYLabels(); + }, + buildYLabels: function(){ + this.yLabels = []; + + var stepDecimalPlaces = getDecimalPlaces(this.stepValue); + + for (var i=0; i<=this.steps; i++){ + this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)})); + } + }, + getCircumference : function(){ + return ((Math.PI*2) / this.valuesCount); + }, + setScaleSize: function(){ + /* + * Right, this is really confusing and there is a lot of maths going on here + * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 + * + * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif + * + * Solution: + * + * We assume the radius of the polygon is half the size of the canvas at first + * at each index we check if the text overlaps. + * + * Where it does, we store that angle and that index. + * + * After finding the largest index and angle we calculate how much we need to remove + * from the shape radius to move the point inwards by that x. + * + * We average the left and right distances to get the maximum shape radius that can fit in the box + * along with labels. + * + * Once we have that, we can find the centre point for the chart, by taking the x text protrusion + * on each side, removing that from the size, halving it and adding the left x protrusion width. + * + * This will mean we have a shape fitted to the canvas, as large as it can be with the labels + * and position it in the most space efficient manner + * + * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif + */ + + + // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. + // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points + var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]), + pointPosition, + i, + textWidth, + halfTextWidth, + furthestRight = this.width, + furthestRightIndex, + furthestRightAngle, + furthestLeft = 0, + furthestLeftIndex, + furthestLeftAngle, + xProtrusionLeft, + xProtrusionRight, + radiusReductionRight, + radiusReductionLeft, + maxWidthRadius; + this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily); + for (i=0;i furthestRight) { + furthestRight = pointPosition.x + halfTextWidth; + furthestRightIndex = i; + } + if (pointPosition.x - halfTextWidth < furthestLeft) { + furthestLeft = pointPosition.x - halfTextWidth; + furthestLeftIndex = i; + } + } + else if (i < this.valuesCount/2) { + // Less than half the values means we'll left align the text + if (pointPosition.x + textWidth > furthestRight) { + furthestRight = pointPosition.x + textWidth; + furthestRightIndex = i; + } + } + else if (i > this.valuesCount/2){ + // More than half the values means we'll right align the text + if (pointPosition.x - textWidth < furthestLeft) { + furthestLeft = pointPosition.x - textWidth; + furthestLeftIndex = i; + } + } + } + + xProtrusionLeft = furthestLeft; + + xProtrusionRight = Math.ceil(furthestRight - this.width); + + furthestRightAngle = this.getIndexAngle(furthestRightIndex); + + furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); + + radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2); + + radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2); + + // Ensure we actually need to reduce the size of the chart + radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0; + radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; + + this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2; + + //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) + this.setCenterPoint(radiusReductionLeft, radiusReductionRight); + + }, + setCenterPoint: function(leftMovement, rightMovement){ + + var maxRight = this.width - rightMovement - this.drawingArea, + maxLeft = leftMovement + this.drawingArea; + + this.xCenter = (maxLeft + maxRight)/2; + // Always vertically in the centre as the text height doesn't change + this.yCenter = (this.height/2); + }, + + getIndexAngle : function(index){ + var angleMultiplier = (Math.PI * 2) / this.valuesCount; + // Start from the top instead of right, so remove a quarter of the circle + + return index * angleMultiplier - (Math.PI/2); + }, + getPointPosition : function(index, distanceFromCenter){ + var thisAngle = this.getIndexAngle(index); + return { + x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, + y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter + }; + }, + draw: function(){ + if (this.display){ + var ctx = this.ctx; + each(this.yLabels, function(label, index){ + // Don't draw a centre value + if (index > 0){ + var yCenterOffset = index * (this.drawingArea/this.steps), + yHeight = this.yCenter - yCenterOffset, + pointPosition; + + // Draw circular lines around the scale + if (this.lineWidth > 0){ + ctx.strokeStyle = this.lineColor; + ctx.lineWidth = this.lineWidth; + + if(this.lineArc){ + ctx.beginPath(); + ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2); + ctx.closePath(); + ctx.stroke(); + } else{ + ctx.beginPath(); + for (var i=0;i= 0; i--) { + if (this.angleLineWidth > 0){ + var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max)); + ctx.beginPath(); + ctx.moveTo(this.xCenter, this.yCenter); + ctx.lineTo(outerPosition.x, outerPosition.y); + ctx.stroke(); + ctx.closePath(); + } + // Extra 3px out for some label spacing + var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5); + ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily); + ctx.fillStyle = this.pointLabelFontColor; + + var labelsCount = this.labels.length, + halfLabelsCount = this.labels.length/2, + quarterLabelsCount = halfLabelsCount/2, + upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), + exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); + if (i === 0){ + ctx.textAlign = 'center'; + } else if(i === halfLabelsCount){ + ctx.textAlign = 'center'; + } else if (i < halfLabelsCount){ + ctx.textAlign = 'left'; + } else { + ctx.textAlign = 'right'; + } + + // Set the correct text baseline based on outer positioning + if (exactQuarter){ + ctx.textBaseline = 'middle'; + } else if (upperHalf){ + ctx.textBaseline = 'bottom'; + } else { + ctx.textBaseline = 'top'; + } + + ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); + } + } + } + } + }); + + // Attach global event to resize each chart instance when the browser resizes + helpers.addEvent(window, "resize", (function(){ + // Basic debounce of resize function so it doesn't hurt performance when resizing browser. + var timeout; + return function(){ + clearTimeout(timeout); + timeout = setTimeout(function(){ + each(Chart.instances,function(instance){ + // If the responsive flag is set in the chart instance config + // Cascade the resize event down to the chart. + if (instance.options.responsive){ + instance.resize(instance.render, true); + } + }); + }, 50); + }; + })()); + + + if (amd) { + define(function(){ + return Chart; + }); + } else if (typeof module === 'object' && module.exports) { + module.exports = Chart; + } + + root.Chart = Chart; + + Chart.noConflict = function(){ + root.Chart = previous; + return Chart; + }; + +}).call(this); + +(function(){ + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + + var defaultConfig = { + //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value + scaleBeginAtZero : true, + + //Boolean - Whether grid lines are shown across the chart + scaleShowGridLines : true, + + //String - Colour of the grid lines + scaleGridLineColor : "rgba(0,0,0,.05)", + + //Number - Width of the grid lines + scaleGridLineWidth : 1, + + //Boolean - Whether to show horizontal lines (except X axis) + scaleShowHorizontalLines: true, + + //Boolean - Whether to show vertical lines (except Y axis) + scaleShowVerticalLines: true, + + //Boolean - If there is a stroke on each bar + barShowStroke : true, + + //Number - Pixel width of the bar stroke + barStrokeWidth : 2, + + //Number - Spacing between each of the X value sets + barValueSpacing : 5, + + //Number - Spacing between data sets within X values + barDatasetSpacing : 1, + + //String - A legend template + legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" + + }; + + + Chart.Type.extend({ + name: "Bar", + defaults : defaultConfig, + initialize: function(data){ + + //Expose options as a scope variable here so we can access it in the ScaleClass + var options = this.options; + + this.ScaleClass = Chart.Scale.extend({ + offsetGridLines : true, + calculateBarX : function(datasetCount, datasetIndex, barIndex){ + //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar + var xWidth = this.calculateBaseWidth(), + xAbsolute = this.calculateX(barIndex) - (xWidth/2), + barWidth = this.calculateBarWidth(datasetCount); + + return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2; + }, + calculateBaseWidth : function(){ + return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing); + }, + calculateBarWidth : function(datasetCount){ + //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset + var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing); + + return (baseWidth / datasetCount); + } + }); + + this.datasets = []; + + //Set up tooltip events on the chart + if (this.options.showTooltips){ + helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ + var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : []; + + this.eachBars(function(bar){ + bar.restore(['fillColor', 'strokeColor']); + }); + helpers.each(activeBars, function(activeBar){ + activeBar.fillColor = activeBar.highlightFill; + activeBar.strokeColor = activeBar.highlightStroke; + }); + this.showTooltip(activeBars); + }); + } + + //Declare the extension of the default point, to cater for the options passed in to the constructor + this.BarClass = Chart.Rectangle.extend({ + strokeWidth : this.options.barStrokeWidth, + showStroke : this.options.barShowStroke, + ctx : this.chart.ctx + }); + + //Iterate through each of the datasets, and build this into a property of the chart + helpers.each(data.datasets,function(dataset,datasetIndex){ + + var datasetObject = { + label : dataset.label || null, + fillColor : dataset.fillColor, + strokeColor : dataset.strokeColor, + bars : [] + }; + + this.datasets.push(datasetObject); + + helpers.each(dataset.data,function(dataPoint,index){ + //Add a new point for each piece of data, passing any required data to draw. + datasetObject.bars.push(new this.BarClass({ + value : dataPoint, + label : data.labels[index], + datasetLabel: dataset.label, + strokeColor : dataset.strokeColor, + fillColor : dataset.fillColor, + highlightFill : dataset.highlightFill || dataset.fillColor, + highlightStroke : dataset.highlightStroke || dataset.strokeColor + })); + },this); + + },this); + + this.buildScale(data.labels); + + this.BarClass.prototype.base = this.scale.endPoint; + + this.eachBars(function(bar, index, datasetIndex){ + helpers.extend(bar, { + width : this.scale.calculateBarWidth(this.datasets.length), + x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index), + y: this.scale.endPoint + }); + bar.save(); + }, this); + + this.render(); + }, + update : function(){ + this.scale.update(); + // Reset any highlight colours before updating. + helpers.each(this.activeElements, function(activeElement){ + activeElement.restore(['fillColor', 'strokeColor']); + }); + + this.eachBars(function(bar){ + bar.save(); + }); + this.render(); + }, + eachBars : function(callback){ + helpers.each(this.datasets,function(dataset, datasetIndex){ + helpers.each(dataset.bars, callback, this, datasetIndex); + },this); + }, + getBarsAtEvent : function(e){ + var barsArray = [], + eventPosition = helpers.getRelativePosition(e), + datasetIterator = function(dataset){ + barsArray.push(dataset.bars[barIndex]); + }, + barIndex; + + for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) { + for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) { + if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){ + helpers.each(this.datasets, datasetIterator); + return barsArray; + } + } + } + + return barsArray; + }, + buildScale : function(labels){ + var self = this; + + var dataTotal = function(){ + var values = []; + self.eachBars(function(bar){ + values.push(bar.value); + }); + return values; + }; + + var scaleOptions = { + templateString : this.options.scaleLabel, + height : this.chart.height, + width : this.chart.width, + ctx : this.chart.ctx, + textColor : this.options.scaleFontColor, + fontSize : this.options.scaleFontSize, + fontStyle : this.options.scaleFontStyle, + fontFamily : this.options.scaleFontFamily, + valuesCount : labels.length, + beginAtZero : this.options.scaleBeginAtZero, + integersOnly : this.options.scaleIntegersOnly, + calculateYRange: function(currentHeight){ + var updatedRanges = helpers.calculateScaleRange( + dataTotal(), + currentHeight, + this.fontSize, + this.beginAtZero, + this.integersOnly + ); + helpers.extend(this, updatedRanges); + }, + xLabels : labels, + font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), + lineWidth : this.options.scaleLineWidth, + lineColor : this.options.scaleLineColor, + showHorizontalLines : this.options.scaleShowHorizontalLines, + showVerticalLines : this.options.scaleShowVerticalLines, + gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, + gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", + padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0, + showLabels : this.options.scaleShowLabels, + display : this.options.showScale + }; + + if (this.options.scaleOverride){ + helpers.extend(scaleOptions, { + calculateYRange: helpers.noop, + steps: this.options.scaleSteps, + stepValue: this.options.scaleStepWidth, + min: this.options.scaleStartValue, + max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) + }); + } + + this.scale = new this.ScaleClass(scaleOptions); + }, + addData : function(valuesArray,label){ + //Map the values array for each of the datasets + helpers.each(valuesArray,function(value,datasetIndex){ + //Add a new point for each piece of data, passing any required data to draw. + this.datasets[datasetIndex].bars.push(new this.BarClass({ + value : value, + label : label, + x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1), + y: this.scale.endPoint, + width : this.scale.calculateBarWidth(this.datasets.length), + base : this.scale.endPoint, + strokeColor : this.datasets[datasetIndex].strokeColor, + fillColor : this.datasets[datasetIndex].fillColor + })); + },this); + + this.scale.addXLabel(label); + //Then re-render the chart. + this.update(); + }, + removeData : function(){ + this.scale.removeXLabel(); + //Then re-render the chart. + helpers.each(this.datasets,function(dataset){ + dataset.bars.shift(); + },this); + this.update(); + }, + reflow : function(){ + helpers.extend(this.BarClass.prototype,{ + y: this.scale.endPoint, + base : this.scale.endPoint + }); + var newScaleProps = helpers.extend({ + height : this.chart.height, + width : this.chart.width + }); + this.scale.update(newScaleProps); + }, + draw : function(ease){ + var easingDecimal = ease || 1; + this.clear(); + + var ctx = this.chart.ctx; + + this.scale.draw(easingDecimal); + + //Draw all the bars for each dataset + helpers.each(this.datasets,function(dataset,datasetIndex){ + helpers.each(dataset.bars,function(bar,index){ + if (bar.hasValue()){ + bar.base = this.scale.endPoint; + //Transition then draw + bar.transition({ + x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index), + y : this.scale.calculateY(bar.value), + width : this.scale.calculateBarWidth(this.datasets.length) + }, easingDecimal).draw(); + } + },this); + + },this); + } + }); + + +}).call(this); + +(function(){ + "use strict"; + + var root = this, + Chart = root.Chart, + //Cache a local reference to Chart.helpers + helpers = Chart.helpers; + + var defaultConfig = { + //Boolean - Whether we should show a stroke on each segment + segmentShowStroke : true, + + //String - The colour of each segment stroke + segmentStrokeColor : "#fff", + + //Number - The width of each segment stroke + segmentStrokeWidth : 2, + + //The percentage of the chart that we cut out of the middle. + percentageInnerCutout : 50, + + //Number - Amount of animation steps + animationSteps : 100, + + //String - Animation easing effect + animationEasing : "easeOutBounce", + + //Boolean - Whether we animate the rotation of the Doughnut + animateRotate : true, + + //Boolean - Whether we animate scaling the Doughnut from the centre + animateScale : false, + + //String - A legend template + legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
" + + }; + + + Chart.Type.extend({ + //Passing in a name registers this chart in the Chart namespace + name: "Doughnut", + //Providing a defaults will also register the deafults in the chart namespace + defaults : defaultConfig, + //Initialize is fired when the chart is initialized - Data is passed in as a parameter + //Config is automatically merged by the core of Chart.js, and is available at this.options + initialize: function(data){ + + //Declare segments as a static property to prevent inheriting across the Chart type prototype + this.segments = []; + this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; + + this.SegmentArc = Chart.Arc.extend({ + ctx : this.chart.ctx, + x : this.chart.width/2, + y : this.chart.height/2 + }); + + //Set up tooltip events on the chart + if (this.options.showTooltips){ + helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ + var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; + + helpers.each(this.segments,function(segment){ + segment.restore(["fillColor"]); + }); + helpers.each(activeSegments,function(activeSegment){ + activeSegment.fillColor = activeSegment.highlightColor; + }); + this.showTooltip(activeSegments); + }); + } + this.calculateTotal(data); + + helpers.each(data,function(datapoint, index){ + this.addData(datapoint, index, true); + },this); + + this.render(); + }, + getSegmentsAtEvent : function(e){ + var segmentsArray = []; + + var location = helpers.getRelativePosition(e); + + helpers.each(this.segments,function(segment){ + if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); + },this); + return segmentsArray; + }, + addData : function(segment, atIndex, silent){ + var index = atIndex || this.segments.length; + this.segments.splice(index, 0, new this.SegmentArc({ + value : segment.value, + outerRadius : (this.options.animateScale) ? 0 : this.outerRadius, + innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout, + fillColor : segment.color, + highlightColor : segment.highlight || segment.color, + showStroke : this.options.segmentShowStroke, + strokeWidth : this.options.segmentStrokeWidth, + strokeColor : this.options.segmentStrokeColor, + startAngle : Math.PI * 1.5, + circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value), + label : segment.label + })); + if (!silent){ + this.reflow(); + this.update(); + } + }, + calculateCircumference : function(value){ + return (Math.PI*2)*(Math.abs(value) / this.total); + }, + calculateTotal : function(data){ + this.total = 0; + helpers.each(data,function(segment){ + this.total += Math.abs(segment.value); + },this); + }, + update : function(){ + this.calculateTotal(this.segments); + + // Reset any highlight colours before updating. + helpers.each(this.activeElements, function(activeElement){ + activeElement.restore(['fillColor']); + }); + + helpers.each(this.segments,function(segment){ + segment.save(); + }); + this.render(); + }, + + removeData: function(atIndex){ + var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; + this.segments.splice(indexToDelete, 1); + this.reflow(); + this.update(); + }, + + reflow : function(){ + helpers.extend(this.SegmentArc.prototype,{ + x : this.chart.width/2, + y : this.chart.height/2 + }); + this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; + helpers.each(this.segments, function(segment){ + segment.update({ + outerRadius : this.outerRadius, + innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout + }); + }, this); + }, + draw : function(easeDecimal){ + var animDecimal = (easeDecimal) ? easeDecimal : 1; + this.clear(); + helpers.each(this.segments,function(segment,index){ + segment.transition({ + circumference : this.calculateCircumference(segment.value), + outerRadius : this.outerRadius, + innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout + },animDecimal); + + segment.endAngle = segment.startAngle + segment.circumference; + + segment.draw(); + if (index === 0){ + segment.startAngle = Math.PI * 1.5; + } + //Check to see if it's the last segment, if not get the next and update the start angle + if (index < this.segments.length-1){ + this.segments[index+1].startAngle = segment.endAngle; + } + },this); + + } + }); + + Chart.types.Doughnut.extend({ + name : "Pie", + defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0}) + }); + +}).call(this); +(function(){ + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + var defaultConfig = { + + ///Boolean - Whether grid lines are shown across the chart + scaleShowGridLines : true, + + //String - Colour of the grid lines + scaleGridLineColor : "rgba(0,0,0,.05)", + + //Number - Width of the grid lines + scaleGridLineWidth : 1, + + //Boolean - Whether to show horizontal lines (except X axis) + scaleShowHorizontalLines: true, + + //Boolean - Whether to show vertical lines (except Y axis) + scaleShowVerticalLines: true, + + //Boolean - Whether the line is curved between points + bezierCurve : true, + + //Number - Tension of the bezier curve between points + bezierCurveTension : 0.4, + + //Boolean - Whether to show a dot for each point + pointDot : true, + + //Number - Radius of each point dot in pixels + pointDotRadius : 4, + + //Number - Pixel width of point dot stroke + pointDotStrokeWidth : 1, + + //Number - amount extra to add to the radius to cater for hit detection outside the drawn point + pointHitDetectionRadius : 20, + + //Boolean - Whether to show a stroke for datasets + datasetStroke : true, + + //Number - Pixel width of dataset stroke + datasetStrokeWidth : 2, + + //Boolean - Whether to fill the dataset with a colour + datasetFill : true, + + //String - A legend template + legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" + + }; + + + Chart.Type.extend({ + name: "Line", + defaults : defaultConfig, + initialize: function(data){ + //Declare the extension of the default point, to cater for the options passed in to the constructor + this.PointClass = Chart.Point.extend({ + strokeWidth : this.options.pointDotStrokeWidth, + radius : this.options.pointDotRadius, + display: this.options.pointDot, + hitDetectionRadius : this.options.pointHitDetectionRadius, + ctx : this.chart.ctx, + inRange : function(mouseX){ + return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2)); + } + }); + + this.datasets = []; + + //Set up tooltip events on the chart + if (this.options.showTooltips){ + helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ + var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; + this.eachPoints(function(point){ + point.restore(['fillColor', 'strokeColor']); + }); + helpers.each(activePoints, function(activePoint){ + activePoint.fillColor = activePoint.highlightFill; + activePoint.strokeColor = activePoint.highlightStroke; + }); + this.showTooltip(activePoints); + }); + } + + //Iterate through each of the datasets, and build this into a property of the chart + helpers.each(data.datasets,function(dataset){ + + var datasetObject = { + label : dataset.label || null, + fillColor : dataset.fillColor, + strokeColor : dataset.strokeColor, + pointColor : dataset.pointColor, + pointStrokeColor : dataset.pointStrokeColor, + points : [] + }; + + this.datasets.push(datasetObject); + + + helpers.each(dataset.data,function(dataPoint,index){ + //Add a new point for each piece of data, passing any required data to draw. + datasetObject.points.push(new this.PointClass({ + value : dataPoint, + label : data.labels[index], + datasetLabel: dataset.label, + strokeColor : dataset.pointStrokeColor, + fillColor : dataset.pointColor, + highlightFill : dataset.pointHighlightFill || dataset.pointColor, + highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor + })); + },this); + + this.buildScale(data.labels); + + + this.eachPoints(function(point, index){ + helpers.extend(point, { + x: this.scale.calculateX(index), + y: this.scale.endPoint + }); + point.save(); + }, this); + + },this); + + + this.render(); + }, + update : function(){ + this.scale.update(); + // Reset any highlight colours before updating. + helpers.each(this.activeElements, function(activeElement){ + activeElement.restore(['fillColor', 'strokeColor']); + }); + this.eachPoints(function(point){ + point.save(); + }); + this.render(); + }, + eachPoints : function(callback){ + helpers.each(this.datasets,function(dataset){ + helpers.each(dataset.points,callback,this); + },this); + }, + getPointsAtEvent : function(e){ + var pointsArray = [], + eventPosition = helpers.getRelativePosition(e); + helpers.each(this.datasets,function(dataset){ + helpers.each(dataset.points,function(point){ + if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point); + }); + },this); + return pointsArray; + }, + buildScale : function(labels){ + var self = this; + + var dataTotal = function(){ + var values = []; + self.eachPoints(function(point){ + values.push(point.value); + }); + + return values; + }; + + var scaleOptions = { + templateString : this.options.scaleLabel, + height : this.chart.height, + width : this.chart.width, + ctx : this.chart.ctx, + textColor : this.options.scaleFontColor, + fontSize : this.options.scaleFontSize, + fontStyle : this.options.scaleFontStyle, + fontFamily : this.options.scaleFontFamily, + valuesCount : labels.length, + beginAtZero : this.options.scaleBeginAtZero, + integersOnly : this.options.scaleIntegersOnly, + calculateYRange : function(currentHeight){ + var updatedRanges = helpers.calculateScaleRange( + dataTotal(), + currentHeight, + this.fontSize, + this.beginAtZero, + this.integersOnly + ); + helpers.extend(this, updatedRanges); + }, + xLabels : labels, + font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), + lineWidth : this.options.scaleLineWidth, + lineColor : this.options.scaleLineColor, + showHorizontalLines : this.options.scaleShowHorizontalLines, + showVerticalLines : this.options.scaleShowVerticalLines, + gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, + gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", + padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth, + showLabels : this.options.scaleShowLabels, + display : this.options.showScale + }; + + if (this.options.scaleOverride){ + helpers.extend(scaleOptions, { + calculateYRange: helpers.noop, + steps: this.options.scaleSteps, + stepValue: this.options.scaleStepWidth, + min: this.options.scaleStartValue, + max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) + }); + } + + + this.scale = new Chart.Scale(scaleOptions); + }, + addData : function(valuesArray,label){ + //Map the values array for each of the datasets + + helpers.each(valuesArray,function(value,datasetIndex){ + //Add a new point for each piece of data, passing any required data to draw. + this.datasets[datasetIndex].points.push(new this.PointClass({ + value : value, + label : label, + x: this.scale.calculateX(this.scale.valuesCount+1), + y: this.scale.endPoint, + strokeColor : this.datasets[datasetIndex].pointStrokeColor, + fillColor : this.datasets[datasetIndex].pointColor + })); + },this); + + this.scale.addXLabel(label); + //Then re-render the chart. + this.update(); + }, + removeData : function(){ + this.scale.removeXLabel(); + //Then re-render the chart. + helpers.each(this.datasets,function(dataset){ + dataset.points.shift(); + },this); + this.update(); + }, + reflow : function(){ + var newScaleProps = helpers.extend({ + height : this.chart.height, + width : this.chart.width + }); + this.scale.update(newScaleProps); + }, + draw : function(ease){ + var easingDecimal = ease || 1; + this.clear(); + + var ctx = this.chart.ctx; + + // Some helper methods for getting the next/prev points + var hasValue = function(item){ + return item.value !== null; + }, + nextPoint = function(point, collection, index){ + return helpers.findNextWhere(collection, hasValue, index) || point; + }, + previousPoint = function(point, collection, index){ + return helpers.findPreviousWhere(collection, hasValue, index) || point; + }; + + this.scale.draw(easingDecimal); + + + helpers.each(this.datasets,function(dataset){ + var pointsWithValues = helpers.where(dataset.points, hasValue); + + //Transition each point first so that the line and point drawing isn't out of sync + //We can use this extra loop to calculate the control points of this dataset also in this loop + + helpers.each(dataset.points, function(point, index){ + if (point.hasValue()){ + point.transition({ + y : this.scale.calculateY(point.value), + x : this.scale.calculateX(index) + }, easingDecimal); + } + },this); + + + // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point + // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed + if (this.options.bezierCurve){ + helpers.each(pointsWithValues, function(point, index){ + var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0; + point.controlPoints = helpers.splineCurve( + previousPoint(point, pointsWithValues, index), + point, + nextPoint(point, pointsWithValues, index), + tension + ); + + // Prevent the bezier going outside of the bounds of the graph + + // Cap puter bezier handles to the upper/lower scale bounds + if (point.controlPoints.outer.y > this.scale.endPoint){ + point.controlPoints.outer.y = this.scale.endPoint; + } + else if (point.controlPoints.outer.y < this.scale.startPoint){ + point.controlPoints.outer.y = this.scale.startPoint; + } + + // Cap inner bezier handles to the upper/lower scale bounds + if (point.controlPoints.inner.y > this.scale.endPoint){ + point.controlPoints.inner.y = this.scale.endPoint; + } + else if (point.controlPoints.inner.y < this.scale.startPoint){ + point.controlPoints.inner.y = this.scale.startPoint; + } + },this); + } + + + //Draw the line between all the points + ctx.lineWidth = this.options.datasetStrokeWidth; + ctx.strokeStyle = dataset.strokeColor; + ctx.beginPath(); + + helpers.each(pointsWithValues, function(point, index){ + if (index === 0){ + ctx.moveTo(point.x, point.y); + } + else{ + if(this.options.bezierCurve){ + var previous = previousPoint(point, pointsWithValues, index); + + ctx.bezierCurveTo( + previous.controlPoints.outer.x, + previous.controlPoints.outer.y, + point.controlPoints.inner.x, + point.controlPoints.inner.y, + point.x, + point.y + ); + } + else{ + ctx.lineTo(point.x,point.y); + } + } + }, this); + + ctx.stroke(); + + if (this.options.datasetFill && pointsWithValues.length > 0){ + //Round off the line by going to the base of the chart, back to the start, then fill. + ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint); + ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint); + ctx.fillStyle = dataset.fillColor; + ctx.closePath(); + ctx.fill(); + } + + //Now draw the points over the line + //A little inefficient double looping, but better than the line + //lagging behind the point positions + helpers.each(pointsWithValues,function(point){ + point.draw(); + }); + },this); + } + }); + + +}).call(this); + +(function(){ + "use strict"; + + var root = this, + Chart = root.Chart, + //Cache a local reference to Chart.helpers + helpers = Chart.helpers; + + var defaultConfig = { + //Boolean - Show a backdrop to the scale label + scaleShowLabelBackdrop : true, + + //String - The colour of the label backdrop + scaleBackdropColor : "rgba(255,255,255,0.75)", + + // Boolean - Whether the scale should begin at zero + scaleBeginAtZero : true, + + //Number - The backdrop padding above & below the label in pixels + scaleBackdropPaddingY : 2, + + //Number - The backdrop padding to the side of the label in pixels + scaleBackdropPaddingX : 2, + + //Boolean - Show line for each value in the scale + scaleShowLine : true, + + //Boolean - Stroke a line around each segment in the chart + segmentShowStroke : true, + + //String - The colour of the stroke on each segement. + segmentStrokeColor : "#fff", + + //Number - The width of the stroke value in pixels + segmentStrokeWidth : 2, + + //Number - Amount of animation steps + animationSteps : 100, + + //String - Animation easing effect. + animationEasing : "easeOutBounce", + + //Boolean - Whether to animate the rotation of the chart + animateRotate : true, + + //Boolean - Whether to animate scaling the chart from the centre + animateScale : false, + + //String - A legend template + legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
" + }; + + + Chart.Type.extend({ + //Passing in a name registers this chart in the Chart namespace + name: "PolarArea", + //Providing a defaults will also register the deafults in the chart namespace + defaults : defaultConfig, + //Initialize is fired when the chart is initialized - Data is passed in as a parameter + //Config is automatically merged by the core of Chart.js, and is available at this.options + initialize: function(data){ + this.segments = []; + //Declare segment class as a chart instance specific class, so it can share props for this instance + this.SegmentArc = Chart.Arc.extend({ + showStroke : this.options.segmentShowStroke, + strokeWidth : this.options.segmentStrokeWidth, + strokeColor : this.options.segmentStrokeColor, + ctx : this.chart.ctx, + innerRadius : 0, + x : this.chart.width/2, + y : this.chart.height/2 + }); + this.scale = new Chart.RadialScale({ + display: this.options.showScale, + fontStyle: this.options.scaleFontStyle, + fontSize: this.options.scaleFontSize, + fontFamily: this.options.scaleFontFamily, + fontColor: this.options.scaleFontColor, + showLabels: this.options.scaleShowLabels, + showLabelBackdrop: this.options.scaleShowLabelBackdrop, + backdropColor: this.options.scaleBackdropColor, + backdropPaddingY : this.options.scaleBackdropPaddingY, + backdropPaddingX: this.options.scaleBackdropPaddingX, + lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, + lineColor: this.options.scaleLineColor, + lineArc: true, + width: this.chart.width, + height: this.chart.height, + xCenter: this.chart.width/2, + yCenter: this.chart.height/2, + ctx : this.chart.ctx, + templateString: this.options.scaleLabel, + valuesCount: data.length + }); + + this.updateScaleRange(data); + + this.scale.update(); + + helpers.each(data,function(segment,index){ + this.addData(segment,index,true); + },this); + + //Set up tooltip events on the chart + if (this.options.showTooltips){ + helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ + var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; + helpers.each(this.segments,function(segment){ + segment.restore(["fillColor"]); + }); + helpers.each(activeSegments,function(activeSegment){ + activeSegment.fillColor = activeSegment.highlightColor; + }); + this.showTooltip(activeSegments); + }); + } + + this.render(); + }, + getSegmentsAtEvent : function(e){ + var segmentsArray = []; + + var location = helpers.getRelativePosition(e); + + helpers.each(this.segments,function(segment){ + if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); + },this); + return segmentsArray; + }, + addData : function(segment, atIndex, silent){ + var index = atIndex || this.segments.length; + + this.segments.splice(index, 0, new this.SegmentArc({ + fillColor: segment.color, + highlightColor: segment.highlight || segment.color, + label: segment.label, + value: segment.value, + outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value), + circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(), + startAngle: Math.PI * 1.5 + })); + if (!silent){ + this.reflow(); + this.update(); + } + }, + removeData: function(atIndex){ + var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; + this.segments.splice(indexToDelete, 1); + this.reflow(); + this.update(); + }, + calculateTotal: function(data){ + this.total = 0; + helpers.each(data,function(segment){ + this.total += segment.value; + },this); + this.scale.valuesCount = this.segments.length; + }, + updateScaleRange: function(datapoints){ + var valuesArray = []; + helpers.each(datapoints,function(segment){ + valuesArray.push(segment.value); + }); + + var scaleSizes = (this.options.scaleOverride) ? + { + steps: this.options.scaleSteps, + stepValue: this.options.scaleStepWidth, + min: this.options.scaleStartValue, + max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) + } : + helpers.calculateScaleRange( + valuesArray, + helpers.min([this.chart.width, this.chart.height])/2, + this.options.scaleFontSize, + this.options.scaleBeginAtZero, + this.options.scaleIntegersOnly + ); + + helpers.extend( + this.scale, + scaleSizes, + { + size: helpers.min([this.chart.width, this.chart.height]), + xCenter: this.chart.width/2, + yCenter: this.chart.height/2 + } + ); + + }, + update : function(){ + this.calculateTotal(this.segments); + + helpers.each(this.segments,function(segment){ + segment.save(); + }); + + this.reflow(); + this.render(); + }, + reflow : function(){ + helpers.extend(this.SegmentArc.prototype,{ + x : this.chart.width/2, + y : this.chart.height/2 + }); + this.updateScaleRange(this.segments); + this.scale.update(); + + helpers.extend(this.scale,{ + xCenter: this.chart.width/2, + yCenter: this.chart.height/2 + }); + + helpers.each(this.segments, function(segment){ + segment.update({ + outerRadius : this.scale.calculateCenterOffset(segment.value) + }); + }, this); + + }, + draw : function(ease){ + var easingDecimal = ease || 1; + //Clear & draw the canvas + this.clear(); + helpers.each(this.segments,function(segment, index){ + segment.transition({ + circumference : this.scale.getCircumference(), + outerRadius : this.scale.calculateCenterOffset(segment.value) + },easingDecimal); + + segment.endAngle = segment.startAngle + segment.circumference; + + // If we've removed the first segment we need to set the first one to + // start at the top. + if (index === 0){ + segment.startAngle = Math.PI * 1.5; + } + + //Check to see if it's the last segment, if not get the next and update the start angle + if (index < this.segments.length - 1){ + this.segments[index+1].startAngle = segment.endAngle; + } + segment.draw(); + }, this); + this.scale.draw(); + } + }); + +}).call(this); +(function(){ + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + + + Chart.Type.extend({ + name: "Radar", + defaults:{ + //Boolean - Whether to show lines for each scale point + scaleShowLine : true, + + //Boolean - Whether we show the angle lines out of the radar + angleShowLineOut : true, + + //Boolean - Whether to show labels on the scale + scaleShowLabels : false, + + // Boolean - Whether the scale should begin at zero + scaleBeginAtZero : true, + + //String - Colour of the angle line + angleLineColor : "rgba(0,0,0,.1)", + + //Number - Pixel width of the angle line + angleLineWidth : 1, + + //String - Point label font declaration + pointLabelFontFamily : "'Arial'", + + //String - Point label font weight + pointLabelFontStyle : "normal", + + //Number - Point label font size in pixels + pointLabelFontSize : 10, + + //String - Point label font colour + pointLabelFontColor : "#666", + + //Boolean - Whether to show a dot for each point + pointDot : true, + + //Number - Radius of each point dot in pixels + pointDotRadius : 3, + + //Number - Pixel width of point dot stroke + pointDotStrokeWidth : 1, + + //Number - amount extra to add to the radius to cater for hit detection outside the drawn point + pointHitDetectionRadius : 20, + + //Boolean - Whether to show a stroke for datasets + datasetStroke : true, + + //Number - Pixel width of dataset stroke + datasetStrokeWidth : 2, + + //Boolean - Whether to fill the dataset with a colour + datasetFill : true, + + //String - A legend template + legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" + + }, + + initialize: function(data){ + this.PointClass = Chart.Point.extend({ + strokeWidth : this.options.pointDotStrokeWidth, + radius : this.options.pointDotRadius, + display: this.options.pointDot, + hitDetectionRadius : this.options.pointHitDetectionRadius, + ctx : this.chart.ctx + }); + + this.datasets = []; + + this.buildScale(data); + + //Set up tooltip events on the chart + if (this.options.showTooltips){ + helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ + var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; + + this.eachPoints(function(point){ + point.restore(['fillColor', 'strokeColor']); + }); + helpers.each(activePointsCollection, function(activePoint){ + activePoint.fillColor = activePoint.highlightFill; + activePoint.strokeColor = activePoint.highlightStroke; + }); + + this.showTooltip(activePointsCollection); + }); + } + + //Iterate through each of the datasets, and build this into a property of the chart + helpers.each(data.datasets,function(dataset){ + + var datasetObject = { + label: dataset.label || null, + fillColor : dataset.fillColor, + strokeColor : dataset.strokeColor, + pointColor : dataset.pointColor, + pointStrokeColor : dataset.pointStrokeColor, + points : [] + }; + + this.datasets.push(datasetObject); + + helpers.each(dataset.data,function(dataPoint,index){ + //Add a new point for each piece of data, passing any required data to draw. + var pointPosition; + if (!this.scale.animation){ + pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint)); + } + datasetObject.points.push(new this.PointClass({ + value : dataPoint, + label : data.labels[index], + datasetLabel: dataset.label, + x: (this.options.animation) ? this.scale.xCenter : pointPosition.x, + y: (this.options.animation) ? this.scale.yCenter : pointPosition.y, + strokeColor : dataset.pointStrokeColor, + fillColor : dataset.pointColor, + highlightFill : dataset.pointHighlightFill || dataset.pointColor, + highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor + })); + },this); + + },this); + + this.render(); + }, + eachPoints : function(callback){ + helpers.each(this.datasets,function(dataset){ + helpers.each(dataset.points,callback,this); + },this); + }, + + getPointsAtEvent : function(evt){ + var mousePosition = helpers.getRelativePosition(evt), + fromCenter = helpers.getAngleFromPoint({ + x: this.scale.xCenter, + y: this.scale.yCenter + }, mousePosition); + + var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount, + pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex), + activePointsCollection = []; + + // If we're at the top, make the pointIndex 0 to get the first of the array. + if (pointIndex >= this.scale.valuesCount || pointIndex < 0){ + pointIndex = 0; + } + + if (fromCenter.distance <= this.scale.drawingArea){ + helpers.each(this.datasets, function(dataset){ + activePointsCollection.push(dataset.points[pointIndex]); + }); + } + + return activePointsCollection; + }, + + buildScale : function(data){ + this.scale = new Chart.RadialScale({ + display: this.options.showScale, + fontStyle: this.options.scaleFontStyle, + fontSize: this.options.scaleFontSize, + fontFamily: this.options.scaleFontFamily, + fontColor: this.options.scaleFontColor, + showLabels: this.options.scaleShowLabels, + showLabelBackdrop: this.options.scaleShowLabelBackdrop, + backdropColor: this.options.scaleBackdropColor, + backdropPaddingY : this.options.scaleBackdropPaddingY, + backdropPaddingX: this.options.scaleBackdropPaddingX, + lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, + lineColor: this.options.scaleLineColor, + angleLineColor : this.options.angleLineColor, + angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0, + // Point labels at the edge of each line + pointLabelFontColor : this.options.pointLabelFontColor, + pointLabelFontSize : this.options.pointLabelFontSize, + pointLabelFontFamily : this.options.pointLabelFontFamily, + pointLabelFontStyle : this.options.pointLabelFontStyle, + height : this.chart.height, + width: this.chart.width, + xCenter: this.chart.width/2, + yCenter: this.chart.height/2, + ctx : this.chart.ctx, + templateString: this.options.scaleLabel, + labels: data.labels, + valuesCount: data.datasets[0].data.length + }); + + this.scale.setScaleSize(); + this.updateScaleRange(data.datasets); + this.scale.buildYLabels(); + }, + updateScaleRange: function(datasets){ + var valuesArray = (function(){ + var totalDataArray = []; + helpers.each(datasets,function(dataset){ + if (dataset.data){ + totalDataArray = totalDataArray.concat(dataset.data); + } + else { + helpers.each(dataset.points, function(point){ + totalDataArray.push(point.value); + }); + } + }); + return totalDataArray; + })(); + + + var scaleSizes = (this.options.scaleOverride) ? + { + steps: this.options.scaleSteps, + stepValue: this.options.scaleStepWidth, + min: this.options.scaleStartValue, + max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) + } : + helpers.calculateScaleRange( + valuesArray, + helpers.min([this.chart.width, this.chart.height])/2, + this.options.scaleFontSize, + this.options.scaleBeginAtZero, + this.options.scaleIntegersOnly + ); + + helpers.extend( + this.scale, + scaleSizes + ); + + }, + addData : function(valuesArray,label){ + //Map the values array for each of the datasets + this.scale.valuesCount++; + helpers.each(valuesArray,function(value,datasetIndex){ + var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value)); + this.datasets[datasetIndex].points.push(new this.PointClass({ + value : value, + label : label, + x: pointPosition.x, + y: pointPosition.y, + strokeColor : this.datasets[datasetIndex].pointStrokeColor, + fillColor : this.datasets[datasetIndex].pointColor + })); + },this); + + this.scale.labels.push(label); + + this.reflow(); + + this.update(); + }, + removeData : function(){ + this.scale.valuesCount--; + this.scale.labels.shift(); + helpers.each(this.datasets,function(dataset){ + dataset.points.shift(); + },this); + this.reflow(); + this.update(); + }, + update : function(){ + this.eachPoints(function(point){ + point.save(); + }); + this.reflow(); + this.render(); + }, + reflow: function(){ + helpers.extend(this.scale, { + width : this.chart.width, + height: this.chart.height, + size : helpers.min([this.chart.width, this.chart.height]), + xCenter: this.chart.width/2, + yCenter: this.chart.height/2 + }); + this.updateScaleRange(this.datasets); + this.scale.setScaleSize(); + this.scale.buildYLabels(); + }, + draw : function(ease){ + var easeDecimal = ease || 1, + ctx = this.chart.ctx; + this.clear(); + this.scale.draw(); + + helpers.each(this.datasets,function(dataset){ + + //Transition each point first so that the line and point drawing isn't out of sync + helpers.each(dataset.points,function(point,index){ + if (point.hasValue()){ + point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal); + } + },this); + + + + //Draw the line between all the points + ctx.lineWidth = this.options.datasetStrokeWidth; + ctx.strokeStyle = dataset.strokeColor; + ctx.beginPath(); + helpers.each(dataset.points,function(point,index){ + if (index === 0){ + ctx.moveTo(point.x,point.y); + } + else{ + ctx.lineTo(point.x,point.y); + } + },this); + ctx.closePath(); + ctx.stroke(); + + ctx.fillStyle = dataset.fillColor; + ctx.fill(); + + //Now draw the points over the line + //A little inefficient double looping, but better than the line + //lagging behind the point positions + helpers.each(dataset.points,function(point){ + if (point.hasValue()){ + point.draw(); + } + }); + + },this); + + } + + }); + + + + + +}).call(this); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..108b4f8 --- /dev/null +++ b/static/style.css @@ -0,0 +1,132 @@ +html, body { + margin: 0; + background-color: #fff; + font-family: verdana; + font-size: 14px; +} + +h1.title, h2.title, h3.title { + margin: 0px; + text-align: center; + color: #fff; +} + +h1.title { + padding: 0.5em 0.5em; + background-color: #24CF5F; +} + +h2.title { + padding: 0.5em 0.5em; + background-color: #60DF8D; + margin-bottom: 1em; +} + +h3.title { + color: #000; + background-color: #24CF5F; + padding: 5px 5px; + margin-bottom: 0.5em; +} + +nav { + margin: 0px; + padding: 10px 10px; + margin-top: 0px; + background-color: #000; + text-align: center; + color: #fff; +} + +nav a { + color: #fff; +} + +form.time_range { + width: 100%; + padding-bottom: 10px; + text-align: center; +} + +canvas.aggregate { + text-align: center; +} + +footer { + margin-top: 1em; + background-color: #CDF4DB; + width: 100%; + bottom: 0; + padding: 1em 0em 1em 0em; + text-align: center; +} + +table { + width: 100%; + align: center; +} + +table, th, td { + border: 1px; + border-style: solid; + border-color: #24CF5F; + border-collapse: collapse; +} + +th { + background-color: #60DF8D; + padding: 0.5em 0.5em; +} + +td { + padding: 0.5em 0.5em; +} + +/* DOCUMENTATION STARTS */ +h4.endpoint_documentation { + padding: 0.5em 0.5em; + background-color: #DDD; + padding-left: 2em; +} + +h5.endpoint_documentation { + padding: 0.5em 0.5em; + background-color: #CCC; + padding-left: 3em; + font-size: 110%; +} + +em.endpoint_documentation { + padding: 1em 1em; + font-style: normal; + font-weight: bold; +} + +div.endpoint_documentation { + padding: 1em 3em; +} + +ul.endpoint_documentation { + padding: 0.5em 3em; +} + + li.argument_element { + margin-left: 1em; +} + +em.argument { + color: red; +} + +/* DOCUMENTATION ENDS */ + +div.alert { + padding: 1em 1em; + margin:0 auto; + margin-top: 1em; margin-bottom: 1em; + text-align: center; + width: 90%; + background-color: #FFCC00; + border-radius: 10px; + border: 2px solid red; +} diff --git a/templates/analytics/analytics_aggregate.html b/templates/analytics/analytics_aggregate.html new file mode 100644 index 0000000..905da07 --- /dev/null +++ b/templates/analytics/analytics_aggregate.html @@ -0,0 +1,31 @@ +{% include 'includes/header.html' %} + +{% import 'includes/forms.html' as forms %} +{% import 'includes/graphs.html' as graphs %} + +

Top {{title}}

+ +{{ forms.time_range(avail_start_time, avail_end_time, start_time, end_time) }} + +

Top 10 Graph

+ +{{ graphs.top_ten(time_series_times, time_series) }} + +

Table

+ + + + + + + + {% for f in flow_aggr %} + + + + + {% endfor %} + +
{{title}}sum_bytes
{{ f['key'] }}{{ f['sum_bytes'] }}
+ +{% include 'includes/footer.html' %} diff --git a/templates/analytics/find_prefix.html b/templates/analytics/find_prefix.html new file mode 100644 index 0000000..6b1d10d --- /dev/null +++ b/templates/analytics/find_prefix.html @@ -0,0 +1,47 @@ +{% include 'includes/header.html' %} + +

Search for {{query_name}}: {{query}}

+ +
+ date: + {% if query_name == 'ASN' %} + origin_as_only: + {% endif %} + {{query_name}}: + +
+ + + + + + + + + + +{% for neigh, prefixes in prefixes.iteritems() %} + {% for p in prefixes %} + + + + + + + + + {% endfor %} +{% endfor %} +
peer_ip_srcip_prefixbgp_nexthopas_pathlocal_prefcomms
{{ p['peer_ip_src'] }}{{ p['ip_prefix'] }}{{ p['bgp_nexthop'] }}{{ p['as_path'] }}{{ p['local_pref'] }}{{ p['comms'] }}
+ + +{% include 'includes/footer.html' %} diff --git a/templates/analytics/offloaded_traffic.html b/templates/analytics/offloaded_traffic.html new file mode 100644 index 0000000..6ae32ee --- /dev/null +++ b/templates/analytics/offloaded_traffic.html @@ -0,0 +1,21 @@ +{% include 'includes/header.html' %} + +

Offloaded Traffic

+ +{% import 'includes/forms.html' as forms %} +{{ forms.time_range(avail_start_time, avail_end_time, start_time, end_time, num_prefixes) }} + + + + + + + + + + + + +
TotalOffloaded%
{{ '{0:,d}'.format(total_bytes) }}{{ '{0:,d}'.format(offloaded_bytes) }}{{ percentage }}
+ +{% include 'includes/footer.html' %} diff --git a/templates/analytics/simulate.html b/templates/analytics/simulate.html new file mode 100644 index 0000000..1ce422b --- /dev/null +++ b/templates/analytics/simulate.html @@ -0,0 +1,30 @@ +{% include 'includes/header.html' %} + + + +

Run Simulation

+ +{% import 'includes/forms.html' as forms %} +{{ forms.time_range(avail_start_time, avail_end_time, start_time, end_time, num_prefixes) }} + +{% import 'includes/graphs.html' as graphs %} +{{ graphs.top_ten(time_series_times, time_series) }} + + + + + + + + + + {% for t in time_series['offloaded_bytes'] %} + + + + + + + {% endfor %} + +{% include 'includes/footer.html' %} diff --git a/templates/analytics/start_page.html b/templates/analytics/start_page.html new file mode 100644 index 0000000..db25f09 --- /dev/null +++ b/templates/analytics/start_page.html @@ -0,0 +1,36 @@ +{% include 'includes/header.html' %} + +

Analytics

+ +
datetimetotal_bytesoffloaded_bytes%
{{ time_series_times[loop.index0] }}{{ time_series['total_bytes'][loop.index0] }}{{ time_series['offloaded_bytes'][loop.index0] }}{{ time_series['offloaded_bytes'][loop.index0]*100/time_series['total_bytes'][loop.index0] }}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
URLDescription
{{ url_for('analytics_view_offloaded_traffic')}}Check how much traffic you would offload with a certain number of prefixes during a period of time.
{{ url_for('analytics_view_aggregate_per_as')}}With this analytics you can aggregate traffic per AS during a period of time.
{{ url_for('analytics_view_aggregate_per_prefix')}}With this analytics you can aggregate traffic per prefix during a period of time.
{{ url_for('analytics_view_simulate')}}Given a period of time and a maximum number of prefixes it will simulate how SIR should perform.
{{ url_for('analytics_view_find_prefix')}}Search for a prefix
{{ url_for('analytics_view_find_prefix_asn')}}Search for prefixes belonging and/or traversing and ASN
+ +{% include 'includes/footer.html' %} diff --git a/templates/api/start_page.html b/templates/api/start_page.html new file mode 100644 index 0000000..06c493d --- /dev/null +++ b/templates/api/start_page.html @@ -0,0 +1,108 @@ +{% include 'includes/header.html' %} + +

API v1.0 Documentation

+ + +You can interact with the agent using a RESTful API. This API gives you full access to the agent and its data. + +

Variables

+ +When reading this documentation you will find variables in two forms: + +
    +
  • <variable>: Variables that are between <> have to be replaced by their values in the URL. For example, /api/v1.0/variables/categories/<category> will turn into /api/v1.0/variables/categories/my_category.
  • +
  • variable: Variables that are NOT enclosed by <>: +
      +
    • If the method is a GET variables that are between <> have to be replaced by their values in the URL. For example, /api/v1.0/variables/categories/<category> will turn into /api/v1.0/variables/categories/my_category.
    • +
    • If the method is a POST or a PUT variables variables that are between <> have to sent as a JSON object.
    • +
  • +
+ +

Responses

+ +All the responses from the agent will be in JSON format and will include three sections: + +
    +
  • meta: Metainformation about the response. For example, request_time, length of the response or if there was any error.
  • +
  • parameters: The parameters used for the call.
  • +
  • result: The result of the call or a description of the error if there was any.
  • +
+ +For example, for the following call you will get the following response: + +
/api/v1.0/analytics/top_prefixes?limit_prefixes=10&start_time=2015-07-13T14:00&end_time=2015-07-14T14:00&net_masks=20,24
+

+  {
+    "meta": {
+      "error": false,
+      "length": 10,
+      "request_time": 11.99163
+    },
+    "parameters": {
+      "end_time": "2015-07-14T14:00",
+      "exclude_net_masks": false,
+      "limit_prefixes": 10,
+      "net_masks": "20,24",
+      "start_time": "2015-07-13T14:00"
+    },
+    "result": [
+      {
+        "as_dst": 43650,
+        "key": "194.14.177.0/24",
+        "sum_bytes": 650537594
+      },
+      ...
+      {
+        "as_dst": 197301,
+        "key": "80.71.128.0/20",
+        "sum_bytes": 5106731
+      }
+    ]
+  }
+ +{% for section, endpoints in documentation.iteritems() %} + +

{{ section }} Endpoint

+ + {% for endpoint in endpoints %} +

{{ endpoint['endpoint']}}

+ + {% for method in endpoint['methods'] %} +
{{method['method']}}
+ +Description: + +
+{{ method['description']|safe }} +
+ +Arguments: +
    + {% for key, value in method['arguments'].iteritems() %} +
  • {{key}}: {{value}} +
  • + {% endfor %} +
+ +Returns: +
    +
  • {{method['returns']}}
  • +
+ +Examples: +
    + {% for example in method['examples'] %} +
  • http://127.0.0.1:5000{{ endpoint['endpoint'] }}{{example}} +
  • + {% endfor %} +
+ + + {% endfor %} + + {% endfor %} + + +{% endfor %} + +{% include 'includes/footer.html' %} diff --git a/templates/basic/start_page.html b/templates/basic/start_page.html new file mode 100644 index 0000000..8744701 --- /dev/null +++ b/templates/basic/start_page.html @@ -0,0 +1,3 @@ +{% include 'includes/header.html' %} + +{% include 'includes/footer.html' %} diff --git a/templates/includes/footer.html b/templates/includes/footer.html new file mode 100644 index 0000000..ef94fd0 --- /dev/null +++ b/templates/includes/footer.html @@ -0,0 +1,4 @@ + +
+ A Unicorn productions. +
diff --git a/templates/includes/forms.html b/templates/includes/forms.html new file mode 100644 index 0000000..b039ec4 --- /dev/null +++ b/templates/includes/forms.html @@ -0,0 +1,9 @@ +{% macro time_range(avail_start_time, avail_end_time, start_time, end_time, num_prefixes=False) -%} +
+ start_time: + end_time: + {% if num_prefixes %}num_prefixes: {% endif %} + + +
+{%- endmacro %} diff --git a/templates/includes/graphs.html b/templates/includes/graphs.html new file mode 100644 index 0000000..337dd92 --- /dev/null +++ b/templates/includes/graphs.html @@ -0,0 +1,45 @@ +{% macro top_ten(time_series_times, time_series) -%} + + + + + +{%- endmacro %} diff --git a/templates/includes/header.html b/templates/includes/header.html new file mode 100644 index 0000000..0dad00e --- /dev/null +++ b/templates/includes/header.html @@ -0,0 +1,19 @@ + +SIR + + + + + + + + +

SIR - SDN Internet Router

+ + +
diff --git a/templates/variables/browse.html b/templates/variables/browse.html new file mode 100644 index 0000000..ed9dde8 --- /dev/null +++ b/templates/variables/browse.html @@ -0,0 +1,35 @@ +{% include 'includes/header.html' %} + +

Browse Variables

+ +
+ + +
+ + + + + + + {##} + + {% for variable in variables %} + + + + + {##} + +{% endfor%} +
categorynamecontentaction
{{ variable['category'] }}{{ variable['name'] }}{{ variable['content'] }}Edit|Delete
+ +{% include 'includes/footer.html' %} diff --git a/templates/variables/edit.html b/templates/variables/edit.html new file mode 100644 index 0000000..0549d86 --- /dev/null +++ b/templates/variables/edit.html @@ -0,0 +1,17 @@ +{% include 'includes/header.html' %} + +

Edit Variable

+ +
+ category: + name: + content: + +
+extra_vars: +
+ + +{% if saved == true %} +
Variable has been saved!!!
+{% endif %} diff --git a/variables/__init__.py b/variables/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/variables/api.py b/variables/api.py new file mode 100644 index 0000000..3b87bf3 --- /dev/null +++ b/variables/api.py @@ -0,0 +1,85 @@ +from flask import g +import helpers.api + + +def _variables_post(request): + # curl -i -H "Content-Type: application/json" -X POST -d '{"name": "test", "category": "development", "content": {"ads": "qwe", "asd": "zxc"}}' http://127.0.0.1:5000/api/v1.0/variables + db = getattr(g, 'db') + + name = request.json.get('name') + content = request.json.get('content') + category = request.json.get('category') + + db.put_variables(name, content, category) + + result = db.get_variable(category, name) + parameters = { + 'name': name, + 'content': content, + 'category': category, + } + return helpers.api.build_api_response(result, error=False, **parameters) + + +def _variables_get(request): + db = getattr(g, 'db') + result = db.get_variables() + parameters = {} + return helpers.api.build_api_response(result, error=False, **parameters) + + +def variables(request): + if request.method == 'GET': + return _variables_get(request) + elif request.method == 'POST': + return _variables_post(request) + + +def _api_variables_var_id_get(request, name): + db = getattr(g, 'db') + result = db.get_variable(name) + parameters = { + 'name': name, + } + return helpers.api.build_api_response(result, error=False, **parameters) + + +def api_variables_name(request, category, name): + db = getattr(g, 'db') + + if request.method == 'GET': + result = db.get_variable(category, name) + elif request.method == 'PUT': + # curl -i -H "Content-Type: application/json" -X PUT -d '{"name": "test", "category": "development", "content": {"ads": "qwe", "asd": "zxc"}}' http://127.0.0.1:5000/api/v1.0/variables/test + variable = db.get_variable(category, name)[0] + new_name = request.json.get('name', variable['name']) + new_content = request.json.get('content', variable['content']) + new_category = request.json.get('category', variable['category']) + db.update_variable(name, category, new_name, new_content, new_category) + result = db.get_variable(new_category, new_name) + elif request.method == 'DELETE': + db.delete_variable(category, name) + result = [] + + result = result + parameters = { + 'name': name, + 'categories': category, + } + return helpers.api.build_api_response(result, error=False, **parameters) + + +def variables_category(request): + db = getattr(g, 'db') + result = db.get_categories() + parameters = {} + return helpers.api.build_api_response(result, error=False, **parameters) + + +def variables_filter_by_category(request, category): + db = getattr(g, 'db') + result = db.filter_variables_category(category) + parameters = { + 'categories': category, + } + return helpers.api.build_api_response(result, error=False, **parameters) diff --git a/variables/views.py b/variables/views.py new file mode 100644 index 0000000..a043a15 --- /dev/null +++ b/variables/views.py @@ -0,0 +1,39 @@ +from flask import render_template +from flask import g + +def browse_variables(request): + db = getattr(g, 'db', None) + context = dict() + + context['categories'] = db.get_categories() + + context['filter_category'] = request.args.get('category', None) + + if context['filter_category'] is None: + context['variables'] = db.get_variables() + else: + context['variables'] = db.filter_variables_category(context['filter_category']) + return render_template('variables/browse.html', **context) + +''' +def edit_variable(request, orig_category, orig_name): + db = getattr(g, 'db', None) + context = dict() + context['saved'] = False + context['deleted'] = False + context['orig_category'] = orig_category + context['orig_name'] = orig_name + + if request.method == 'GET': + context['variable'] = db.get_variable(orig_category, orig_name) + elif request.method == 'POST': + category = request.form.get('category') + name = request.form.get('name') + content = request.form.get('content') + extra_vars = request.form.get('extra_vars') + db.update_variable(orig_name, orig_category, name, content, category, extra_vars) + context['saved'] = True + context['variable'] = db.get_variable(orig_category, orig_name) + + return render_template('variables/edit.html', **context) +'''