DriverLog initial commit

This commit is contained in:
Ilya Shakhat 2014-03-05 19:55:47 +04:00
parent 41eed1e719
commit a1d3e8d05d
100 changed files with 6054 additions and 2 deletions

4
.gitignore vendored
View File

@ -1,4 +1,5 @@
*.py[cod]
.venv
# C extensions
*.so
@ -26,6 +27,7 @@ pip-log.txt
.coverage
.tox
nosetests.xml
.testrepository
# Translations
*.mo
@ -34,3 +36,5 @@ nosetests.xml
.mr.developer.cfg
.project
.pydevproject
.idea
*.local

4
.testr.conf Normal file
View File

@ -0,0 +1,4 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

9
MANIFEST.in Normal file
View File

@ -0,0 +1,9 @@
include AUTHORS
include README.rst
include ChangeLog
include LICENSE
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

View File

@ -1,2 +0,0 @@
oscp
====

7
README.rst Normal file
View File

@ -0,0 +1,7 @@
DriverLog | vendor drivers for OpenStack
========================================
Project Info
------------
* Source Code: http://github.com/stackforge/driverlog

0
driverlog/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,93 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import flask
from driverlog.dashboard import decorators
from driverlog.dashboard import vault
blueprint = flask.Blueprint('api', __name__, url_prefix='/api/1.0')
@blueprint.route('/records')
@decorators.jsonify()
@decorators.exception_handler()
def get_records():
return [
{
'driver': 'SolidFire',
'project': 'openstack/cinder',
'branch': 'master',
'timestamp': 1234567890,
'success': True,
'endpoint': 'create_volume',
'passed_tests': [
'test_volume_create'
],
'failed_tests': [
]
},
{
'driver': 'SolidFire',
'project': 'openstack/cinder',
'branch': 'master',
'timestamp': 1234567890,
'success': True,
'endpoint': 'list_volume',
'passed_tests': [
'test_volume_list', 'test_volume_list_with_paging'
],
'failed_tests': [
]
},
{
'driver': 'Ceph',
'project': 'openstack/cinder',
'branch': 'stable/havana',
'timestamp': 1234567890,
'success': False,
'endpoint': 'create_volume',
'passed_tests': [
],
'failed_tests': [
'test_volume_create'
]
}
]
def get_drivers_internal(**params):
drivers = vault.get_vault()['drivers_map']
filtered_drivers = []
for driver in drivers.values():
include = True
for param, value in params.iteritems():
if value and driver.get(param) != value:
include = False
break
if include:
filtered_drivers.append(driver)
return filtered_drivers
@blueprint.route('/drivers')
@decorators.jsonify('drivers')
@decorators.exception_handler()
def get_drivers():
return get_drivers_internal()

View File

@ -0,0 +1,156 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import json
import flask
from werkzeug import exceptions
from driverlog.dashboard import parameters
from driverlog.dashboard import vault
from driverlog.openstack.common import log as logging
from driverlog.processor import utils
LOG = logging.getLogger(__name__)
def _get_time_filter(kwargs):
start_date = parameters.get_single_parameter(kwargs, 'start_date')
if start_date:
start_date = utils.date_to_timestamp_ext(start_date)
else:
start_date = 0
end_date = parameters.get_single_parameter(kwargs, 'end_date')
if end_date:
end_date = utils.date_to_timestamp_ext(end_date)
else:
end_date = utils.date_to_timestamp_ext('now')
def time_filter(records):
for record in records:
if start_date <= record['date'] <= end_date:
yield record
return time_filter
def record_filter(ignore=None, use_default=True):
if not ignore:
ignore = []
def decorator(f):
@functools.wraps(f)
def record_filter_decorated_function(*args, **kwargs):
memory_storage_inst = vault.get_memory_storage()
record_ids = set(memory_storage_inst.get_record_ids()) # a copy
# if 'module' not in ignore:
# param = parameters.get_parameter(kwargs, 'module', 'modules',
# use_default)
# if param:
# record_ids &= (
# memory_storage_inst.get_record_ids_by_modules(
# vault.resolve_modules(param)))
time_filter = _get_time_filter(kwargs)
kwargs['records'] = time_filter(
memory_storage_inst.get_records(record_ids))
return f(*args, **kwargs)
return record_filter_decorated_function
return decorator
def exception_handler():
def decorator(f):
@functools.wraps(f)
def exception_handler_decorated_function(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
if isinstance(e, exceptions.HTTPException):
raise # ignore Flask exceptions
LOG.exception(e)
flask.abort(404)
return exception_handler_decorated_function
return decorator
def templated(template=None, return_code=200):
def decorator(f):
@functools.wraps(f)
def templated_decorated_function(*args, **kwargs):
# vault_inst = vault.get_vault()
template_name = template
if template_name is None:
template_name = (flask.request.endpoint.replace('.', '/') +
'.html')
ctx = f(*args, **kwargs)
if ctx is None:
ctx = {}
# put parameters into template
# vault_inst = vault.get_vault()
# ctx['projects'] = vault_inst['default_data']['projects']
#
# project = parameters.get_single_parameter(kwargs, 'project')
# if project in vault_inst['projects_map']:
# ctx['project'] = vault_inst['projects_map'][project]
#
# driver = parameters.get_single_parameter(kwargs, 'driver')
# if driver:
# ctx['driver'] = driver
# ctx['project'] = vault_inst[
# 'driver_to_project_map'][ctx['driver']]
#
# date = parameters.get_single_parameter(kwargs, 'date')
# if date:
# ctx['date'] = date
# else:
# ctx['date'] = int(time.time())
return flask.render_template(template_name, **ctx), return_code
return templated_decorated_function
return decorator
def jsonify(root='data'):
def decorator(func):
@functools.wraps(func)
def jsonify_decorated_function(*args, **kwargs):
callback = flask.app.request.args.get('callback', False)
data = json.dumps({root: func(*args, **kwargs)})
if callback:
data = str(callback) + '(' + data + ')'
mimetype = 'application/javascript'
else:
mimetype = 'application/json'
return flask.current_app.response_class(data, mimetype=mimetype)
return jsonify_decorated_function
return decorator

View File

@ -0,0 +1,32 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
from flask.ext import gravatar as gravatar_ext
INFINITY_HTML = '&#x221E;'
gravatar = gravatar_ext.Gravatar(None, size=64, rating='g', default='wavatar')
def format_datetime(timestamp):
return datetime.datetime.utcfromtimestamp(
timestamp).strftime('%d %b %Y %H:%M:%S')
def format_date(timestamp):
return datetime.datetime.utcfromtimestamp(timestamp).strftime('%d-%b-%y')

View File

@ -0,0 +1,154 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import six
MEMORY_STORAGE_CACHED = 0
class MemoryStorage(object):
def __init__(self):
pass
class CachedMemoryStorage(MemoryStorage):
def __init__(self):
super(CachedMemoryStorage, self).__init__()
# common indexes
self.records = {}
self.primary_key_index = {}
self.record_types_index = {}
self.module_index = {}
self.user_id_index = {}
self.company_index = {}
self.release_index = {}
self.blueprint_id_index = {}
self.company_name_mapping = {}
self.indexes = {
'primary_key': self.primary_key_index,
'record_type': self.record_types_index,
'company_name': self.company_index,
'module': self.module_index,
'user_id': self.user_id_index,
'release': self.release_index,
}
def _save_record(self, record):
if record.get('company_name') == '*robots':
return
self.records[record['record_id']] = record
for key, index in six.iteritems(self.indexes):
self._add_to_index(index, record, key)
for bp_id in (record.get('blueprint_id') or []):
if bp_id in self.blueprint_id_index:
self.blueprint_id_index[bp_id].add(record['record_id'])
else:
self.blueprint_id_index[bp_id] = set([record['record_id']])
def update(self, records):
have_updates = False
for record in records:
have_updates = True
record_id = record['record_id']
if record_id in self.records:
# remove existing record from indexes
self._remove_record_from_index(self.records[record_id])
self._save_record(record)
if have_updates:
self.company_name_mapping = dict(
(c.lower(), c) for c in self.company_index.keys())
return have_updates
def _remove_record_from_index(self, record):
for key, index in six.iteritems(self.indexes):
index[record[key]].remove(record['record_id'])
def _add_to_index(self, record_index, record, key):
record_key = record[key]
if record_key in record_index:
record_index[record_key].add(record['record_id'])
else:
record_index[record_key] = set([record['record_id']])
def _get_record_ids_from_index(self, items, index):
record_ids = set()
for item in items:
if item in index:
record_ids |= index[item]
return record_ids
def get_record_ids_by_modules(self, modules):
return self._get_record_ids_from_index(modules, self.module_index)
def get_record_ids_by_companies(self, companies):
return self._get_record_ids_from_index(
map(self.get_original_company_name, companies),
self.company_index)
def get_record_ids_by_user_ids(self, launchpad_ids):
return self._get_record_ids_from_index(launchpad_ids,
self.user_id_index)
def get_record_ids_by_releases(self, releases):
return self._get_record_ids_from_index(releases, self.release_index)
def get_record_ids_by_blueprint_ids(self, blueprint_ids):
return self._get_record_ids_from_index(blueprint_ids,
self.blueprint_id_index)
def get_record_ids(self):
return self.records.keys()
def get_record_ids_by_type(self, record_type):
return self.record_types_index.get(record_type, set())
def get_records(self, record_ids):
for i in record_ids:
yield self.records[i]
def get_record_by_primary_key(self, primary_key):
if primary_key in self.primary_key_index:
record_id = list(self.primary_key_index[primary_key])
if record_id:
return self.records[record_id[0]]
return None
def get_original_company_name(self, company_name):
normalized = company_name.lower()
if normalized not in self.company_name_mapping:
return normalized
return self.company_name_mapping[normalized]
def get_companies(self):
return self.company_index.keys()
def get_modules(self):
return self.module_index.keys()
def get_user_ids(self):
return self.user_id_index.keys()
def get_memory_storage(memory_storage_type):
if memory_storage_type == MEMORY_STORAGE_CACHED:
return CachedMemoryStorage()
else:
raise Exception('Unknown memory storage type %s' % memory_storage_type)

View File

@ -0,0 +1,43 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import flask
from six.moves.urllib import parse
from driverlog.openstack.common import log as logging
LOG = logging.getLogger(__name__)
def get_parameter(kwargs, singular_name, plural_name=None):
if singular_name in kwargs:
p = kwargs[singular_name]
else:
p = flask.request.args.get(singular_name)
if (not p) and plural_name:
p = flask.request.args.get(plural_name)
if p:
return parse.unquote_plus(p).split(',')
else:
return []
def get_single_parameter(kwargs, singular_name, use_default=True):
param = get_parameter(kwargs, singular_name, use_default)
if param:
return param[0]
else:
return ''

View File

@ -0,0 +1,252 @@
/*
* Table
*/
table.dataTable {
border-spacing:0;
margin: 0 auto;
clear: both;
width: 100%;
}
table.dataTable thead th {
font-size: 18px;
font-weight: normal;
border-right: 1px solid #CCC;
background-color: #F0F3FA;
text-align: left;
font-weight: normal;
border-bottom: 1px dotted #CECECE;
text-shadow: 1px 1px 0px white;
padding: 5px 6px;
text-align: center;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
cursor: pointer;
*cursor: hand;
}
table.dataTable tfoot th {
padding: 3px 18px 3px 10px;
border-top: 1px solid black;
font-weight: bold;
}
table.dataTable td {
padding: 5px 10px;
vertical-align: top;
}
table.dataTable td.center,
table.dataTable td.dataTables_empty {
text-align: center;
}
table.dataTable tr.odd { background-color: #f6f5f5; }
table.dataTable tr.even { background-color: #fbfafa; }
table.dataTable tr.odd td.sorting_1 { background-color: #eef1f4; }
table.dataTable tr.odd td.sorting_2 { background-color: #DADCFF; }
table.dataTable tr.odd td.sorting_3 { background-color: #E0E2FF; }
table.dataTable tr.even td.sorting_1 { background-color: #f8f9fa; }
table.dataTable tr.even td.sorting_2 { background-color: #F2F3FF; }
table.dataTable tr.even td.sorting_3 { background-color: #F9F9FF; }
/*
* Table wrapper
*/
.dataTables_wrapper {
position: relative;
clear: both;
*zoom: 1;
}
/*
* Page length menu
*/
.dataTables_length {
float: left;
color:#bcc1cb;
font-style:italic;
}
/*
* Filter
*/
.dataTables_filter {
float: right;
text-align: right;
margin-bottom: 25px;
}
.dataTables_filter label {
color:#bcc1cb;
font-style:italic;
}
/*
* Table information
*/
.dataTables_info {
clear: both;
float: left;
margin-top: 10px;
color:#bcc1cb;
font-style:italic;
}
/*
* Pagination
*/
.dataTables_paginate {
float: right;
text-align: right;
margin-top: 10px;
}
/* Two button pagination - previous / next */
.paginate_disabled_previous,
.paginate_enabled_previous,
.paginate_disabled_next,
.paginate_enabled_next {
height: 19px;
float: left;
cursor: pointer;
*cursor: hand;
color: #111 !important;
}
.paginate_disabled_previous:hover,
.paginate_enabled_previous:hover,
.paginate_disabled_next:hover,
.paginate_enabled_next:hover {
text-decoration: none !important;
}
.paginate_disabled_previous:active,
.paginate_enabled_previous:active,
.paginate_disabled_next:active,
.paginate_enabled_next:active {
outline: none;
}
.paginate_disabled_previous,
.paginate_disabled_next {
color: #666 !important;
}
.paginate_disabled_previous,
.paginate_enabled_previous {
padding-left: 23px;
}
.paginate_disabled_next,
.paginate_enabled_next {
padding-right: 23px;
margin-left: 10px;
}
.paginate_enabled_previous { background: url('../images/back_enabled.png') no-repeat top left; }
.paginate_enabled_previous:hover { background: url('../images/back_enabled_hover.png') no-repeat top left; }
.paginate_disabled_previous { background: url('../images/back_disabled.png') no-repeat top left; }
.paginate_enabled_next { background: url('../images/forward_enabled.png') no-repeat top right; }
.paginate_enabled_next:hover { background: url('../images/forward_enabled_hover.png') no-repeat top right; }
.paginate_disabled_next { background: url('../images/forward_disabled.png') no-repeat top right; }
/* Full number pagination */
.paging_full_numbers {
height: 22px;
line-height: 22px;
}
.paging_full_numbers a:active {
outline: none
}
.paging_full_numbers a:hover {
text-decoration: none;
}
.paging_full_numbers a.paginate_active {
text-decoration: none;
cursor: pointer;
padding: 0.2em 0.5em;
-moz-border-radius: 3px;
border-radius: 3px;
margin: 4px;
font-weight: normal;
color: #9f3729;
text-shadow: 1px 1px 0px #ff7d6c;
background: #d3301a;
}
.paging_full_numbers a.paginate_button {
text-decoration: none;
cursor: pointer;
padding: 0.2em 0.5em;
background: #edeff1;
-moz-border-radius: 3px;
border-radius: 3px;
text-shadow: 1px 1px 0px white;
margin: 4px;
border: 1px solid #e0e2e4;
}
.paging_full_numbers a.paginate_button:hover {
border-color: #bc2814;
background: #d3301a;
color: white;
text-shadow: -1px -1px 0px #992010;
}
/*
* Processing indicator
*/
.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 250px;
height: 30px;
margin-left: -125px;
margin-top: -15px;
padding: 14px 0 2px 0;
border: 1px solid #ddd;
text-align: center;
color: #999;
font-size: 14px;
background-color: white;
}
/*
* Sorting
*/
.sorting { background: url('../images/sort_both.png') no-repeat center right; }
.sorting_asc { background: url('../images/sort_asc.png') no-repeat center right; }
.sorting_desc { background: url('../images/sort_desc.png') no-repeat center right; }
.sorting_asc_disabled { background: url('../images/sort_asc_disabled.png') no-repeat center right; }
.sorting_desc_disabled { background: url('../images/sort_desc_disabled.png') no-repeat center right; }
table.dataTable thead th:active,
table.dataTable thead td:active {
outline: none;
}
/*
* Scrolling
*/
.dataTables_scroll {
clear: both;
}
.dataTables_scrollBody {
*margin-top: -1px;
-webkit-overflow-scrolling: touch;
}

View File

@ -0,0 +1 @@
.jqplot-target{position:relative;color:black;font-family:"PT Sans",Arial,sans-serif;font-size:1em;text-shadow: 1px 1px 0 rgba(255,255,255, 0.5);}.jqplot-axis{font-size:.75em;}.jqplot-xaxis{margin-top:10px;}.jqplot-x2axis{margin-bottom:10px;}.jqplot-yaxis{margin-right:10px;}.jqplot-y2axis,.jqplot-y3axis,.jqplot-y4axis,.jqplot-y5axis,.jqplot-y6axis,.jqplot-y7axis,.jqplot-y8axis,.jqplot-y9axis,.jqplot-yMidAxis{margin-left:10px;margin-right:10px;}.jqplot-axis-tick,.jqplot-xaxis-tick,.jqplot-yaxis-tick,.jqplot-x2axis-tick,.jqplot-y2axis-tick,.jqplot-y3axis-tick,.jqplot-y4axis-tick,.jqplot-y5axis-tick,.jqplot-y6axis-tick,.jqplot-y7axis-tick,.jqplot-y8axis-tick,.jqplot-y9axis-tick,.jqplot-yMidAxis-tick{position:absolute;white-space:pre;}.jqplot-xaxis-tick{top:0;left:15px;vertical-align:top;}.jqplot-x2axis-tick{bottom:0;left:15px;vertical-align:bottom;}.jqplot-yaxis-tick{right:0;top:15px;text-align:right;}.jqplot-yaxis-tick.jqplot-breakTick{right:-20px;margin-right:0;padding:1px 5px 1px 5px;z-index:2;font-size:1.5em;}.jqplot-y2axis-tick,.jqplot-y3axis-tick,.jqplot-y4axis-tick,.jqplot-y5axis-tick,.jqplot-y6axis-tick,.jqplot-y7axis-tick,.jqplot-y8axis-tick,.jqplot-y9axis-tick{left:0;top:15px;text-align:left;}.jqplot-yMidAxis-tick{text-align:center;white-space:nowrap;}.jqplot-xaxis-label{margin-top:10px;font-size:11pt;position:absolute;}.jqplot-x2axis-label{margin-bottom:10px;font-size:11pt;position:absolute;}.jqplot-yaxis-label{margin-right:10px;font-size:11pt;position:absolute;}.jqplot-yMidAxis-label{font-size:11pt;position:absolute;}.jqplot-y2axis-label,.jqplot-y3axis-label,.jqplot-y4axis-label,.jqplot-y5axis-label,.jqplot-y6axis-label,.jqplot-y7axis-label,.jqplot-y8axis-label,.jqplot-y9axis-label{font-size:11pt;margin-left:10px;position:absolute;}.jqplot-meterGauge-tick{font-size:.75em;color:#999;}.jqplot-meterGauge-label{font-size:1em;color:#999;}table.jqplot-table-legend{margin-top:12px;margin-bottom:12px;margin-left:12px;margin-right:12px;}table.jqplot-table-legend,table.jqplot-cursor-legend{background-color:rgba(255,255,255,0.6);border:none;position:absolute;font-size:13px; color:#192233;}td.jqplot-table-legend{vertical-align:middle;}td.jqplot-seriesToggle:hover,td.jqplot-seriesToggle:active{cursor:pointer;}.jqplot-table-legend .jqplot-series-hidden{text-decoration:line-through;}div.jqplot-table-legend-swatch-outline{/*border:1px solid #ccc;padding:1px;*/}div.jqplot-table-legend-swatch{width:0;height:0;border-top-width:5px;border-bottom-width:5px;border-left-width:6px;border-right-width:6px;border-top-style:solid;border-bottom-style:solid;border-left-style:solid;border-right-style:solid;}.jqplot-title{top:0;left:0;padding-bottom:.5em;font-size:1.2em;}table.jqplot-cursor-tooltip{border:1px solid #ccc;font-size:.75em;}.jqplot-cursor-tooltip{border:1px solid #ccc;font-size:.75em;white-space:nowrap;background:rgba(208,208,208,0.5);padding:1px;}.jqplot-highlighter-tooltip,.jqplot-canvasOverlay-tooltip{border:1px solid #ccc;font-size:.75em;white-space:nowrap;background:rgba(208,208,208,0.5);padding:1px;}.jqplot-point-label{font-size:.75em;z-index:2;}td.jqplot-cursor-legend-swatch{vertical-align:middle;text-align:center;}div.jqplot-cursor-legend-swatch{width:1.2em;height:.7em;}.jqplot-error{text-align:center;}.jqplot-error-message{position:relative;top:46%;display:inline-block;}div.jqplot-bubble-label{font-size:.8em;padding-left:2px;padding-right:2px;color:rgb(20%,20%,20%);}div.jqplot-bubble-label.jqplot-bubble-label-highlight{background:rgba(90%,90%,90%,0.7);}div.jqplot-noData-container{text-align:center;background-color:rgba(96%,96%,96%,0.3);}

View File

@ -0,0 +1,681 @@
/*
Version: 3.4.1 Timestamp: Thu Jun 27 18:02:10 PDT 2013
*/
.select2-container {
margin: 0;
position: relative;
display: inline-block;
/* inline-block for ie7 */
zoom: 1;
*display: inline;
vertical-align: middle;
}
.select2-container,
.select2-drop,
.select2-search,
.select2-search input{
/*
Force border-box so that % widths fit the parent
container without overlap because of margin/padding.
More Info : http://www.quirksmode.org/css/box.html
*/
-webkit-box-sizing: border-box; /* webkit */
-khtml-box-sizing: border-box; /* konqueror */
-moz-box-sizing: border-box; /* firefox */
-ms-box-sizing: border-box; /* ie */
box-sizing: border-box; /* css3 */
}
.select2-container .select2-choice {
display: block;
height: 26px;
padding: 0 0 0 8px;
overflow: hidden;
position: relative;
border: 1px solid #aaa;
white-space: nowrap;
line-height: 26px;
color: #444;
text-decoration: none;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: #fff;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white));
background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%);
background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%);
background-image: -o-linear-gradient(bottom, #eeeeee 0%, #ffffff 50%);
background-image: -ms-linear-gradient(top, #ffffff 0%, #eeeeee 50%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
background-image: linear-gradient(top, #ffffff 0%, #eeeeee 50%);
}
.select2-container.select2-drop-above .select2-choice {
border-bottom-color: #aaa;
-webkit-border-radius:0 0 4px 4px;
-moz-border-radius:0 0 4px 4px;
border-radius:0 0 4px 4px;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.9, white));
background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 90%);
background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 90%);
background-image: -o-linear-gradient(bottom, #eeeeee 0%, white 90%);
background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 90%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
background-image: linear-gradient(top, #eeeeee 0%,#ffffff 90%);
}
.select2-container.select2-allowclear .select2-choice .select2-chosen {
margin-right: 42px;
}
.select2-container .select2-choice > .select2-chosen {
margin-right: 26px;
display: block;
overflow: hidden;
white-space: nowrap;
-ms-text-overflow: ellipsis;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}
.select2-container .select2-choice abbr {
display: none;
width: 12px;
height: 12px;
position: absolute;
right: 24px;
top: 8px;
font-size: 1px;
text-decoration: none;
border: 0;
background: url('../images/select2.png') right top no-repeat;
cursor: pointer;
outline: 0;
}
.select2-container.select2-allowclear .select2-choice abbr {
display: inline-block;
}
.select2-container .select2-choice abbr:hover {
background-position: right -11px;
cursor: pointer;
}
.select2-drop-undermask {
border: 0;
margin: 0;
padding: 0;
position: absolute;
left: 0;
top: 0;
z-index: 9998;
background-color: transparent;
filter: alpha(opacity=0);
}
.select2-drop-mask {
border: 0;
margin: 0;
padding: 0;
position: absolute;
left: 0;
top: 0;
z-index: 9998;
/* styles required for IE to work */
background-color: #fff;
opacity: 0;
filter: alpha(opacity=0);
}
.select2-drop {
width: 100%;
margin-top: -1px;
position: absolute;
z-index: 9999;
top: 100%;
background: #fff;
color: #000;
border: 1px solid #aaa;
border-top: 0;
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
border-radius: 0 0 4px 4px;
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
-moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
}
.select2-drop-auto-width {
border-top: 1px solid #aaa;
width: auto;
}
.select2-drop-auto-width .select2-search {
padding-top: 4px;
}
.select2-drop.select2-drop-above {
margin-top: 1px;
border-top: 1px solid #aaa;
border-bottom: 0;
-webkit-border-radius: 4px 4px 0 0;
-moz-border-radius: 4px 4px 0 0;
border-radius: 4px 4px 0 0;
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
-moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
}
.select2-drop-active {
border: 1px solid #5897fb;
border-top: none;
}
.select2-drop.select2-drop-above.select2-drop-active {
border-top: 1px solid #5897fb;
}
.select2-container .select2-choice .select2-arrow {
display: inline-block;
width: 18px;
height: 100%;
position: absolute;
right: 0;
top: 0;
border-left: 1px solid #aaa;
-webkit-border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0;
border-radius: 0 4px 4px 0;
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
background: #ccc;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%);
background-image: -ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%);
}
.select2-container .select2-choice .select2-arrow b {
display: block;
width: 100%;
height: 100%;
background: url('../images/select2.png') no-repeat 0 1px;
}
.select2-search {
display: inline-block;
width: 100%;
min-height: 26px;
margin: 0;
padding-left: 4px;
padding-right: 4px;
position: relative;
z-index: 10000;
white-space: nowrap;
}
.select2-search input {
width: 100%;
height: auto !important;
min-height: 26px;
padding: 4px 20px 4px 5px;
margin: 0;
outline: 0;
font-family: sans-serif;
font-size: 1em;
border: 1px solid #aaa;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
background: #fff url('../images/select2.png') no-repeat 100% -22px;
background: url('../images/select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
background: url('../images/select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('../images/select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('../images/select2.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
background: url('../images/select2.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
background: url('../images/select2.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
}
.select2-drop.select2-drop-above .select2-search input {
margin-top: 4px;
}
.select2-search input.select2-active {
background: #fff url('../images/select2-spinner.gif') no-repeat 100%;
background: url('../images/select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
background: url('../images/select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('../images/select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('../images/select2-spinner.gif') no-repeat 100%, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
background: url('../images/select2-spinner.gif') no-repeat 100%, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
background: url('../images/select2-spinner.gif') no-repeat 100%, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
}
.select2-container-active .select2-choice,
.select2-container-active .select2-choices {
border: 1px solid #5897fb;
outline: none;
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
-moz-box-shadow: 0 0 5px rgba(0,0,0,.3);
box-shadow: 0 0 5px rgba(0,0,0,.3);
}
.select2-dropdown-open .select2-choice {
border-bottom-color: transparent;
-webkit-box-shadow: 0 1px 0 #fff inset;
-moz-box-shadow: 0 1px 0 #fff inset;
box-shadow: 0 1px 0 #fff inset;
-webkit-border-bottom-left-radius: 0;
-moz-border-radius-bottomleft: 0;
border-bottom-left-radius: 0;
-webkit-border-bottom-right-radius: 0;
-moz-border-radius-bottomright: 0;
border-bottom-right-radius: 0;
background-color: #eee;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee));
background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%);
background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%);
background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%);
background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%);
}
.select2-dropdown-open.select2-drop-above .select2-choice,
.select2-dropdown-open.select2-drop-above .select2-choices {
border: 1px solid #5897fb;
border-top-color: transparent;
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, white), color-stop(0.5, #eeeeee));
background-image: -webkit-linear-gradient(center top, white 0%, #eeeeee 50%);
background-image: -moz-linear-gradient(center top, white 0%, #eeeeee 50%);
background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
background-image: -ms-linear-gradient(bottom, #ffffff 0%,#eeeeee 50%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
background-image: linear-gradient(bottom, #ffffff 0%,#eeeeee 50%);
}
.select2-dropdown-open .select2-choice .select2-arrow {
background: transparent;
border-left: none;
filter: none;
}
.select2-dropdown-open .select2-choice .select2-arrow b {
background-position: -18px 1px;
}
/* results */
.select2-results {
max-height: 200px;
padding: 0 0 0 4px;
margin: 4px 4px 4px 0;
position: relative;
overflow-x: hidden;
overflow-y: auto;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.select2-results ul.select2-result-sub {
margin: 0;
padding-left: 0;
}
.select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px }
.select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px }
.select2-results li {
list-style: none;
display: list-item;
background-image: none;
}
.select2-results li.select2-result-with-children > .select2-result-label {
font-weight: bold;
}
.select2-results .select2-result-label {
padding: 3px 7px 4px;
margin: 0;
cursor: pointer;
min-height: 1em;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.select2-results .select2-highlighted {
background: #3875d7;
color: #fff;
}
.select2-results li em {
background: #feffde;
font-style: normal;
}
.select2-results .select2-highlighted em {
background: transparent;
}
.select2-results .select2-highlighted ul {
background: white;
color: #000;
}
.select2-results .select2-no-results,
.select2-results .select2-searching,
.select2-results .select2-selection-limit {
background: #f4f4f4;
display: list-item;
}
/*
disabled look for disabled choices in the results dropdown
*/
.select2-results .select2-disabled.select2-highlighted {
color: #666;
background: #f4f4f4;
display: list-item;
cursor: default;
}
.select2-results .select2-disabled {
background: #f4f4f4;
display: list-item;
cursor: default;
}
.select2-results .select2-selected {
display: none;
}
.select2-more-results.select2-active {
background: #f4f4f4 url('../images/select2-spinner.gif') no-repeat 100%;
}
.select2-more-results {
background: #f4f4f4;
display: list-item;
}
/* disabled styles */
.select2-container.select2-container-disabled .select2-choice {
background-color: #f4f4f4;
background-image: none;
border: 1px solid #ddd;
cursor: default;
}
.select2-container.select2-container-disabled .select2-choice .select2-arrow {
background-color: #f4f4f4;
background-image: none;
border-left: 0;
}
.select2-container.select2-container-disabled .select2-choice abbr {
display: none;
}
/* multiselect */
.select2-container-multi .select2-choices {
height: auto !important;
height: 1%;
margin: 0;
padding: 0;
position: relative;
border: 1px solid #aaa;
cursor: text;
overflow: hidden;
background-color: #fff;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%);
}
.select2-locked {
padding: 3px 5px 3px 5px !important;
}
.select2-container-multi .select2-choices {
min-height: 26px;
}
.select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #5897fb;
outline: none;
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
-moz-box-shadow: 0 0 5px rgba(0,0,0,.3);
box-shadow: 0 0 5px rgba(0,0,0,.3);
}
.select2-container-multi .select2-choices li {
float: left;
list-style: none;
}
.select2-container-multi .select2-choices .select2-search-field {
margin: 0;
padding: 0;
white-space: nowrap;
}
.select2-container-multi .select2-choices .select2-search-field input {
padding: 5px;
margin: 1px 0;
font-family: sans-serif;
font-size: 100%;
color: #666;
outline: 0;
border: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
background: transparent !important;
}
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
background: #fff url('../images/select2-spinner.gif') no-repeat 100% !important;
}
.select2-default {
color: #999 !important;
font-style: italic;
}
.select2-container-multi .select2-choices .select2-search-choice {
padding: 3px 5px 3px 18px;
margin: 3px 0 3px 5px;
position: relative;
line-height: 13px;
color: #333;
cursor: default;
border: 1px solid #aaaaaa;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
-moz-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: #e4e4e4;
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0 );
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
}
.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
cursor: default;
}
.select2-container-multi .select2-choices .select2-search-choice-focus {
background: #d4d4d4;
}
.select2-search-choice-close {
display: block;
width: 12px;
height: 13px;
position: absolute;
right: 3px;
top: 4px;
font-size: 1px;
outline: none;
background: url('../images/select2.png') right top no-repeat;
}
.select2-container-multi .select2-search-choice-close {
left: 3px;
}
.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
background-position: right -11px;
}
.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
background-position: right -11px;
}
/* disabled styles */
.select2-container-multi.select2-container-disabled .select2-choices{
background-color: #f4f4f4;
background-image: none;
border: 1px solid #ddd;
cursor: default;
}
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
padding: 3px 5px 3px 5px;
border: 1px solid #ddd;
background-image: none;
background-color: #f4f4f4;
}
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
background:none;
}
/* end multiselect */
.select2-result-selectable .select2-match,
.select2-result-unselectable .select2-match {
text-decoration: underline;
}
.select2-offscreen, .select2-offscreen:focus {
clip: rect(0 0 0 0);
width: 1px;
height: 1px;
border: 0;
margin: 0;
padding: 0;
overflow: hidden;
position: absolute;
outline: 0;
left: 0px;
}
.select2-display-none {
display: none;
}
.select2-measure-scrollbar {
position: absolute;
top: -10000px;
left: -10000px;
width: 100px;
height: 100px;
overflow: scroll;
}
/* Retina-ize icons */
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) {
.select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice .select2-arrow b {
background-image: url('../images/select2x2.png') !important;
background-repeat: no-repeat !important;
background-size: 60px 40px !important;
}
.select2-search input {
background-position: 100% -21px !important;
}
}

View File

@ -0,0 +1,417 @@
html, body {
font-family: 'PT Sans', arial, sans-serif;
font-size: 14px;
height: 100%;
color: #41454d;
margin: 0;
}
a {
color: #D32F1A;
text-decoration: none;
}
a:hover {
color: #F00;
}
p {
font-size: 15px;
color: #41454D;
font-style: normal;
margin: 6px 0px 15px 0px;
}
div.page {
width: 960px;
margin: 0 auto;
}
div.page h2 {
font-family: 'PT Sans Narrow', 'Arial Narrow', arial, sans-serif;
font-size: 23px;
font-weight: normal;
font-style: normal;
margin-bottom: 10px;
color: #a41200;
text-shadow: 1px 1px 0 #fff;
}
div.page h3 {
font-family: 'PT Sans Narrow', 'Arial Narrow', arial, sans-serif;
font-size: 18px;
margin-bottom: 10px;
}
input[type="text"],
input[type="password"] {
background: #f8f9f9;
padding: 0 5px 0 5px;
min-height: 24px;
border: none;
border-bottom: 1px solid white;
border-right: 1px solid white;
-moz-border-radius: 3px;
border-radius: 3px;
text-shadow: 1px 1px 0 white;
-moz-box-shadow: inset 2px 2px 7px #D3D8DD;
-webkit-box-shadow: inset 2px 2px 7px #D3D8DD;
box-shadow: inset 2px 2px 7px #D3D8DD;
}
input[type="submit"] {
background: #f8f9f9;
padding: 0 5px 0 5px;
min-height: 24px;
border: none;
border-bottom: 1px solid white;
border-right: 1px solid white;
-moz-border-radius: 2px;
border-radius: 2px;
text-shadow: 1px 1px 0 white;
-moz-box-shadow: inset 2px 2px 7px #D3D8DD;
-webkit-box-shadow: inset 2px 2px 7px #D3D8DD;
box-shadow: inset 2px 2px 7px #D3D8DD;
}
div.drops {
height: 60px;
margin-top: 10px;
}
div.drop label {
color: #909cb5;
}
.drop {
height: 30px;
float: left;
margin-right: 20px;
}
.drop label {
display: block;
}
div.aheader {
margin-top: 23px;
text-shadow: 1px 1px 0 #fff;
}
div.aheader h1 {
font-size: 36px;
color: #a8b3bd;
text-shadow: 1px 1px 0 #fff;
}
div.aheader h1 a {
font-weight: bold;
color: #637f99;
text-decoration: none;
text-shadow: 1px 1px 0 #fff;
}
div.page div.navigation {
text-shadow: 1px 1px 0 #fff;
padding: 4px 10px;
border-top: 1px dashed #e9eaef;
border-bottom: 1px dashed #e9eaef;
color: #909cb5;
font-size: 10pt;
}
div.page div.navigation a {
color: #444;
font-weight: bold;
}
div.page div.body {
padding: 10px;
}
div.page div.footer {
background: #eee;
color: #888;
padding: 1em 1em;
font-size: 9pt;
}
.spacer {
height: 20px;
clear: both;
}
table#left_list td {
font-size: 15px;
}
table#right_list td {
font-size: 15px;
}
div#left_list_wrapper {
margin-top: 20px;
}
div#right_list_wrapper {
margin-top: 20px;
}
.label {
font-weight: bold;
line-height: 135%;
}
.activity {
margin-bottom: 2em;
}
.activity h3 {
margin-bottom: 0.2em;
}
.activity b {
color: red;
}
.activity .message {
margin: 0;
}
.record {
margin-bottom: 1em;
}
.record .header {
font-weight: bold;
}
.record .message {
white-space: pre-wrap;
}
a[href^="https://blueprints"]:after {
content: "↗";
opacity: 0.3;
}
a[href^="https://review"]:after {
content: "↗";
opacity: 0.3;
}
a[href^="https://bugs"]:after {
content: "↗";
opacity: 0.3;
}
a[href^="https://launchpad"]:after {
content: "↗";
opacity: 0.3;
}
a[target]:not([target='']):after {
content: "↗";
opacity: 0.3;
}
#analytics_header #logo {
font-family: 'PT Sans', 'Arial Narrow', arial, sans-serif;
font-weight: bolder;
font-style: normal;
font-size: 30px;
color: black;
text-shadow: 1px 1px 0 #fff;
margin: 0;
padding-right: 10px;
}
#analytics_header #slogan {
font-family: georgia, serif;
font-weight: lighter;
font-style: italic;
font-size: 24px;
margin: 0;
padding: 0;
color: #9caebf;
line-height: 1.1em;
text-shadow: 1px 1px 0 #fff;
}
.paging_full_numbers {
font-size: 9pt;
}
.select_module_group {
font-weight: bold;
color: #4bb2c5;
}
.select_module_program {
font-weight: bold;
color: #ab64c5;
}
.project_group {
font-weight: bold;
}
.project_group_item {
padding-left: 0.5em;
}
.review_mark {
font-weight: bold;
}
.specstatusApproved, .specstatusApproved a {
color: #008000;
}
.specstatusPendingApproval, .specstatusPendingApproval a, .specstatusPendingView, .specstatusPendingView a {
color: #FF0099;
}
.specstatusDraft, .specstatusDraft a, .specstatusDiscussion, .specstatusDiscussion a {
color: #993300;
}
.specstatusNew, .specstatusNew a {
color: #FF0000;
}
.specstatusSuperseded, .specstatusSuperseded a, .specstatusObsolete, .specstatusObsolete a, .specpriorityUndefined, .specpriorityUndefined a {
color: #808080;
}
.specpriorityLow, .specpriorityLow a {
color: #000000;
}
.specpriorityMedium, .specpriorityMedium a {
color: #FF6600;
}
.specpriorityHigh, .specpriorityHigh a, .specpriorityEssential, .specpriorityEssential a {
color: #FF0000;
}
.specdeliveryUnknown, .specdeliveryUnknown a, .specdeliveryNotStarted, .specdeliveryNotStarted a {
color: #808080;
}
.specdeliveryDeferred, .specdeliveryDeferred a, .specdeliveryNeendsInfrastructure, .specdeliveryNeendsInfrastructure a, .specdeliveryBlocked, .specdeliveryBlocked a {
color: #FF0000;
}
.specdeliveryStarted, .specdeliveryStarted a, .specdeliveryGood, .specdeliveryGood a {
color: #0000FF;
}
.specdeliverySlow, .specdeliverySlow a {
color: #FF0000;
}
.specdeliveryBeta, .specdeliveryBeta a {
color: #FF6600;
}
.specdeliveryNEEDSREVIEW, .specdeliveryNEEDSREVIEW a {
color: #800080;
}
.specdeliveryAWAITINGDEPLOYMENT, .specdeliveryAWAITINGDEPLOYMENT a {
color: #FF0000;
}
.specdeliveryImplemented, .specdeliveryImplemented a, .specdeliveryINFORMATIONAL, .specdeliveryINFORMATIONAL a {
color: #008000;
}
.bug-activity {
color: #555555;
}
.statusNew, .statusNew a {
color: #993300;
}
.statusIncomplete, .statusIncomplete a, .statusConfirmed, .statusConfirmed a {
color: #FF0000;
}
.statusTriaged, .statusTriaged a {
color: #FF6600;
}
.statusInProgress, .statusInProgress a {
color: #000000;
}
.statusComplete, .statusComplete a, .statusFixCommitted, .statusFixCommitted a {
color: #005500;
}
.statusFixReleased, .statusFixReleased a {
color: #008000;
}
.statusInvalid, .statusInvalid a, .statusWontFix, .statusWontFix a {
color: #555555;
}
.importanceCritical, .importanceCritical a {
color: #FF0000;
}
.importanceHigh, .importanceHigh a {
color: #FF6600;
}
.importanceMedium, .importanceMedium a {
color: #008000;
}
.importanceLow, .importanceLow a {
color: #000000;
}
.importanceWishlist, .importanceWishlist a {
color: #0000FF;
}
.importanceUndecided, .importanceUndecided a {
color: #999999;
}
.kpi_block {
padding-bottom: 1em;
}
.kpi_title_block {
margin-left: 40px;
}
.kpi_title {
font-size: 13pt;
font-weight: bold;
}
.kpi_marker {
font-size: 16pt;
font-weight: bold;
text-align: center; vertical-align: middle;
color: lightgray;
float: left; width: 32px; height: 32px;
}
.kpi_good {
color: #008000;
}
.kpi_bad {
color: #C00000;
}
.kpi_info {
font-size: 11pt;
color: #606060;
}
.select2-results {
max-height: 300px;
}
.ui-tooltip {
box-shadow: 0 0 5px #AAAAAA;
max-width: 300px;
padding: 8px;
position: absolute;
z-index: 9999;
}
body .ui-tooltip {
border-width: 2px;
background-color: white;
opacity: 0.8;
}
.verification_1 {
color: red;
}
.verification_2 {
color: orange;
}
.verification_3 {
color: #008000;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,279 @@
/*
Copyright (c) 2014 Mirantis Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
function format_test_result(value) {
var tooltip = "";
var result;
if (typeof value == 'undefined') {
result = "<span style='color: grey;'>n/a</span>";
} else {
if (value.success) {
result = "<span style='color: green;'>&#x2714;</span>"
} else {
result = "<span style='color: red;'>&#x2718;</span>"
}
if (value.passed.length > 0) {
tooltip += "Passed: " + value.passed.join(", ") + " ";
}
if (value.failed.length > 0) {
tooltip += "Failed: " + value.failed.join(", ");
}
}
return "<span title='" + tooltip + "'>" + result + "</span>";
}
function renderTable(table_id) {
$(document).ready(function () {
$.ajax({
url: "/api/1.0/records",
dataType: "json",
success: function (data) {
var matrix = {};
var driver_map = {};
var branch_map = {};
for (var i in data["data"]) {
var record = data["data"][i];
var driver = record.driver;
var project = record.project;
var branch = record.branch;
if (!matrix[driver]) {
matrix[driver] = {};
}
if (typeof matrix[driver][branch] == 'undefined') {
matrix[driver][branch] = {
success: record.success,
passed: [],
failed: []
};
}
if (record.success) {
matrix[driver][branch].passed.push(record.endpoint);
} else {
matrix[driver][branch].failed.push(record.endpoint);
matrix[driver][branch].success = false;
}
driver_map[driver] = true;
branch_map[branch] = true;
}
var tableColumns = [{"mData": "driver", "sTitle": "Driver"}];
for (branch in branch_map) {
tableColumns.push({"mData": branch, "sTitle": branch});
}
var tableData = [];
for (driver in driver_map) {
var row = {
'driver': make_link(driver, driver, 'driver')
};
for (branch in branch_map) {
row[branch] = format_test_result(matrix[driver][branch]);
}
tableData.push(row);
}
$("#" + table_id).dataTable({
"bPaginate": false,
// "aaSorting": [
// [ 0, "asc" ]
// ],
"aaData": tableData,
"aoColumns": tableColumns
});
}
});
});
}
function renderDriverTable(table_id) {
$(document).ready(function () {
$.ajax({
url: "/api/1.0/records",
dataType: "json",
success: function (data) {
var matrix = {};
var endpoint_map = {};
var branch_map = {};
for (var i in data["data"]) {
var record = data["data"][i];
var endpoint = record.endpoint;
var project = record.project;
var branch = record.branch;
if (!matrix[endpoint]) {
matrix[endpoint] = {};
}
matrix[endpoint][branch] = record;
matrix[endpoint][branch].passed = record.passed_tests;
matrix[endpoint][branch].failed = record.failed_tests;
endpoint_map[endpoint] = true;
branch_map[branch] = true;
}
var tableColumns = [{"mData": "endpoint", "sTitle": "Endpoint"}];
for (branch in branch_map) {
tableColumns.push({"mData": branch, "sTitle": branch});
}
var tableData = [];
for (endpoint in endpoint_map) {
var row = {
'endpoint': endpoint
};
for (branch in branch_map) {
row[branch] = format_test_result(matrix[endpoint][branch]);
}
tableData.push(row);
}
$("#" + table_id).dataTable({
"bPaginate": false,
// "aaSorting": [
// [ 0, "asc" ]
// ],
"aaData": tableData,
"aoColumns": tableColumns
});
}
});
});
}
function renderSummaryTable(table_id) {
$(document).ready(function () {
$.ajax({
url: "/api/1.0/drivers",
dataType: "json",
success: function (data) {
var tableColumns = [
{"mData": "project_name", "sTitle": "Project"},
{"mData": "vendor", "sTitle": "Vendor"},
{"mData": "name", "sTitle": "Driver Name"},
{"mData": "verification_name", "sTitle": "Verification"}
];
var tableData = data["drivers"];
for (var i in tableData) {
var driver = tableData[i];
driver["name"] = "<a href='details?driver=" + driver["id"] + "'>" + driver["name"] + "</a>";
driver["verification_name"]
}
$("#" + table_id).dataTable({
"bPaginate": false,
"aaData": tableData,
"aoColumns": tableColumns
});
}
});
});
}
function getUrlVars() {
var vars = {};
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
vars[key] = decodeURIComponent(value);
});
return vars;
}
function make_link(id, title, param_name) {
var options = {};
options[param_name] = encodeURIComponent(id).toLowerCase();
var link = make_uri("/", options);
return "<a href=\"" + link + "\">" + title + "</a>"
}
function make_uri(uri, options) {
var ops = {};
$.extend(ops, getUrlVars());
if (options != null) {
$.extend(ops, options);
}
var str = $.map(ops,function (val, index) {
return index + "=" + encodeURIComponent(val).toLowerCase();
}).join("&");
return (str == "") ? uri : uri + "?" + str;
}
function make_std_options() {
var options = {};
options['project_id'] = $('#project_selector').val();
options['vendor'] = $('#vendor_selector').val();
options['level_id'] = $('#level_selector').val();
// options['date'] = $('#date_selector').datepicker("getDate").getTime() / 1000;
return options;
}
function reload() {
var ops = {};
$.extend(ops, getUrlVars());
$.extend(ops, make_std_options());
window.location.search = $.map(ops,function (val, index) {
return index + "=" + encodeURIComponent(val);
}).join("&")
}
function init_application(project_id, date) {
$(function () {
$(document).tooltip();
});
$(document).ready(function () {
$("#project_selector").select2();
$("#project_selector").select2("val", project_id);
$("#project_selector").select2().on("change", function(e) {
reload();
});
});
$(function () {
var datepicker = $("#date_selector");
datepicker.datepicker({
dateFormat: "dd M yy",
maxDate: "0"
});
if (date) {
datepicker.datepicker("setDate", new Date(parseInt(date) * 1000));
} else {
datepicker.datepicker("setDate", "0");
}
datepicker.on("change", function(e) {
reload();
})
});
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
/* jqPlot 1.0.7r1224 | (c) 2009-2013 Chris Leonello | jplot.com
jsDate | (c) 2010-2013 Chris Leonello
*/(function(a){a.jqplot.CanvasAxisLabelRenderer=function(b){this.angle=0;this.axis;this.show=true;this.showLabel=true;this.label="";this.fontFamily='"Trebuchet MS", Arial, Helvetica, sans-serif';this.fontSize="11pt";this.fontWeight="normal";this.fontStretch=1;this.textColor="#666666";this.enableFontSupport=true;this.pt2px=null;this._elem;this._ctx;this._plotWidth;this._plotHeight;this._plotDimensions={height:null,width:null};a.extend(true,this,b);if(b.angle==null&&this.axis!="xaxis"&&this.axis!="x2axis"){this.angle=-90}var c={fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily};if(this.pt2px){c.pt2px=this.pt2px}if(this.enableFontSupport){if(a.jqplot.support_canvas_text()){this._textRenderer=new a.jqplot.CanvasFontRenderer(c)}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}};a.jqplot.CanvasAxisLabelRenderer.prototype.init=function(b){a.extend(true,this,b);this._textRenderer.init({fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily})};a.jqplot.CanvasAxisLabelRenderer.prototype.getWidth=function(d){if(this._elem){return this._elem.outerWidth(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.sin(f.angle)*e)+Math.abs(Math.cos(f.angle)*c);return b}};a.jqplot.CanvasAxisLabelRenderer.prototype.getHeight=function(d){if(this._elem){return this._elem.outerHeight(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.cos(f.angle)*e)+Math.abs(Math.sin(f.angle)*c);return b}};a.jqplot.CanvasAxisLabelRenderer.prototype.getAngleRad=function(){var b=this.angle*Math.PI/180;return b};a.jqplot.CanvasAxisLabelRenderer.prototype.draw=function(c,f){if(this._elem){if(a.jqplot.use_excanvas&&window.G_vmlCanvasManager.uninitElement!==undefined){window.G_vmlCanvasManager.uninitElement(this._elem.get(0))}this._elem.emptyForce();this._elem=null}var e=f.canvasManager.getCanvas();this._textRenderer.setText(this.label,c);var b=this.getWidth(c);var d=this.getHeight(c);e.width=b;e.height=d;e.style.width=b;e.style.height=d;e=f.canvasManager.initCanvas(e);this._elem=a(e);this._elem.css({position:"absolute"});this._elem.addClass("jqplot-"+this.axis+"-label");e=null;return this._elem};a.jqplot.CanvasAxisLabelRenderer.prototype.pack=function(){this._textRenderer.draw(this._elem.get(0).getContext("2d"),this.label)}})(jQuery);

View File

@ -0,0 +1,3 @@
/* jqPlot 1.0.7r1224 | (c) 2009-2013 Chris Leonello | jplot.com
jsDate | (c) 2010-2013 Chris Leonello
*/(function(a){a.jqplot.CanvasAxisTickRenderer=function(b){this.mark="outside";this.showMark=true;this.showGridline=true;this.isMinorTick=false;this.angle=0;this.markSize=4;this.show=true;this.showLabel=true;this.labelPosition="auto";this.label="";this.value=null;this._styles={};this.formatter=a.jqplot.DefaultTickFormatter;this.formatString="";this.prefix="";this.fontFamily='"Trebuchet MS", Arial, Helvetica, sans-serif';this.fontSize="10pt";this.fontWeight="normal";this.fontStretch=1;this.textColor="#666666";this.enableFontSupport=true;this.pt2px=null;this._elem;this._ctx;this._plotWidth;this._plotHeight;this._plotDimensions={height:null,width:null};a.extend(true,this,b);var c={fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily};if(this.pt2px){c.pt2px=this.pt2px}if(this.enableFontSupport){if(a.jqplot.support_canvas_text()){this._textRenderer=new a.jqplot.CanvasFontRenderer(c)}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}};a.jqplot.CanvasAxisTickRenderer.prototype.init=function(b){a.extend(true,this,b);this._textRenderer.init({fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily})};a.jqplot.CanvasAxisTickRenderer.prototype.getWidth=function(d){if(this._elem){return this._elem.outerWidth(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.sin(f.angle)*e)+Math.abs(Math.cos(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getHeight=function(d){if(this._elem){return this._elem.outerHeight(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.cos(f.angle)*e)+Math.abs(Math.sin(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getTop=function(b){if(this._elem){return this._elem.position().top}else{return null}};a.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad=function(){var b=this.angle*Math.PI/180;return b};a.jqplot.CanvasAxisTickRenderer.prototype.setTick=function(b,d,c){this.value=b;if(c){this.isMinorTick=true}return this};a.jqplot.CanvasAxisTickRenderer.prototype.draw=function(c,f){if(!this.label){this.label=this.prefix+this.formatter(this.formatString,this.value)}if(this._elem){if(a.jqplot.use_excanvas&&window.G_vmlCanvasManager.uninitElement!==undefined){window.G_vmlCanvasManager.uninitElement(this._elem.get(0))}this._elem.emptyForce();this._elem=null}var e=f.canvasManager.getCanvas();this._textRenderer.setText(this.label,c);var b=this.getWidth(c);var d=this.getHeight(c);e.width=b;e.height=d;e.style.width=b;e.style.height=d;e.style.textAlign="left";e.style.position="absolute";e=f.canvasManager.initCanvas(e);this._elem=a(e);this._elem.css(this._styles);this._elem.addClass("jqplot-"+this.axis+"-tick");e=null;return this._elem};a.jqplot.CanvasAxisTickRenderer.prototype.pack=function(){this._textRenderer.draw(this._elem.get(0).getContext("2d"),this.label)}})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
/* jqPlot 1.0.7r1224 | (c) 2009-2013 Chris Leonello | jplot.com
jsDate | (c) 2010-2013 Chris Leonello
*/(function($){$.jqplot.JSON=window.JSON;if(!window.JSON){$.jqplot.JSON={}}function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null"}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==="string"){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v}}if(typeof $.jqplot.JSON.stringify!=="function"){$.jqplot.JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" "}}else{if(typeof space==="string"){indent=space}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("$.jqplot.JSON.stringify")}return str("",{"":value})}}if(typeof $.jqplot.JSON.parse!=="function"){$.jqplot.JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("$.jqplot.JSON.parse")}}})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,155 @@
/*
* File: jquery.dataTables.min.js
* Version: 1.9.4
* Author: Allan Jardine (www.sprymedia.co.uk)
* Info: www.datatables.net
*
* Copyright 2008-2012 Allan Jardine, all rights reserved.
*
* This source file is free software, under either the GPL v2 license or a
* BSD style license, available at:
* http://datatables.net/license_gpl2
* http://datatables.net/license_bsd
*
* This source file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
*/
(function(X,l,n){var L=function(h){var j=function(e){function o(a,b){var c=j.defaults.columns,d=a.aoColumns.length,c=h.extend({},j.models.oColumn,c,{sSortingClass:a.oClasses.sSortable,sSortingClassJUI:a.oClasses.sSortJUI,nTh:b?b:l.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.oDefaults:d});a.aoColumns.push(c);if(a.aoPreSearchCols[d]===n||null===a.aoPreSearchCols[d])a.aoPreSearchCols[d]=h.extend({},j.models.oSearch);else if(c=a.aoPreSearchCols[d],
c.bRegex===n&&(c.bRegex=!0),c.bSmart===n&&(c.bSmart=!0),c.bCaseInsensitive===n)c.bCaseInsensitive=!0;m(a,d,null)}function m(a,b,c){var d=a.aoColumns[b];c!==n&&null!==c&&(c.mDataProp&&!c.mData&&(c.mData=c.mDataProp),c.sType!==n&&(d.sType=c.sType,d._bAutoType=!1),h.extend(d,c),p(d,c,"sWidth","sWidthOrig"),c.iDataSort!==n&&(d.aDataSort=[c.iDataSort]),p(d,c,"aDataSort"));var i=d.mRender?Q(d.mRender):null,f=Q(d.mData);d.fnGetData=function(a,b){var c=f(a,b);return d.mRender&&b&&""!==b?i(c,b,a):c};d.fnSetData=
L(d.mData);a.oFeatures.bSort||(d.bSortable=!1);!d.bSortable||-1==h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortableNone,d.sSortingClassJUI=""):-1==h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortable,d.sSortingClassJUI=a.oClasses.sSortJUI):-1!=h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortableAsc,d.sSortingClassJUI=a.oClasses.sSortJUIAscAllowed):-1==
h.inArray("asc",d.asSorting)&&-1!=h.inArray("desc",d.asSorting)&&(d.sSortingClass=a.oClasses.sSortableDesc,d.sSortingClassJUI=a.oClasses.sSortJUIDescAllowed)}function k(a){if(!1===a.oFeatures.bAutoWidth)return!1;da(a);for(var b=0,c=a.aoColumns.length;b<c;b++)a.aoColumns[b].nTh.style.width=a.aoColumns[b].sWidth}function G(a,b){var c=r(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function R(a,b){var c=r(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function t(a){return r(a,"bVisible").length}
function r(a,b){var c=[];h.map(a.aoColumns,function(a,i){a[b]&&c.push(i)});return c}function B(a){for(var b=j.ext.aTypes,c=b.length,d=0;d<c;d++){var i=b[d](a);if(null!==i)return i}return"string"}function u(a,b){for(var c=b.split(","),d=[],i=0,f=a.aoColumns.length;i<f;i++)for(var g=0;g<f;g++)if(a.aoColumns[i].sName==c[g]){d.push(g);break}return d}function M(a){for(var b="",c=0,d=a.aoColumns.length;c<d;c++)b+=a.aoColumns[c].sName+",";return b.length==d?"":b.slice(0,-1)}function ta(a,b,c,d){var i,f,
g,e,w;if(b)for(i=b.length-1;0<=i;i--){var j=b[i].aTargets;h.isArray(j)||D(a,1,"aTargets must be an array of targets, not a "+typeof j);f=0;for(g=j.length;f<g;f++)if("number"===typeof j[f]&&0<=j[f]){for(;a.aoColumns.length<=j[f];)o(a);d(j[f],b[i])}else if("number"===typeof j[f]&&0>j[f])d(a.aoColumns.length+j[f],b[i]);else if("string"===typeof j[f]){e=0;for(w=a.aoColumns.length;e<w;e++)("_all"==j[f]||h(a.aoColumns[e].nTh).hasClass(j[f]))&&d(e,b[i])}}if(c){i=0;for(a=c.length;i<a;i++)d(i,c[i])}}function H(a,
b){var c;c=h.isArray(b)?b.slice():h.extend(!0,{},b);var d=a.aoData.length,i=h.extend(!0,{},j.models.oRow);i._aData=c;a.aoData.push(i);for(var f,i=0,g=a.aoColumns.length;i<g;i++)c=a.aoColumns[i],"function"===typeof c.fnRender&&c.bUseRendered&&null!==c.mData?F(a,d,i,S(a,d,i)):F(a,d,i,v(a,d,i)),c._bAutoType&&"string"!=c.sType&&(f=v(a,d,i,"type"),null!==f&&""!==f&&(f=B(f),null===c.sType?c.sType=f:c.sType!=f&&"html"!=c.sType&&(c.sType="string")));a.aiDisplayMaster.push(d);a.oFeatures.bDeferRender||ea(a,
d);return d}function ua(a){var b,c,d,i,f,g,e;if(a.bDeferLoading||null===a.sAjaxSource)for(b=a.nTBody.firstChild;b;){if("TR"==b.nodeName.toUpperCase()){c=a.aoData.length;b._DT_RowIndex=c;a.aoData.push(h.extend(!0,{},j.models.oRow,{nTr:b}));a.aiDisplayMaster.push(c);f=b.firstChild;for(d=0;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)F(a,c,d,h.trim(f.innerHTML)),d++;f=f.nextSibling}}b=b.nextSibling}i=T(a);d=[];b=0;for(c=i.length;b<c;b++)for(f=i[b].firstChild;f;)g=f.nodeName.toUpperCase(),("TD"==
g||"TH"==g)&&d.push(f),f=f.nextSibling;c=0;for(i=a.aoColumns.length;c<i;c++){e=a.aoColumns[c];null===e.sTitle&&(e.sTitle=e.nTh.innerHTML);var w=e._bAutoType,o="function"===typeof e.fnRender,k=null!==e.sClass,n=e.bVisible,m,p;if(w||o||k||!n){g=0;for(b=a.aoData.length;g<b;g++)f=a.aoData[g],m=d[g*i+c],w&&"string"!=e.sType&&(p=v(a,g,c,"type"),""!==p&&(p=B(p),null===e.sType?e.sType=p:e.sType!=p&&"html"!=e.sType&&(e.sType="string"))),e.mRender?m.innerHTML=v(a,g,c,"display"):e.mData!==c&&(m.innerHTML=v(a,
g,c,"display")),o&&(p=S(a,g,c),m.innerHTML=p,e.bUseRendered&&F(a,g,c,p)),k&&(m.className+=" "+e.sClass),n?f._anHidden[c]=null:(f._anHidden[c]=m,m.parentNode.removeChild(m)),e.fnCreatedCell&&e.fnCreatedCell.call(a.oInstance,m,v(a,g,c,"display"),f._aData,g,c)}}if(0!==a.aoRowCreatedCallback.length){b=0;for(c=a.aoData.length;b<c;b++)f=a.aoData[b],A(a,"aoRowCreatedCallback",null,[f.nTr,f._aData,b])}}function I(a,b){return b._DT_RowIndex!==n?b._DT_RowIndex:null}function fa(a,b,c){for(var b=J(a,b),d=0,a=
a.aoColumns.length;d<a;d++)if(b[d]===c)return d;return-1}function Y(a,b,c,d){for(var i=[],f=0,g=d.length;f<g;f++)i.push(v(a,b,d[f],c));return i}function v(a,b,c,d){var i=a.aoColumns[c];if((c=i.fnGetData(a.aoData[b]._aData,d))===n)return a.iDrawError!=a.iDraw&&null===i.sDefaultContent&&(D(a,0,"Requested unknown parameter "+("function"==typeof i.mData?"{mData function}":"'"+i.mData+"'")+" from the data source for row "+b),a.iDrawError=a.iDraw),i.sDefaultContent;if(null===c&&null!==i.sDefaultContent)c=
i.sDefaultContent;else if("function"===typeof c)return c();return"display"==d&&null===c?"":c}function F(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d)}function Q(a){if(null===a)return function(){return null};if("function"===typeof a)return function(b,d,i){return a(b,d,i)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("["))){var b=function(a,d,i){var f=i.split("."),g;if(""!==i){var e=0;for(g=f.length;e<g;e++){if(i=f[e].match(U)){f[e]=f[e].replace(U,"");""!==f[e]&&(a=a[f[e]]);
g=[];f.splice(0,e+1);for(var f=f.join("."),e=0,h=a.length;e<h;e++)g.push(b(a[e],d,f));a=i[0].substring(1,i[0].length-1);a=""===a?g:g.join(a);break}if(null===a||a[f[e]]===n)return n;a=a[f[e]]}}return a};return function(c,d){return b(c,d,a)}}return function(b){return b[a]}}function L(a){if(null===a)return function(){};if("function"===typeof a)return function(b,d){a(b,"set",d)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("["))){var b=function(a,d,i){var i=i.split("."),f,g,e=0;for(g=
i.length-1;e<g;e++){if(f=i[e].match(U)){i[e]=i[e].replace(U,"");a[i[e]]=[];f=i.slice();f.splice(0,e+1);g=f.join(".");for(var h=0,j=d.length;h<j;h++)f={},b(f,d[h],g),a[i[e]].push(f);return}if(null===a[i[e]]||a[i[e]]===n)a[i[e]]={};a=a[i[e]]}a[i[i.length-1].replace(U,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Z(a){for(var b=[],c=a.aoData.length,d=0;d<c;d++)b.push(a.aoData[d]._aData);return b}function ga(a){a.aoData.splice(0,a.aoData.length);a.aiDisplayMaster.splice(0,
a.aiDisplayMaster.length);a.aiDisplay.splice(0,a.aiDisplay.length);y(a)}function ha(a,b){for(var c=-1,d=0,i=a.length;d<i;d++)a[d]==b?c=d:a[d]>b&&a[d]--; -1!=c&&a.splice(c,1)}function S(a,b,c){var d=a.aoColumns[c];return d.fnRender({iDataRow:b,iDataColumn:c,oSettings:a,aData:a.aoData[b]._aData,mDataProp:d.mData},v(a,b,c,"display"))}function ea(a,b){var c=a.aoData[b],d;if(null===c.nTr){c.nTr=l.createElement("tr");c.nTr._DT_RowIndex=b;c._aData.DT_RowId&&(c.nTr.id=c._aData.DT_RowId);c._aData.DT_RowClass&&
(c.nTr.className=c._aData.DT_RowClass);for(var i=0,f=a.aoColumns.length;i<f;i++){var g=a.aoColumns[i];d=l.createElement(g.sCellType);d.innerHTML="function"===typeof g.fnRender&&(!g.bUseRendered||null===g.mData)?S(a,b,i):v(a,b,i,"display");null!==g.sClass&&(d.className=g.sClass);g.bVisible?(c.nTr.appendChild(d),c._anHidden[i]=null):c._anHidden[i]=d;g.fnCreatedCell&&g.fnCreatedCell.call(a.oInstance,d,v(a,b,i,"display"),c._aData,b,i)}A(a,"aoRowCreatedCallback",null,[c.nTr,c._aData,b])}}function va(a){var b,
c,d;if(0!==h("th, td",a.nTHead).length){b=0;for(d=a.aoColumns.length;b<d;b++)if(c=a.aoColumns[b].nTh,c.setAttribute("role","columnheader"),a.aoColumns[b].bSortable&&(c.setAttribute("tabindex",a.iTabIndex),c.setAttribute("aria-controls",a.sTableId)),null!==a.aoColumns[b].sClass&&h(c).addClass(a.aoColumns[b].sClass),a.aoColumns[b].sTitle!=c.innerHTML)c.innerHTML=a.aoColumns[b].sTitle}else{var i=l.createElement("tr");b=0;for(d=a.aoColumns.length;b<d;b++)c=a.aoColumns[b].nTh,c.innerHTML=a.aoColumns[b].sTitle,
c.setAttribute("tabindex","0"),null!==a.aoColumns[b].sClass&&h(c).addClass(a.aoColumns[b].sClass),i.appendChild(c);h(a.nTHead).html("")[0].appendChild(i);V(a.aoHeader,a.nTHead)}h(a.nTHead).children("tr").attr("role","row");if(a.bJUI){b=0;for(d=a.aoColumns.length;b<d;b++){c=a.aoColumns[b].nTh;i=l.createElement("div");i.className=a.oClasses.sSortJUIWrapper;h(c).contents().appendTo(i);var f=l.createElement("span");f.className=a.oClasses.sSortIcon;i.appendChild(f);c.appendChild(i)}}if(a.oFeatures.bSort)for(b=
0;b<a.aoColumns.length;b++)!1!==a.aoColumns[b].bSortable?ia(a,a.aoColumns[b].nTh,b):h(a.aoColumns[b].nTh).addClass(a.oClasses.sSortableNone);""!==a.oClasses.sFooterTH&&h(a.nTFoot).children("tr").children("th").addClass(a.oClasses.sFooterTH);if(null!==a.nTFoot){c=N(a,null,a.aoFooter);b=0;for(d=a.aoColumns.length;b<d;b++)c[b]&&(a.aoColumns[b].nTf=c[b],a.aoColumns[b].sClass&&h(c[b]).addClass(a.aoColumns[b].sClass))}}function W(a,b,c){var d,i,f,g=[],e=[],h=a.aoColumns.length,j;c===n&&(c=!1);d=0;for(i=
b.length;d<i;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=h-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);e.push([])}d=0;for(i=g.length;d<i;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(j=h=1,e[d][f]===n){a.appendChild(g[d][f].cell);for(e[d][f]=1;g[d+h]!==n&&g[d][f].cell==g[d+h][f].cell;)e[d+h][f]=1,h++;for(;g[d][f+j]!==n&&g[d][f].cell==g[d][f+j].cell;){for(c=0;c<h;c++)e[d+c][f+j]=1;j++}g[d][f].cell.rowSpan=h;g[d][f].cell.colSpan=j}}}function x(a){var b=
A(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))E(a,!1);else{var c,d,b=[],i=0,f=a.asStripeClasses.length;c=a.aoOpenRows.length;a.bDrawing=!0;a.iInitDisplayStart!==n&&-1!=a.iInitDisplayStart&&(a._iDisplayStart=a.oFeatures.bServerSide?a.iInitDisplayStart:a.iInitDisplayStart>=a.fnRecordsDisplay()?0:a.iInitDisplayStart,a.iInitDisplayStart=-1,y(a));if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++;else if(a.oFeatures.bServerSide){if(!a.bDestroying&&!wa(a))return}else a.iDraw++;if(0!==a.aiDisplay.length){var g=
a._iDisplayStart;d=a._iDisplayEnd;a.oFeatures.bServerSide&&(g=0,d=a.aoData.length);for(;g<d;g++){var e=a.aoData[a.aiDisplay[g]];null===e.nTr&&ea(a,a.aiDisplay[g]);var j=e.nTr;if(0!==f){var o=a.asStripeClasses[i%f];e._sRowStripe!=o&&(h(j).removeClass(e._sRowStripe).addClass(o),e._sRowStripe=o)}A(a,"aoRowCallback",null,[j,a.aoData[a.aiDisplay[g]]._aData,i,g]);b.push(j);i++;if(0!==c)for(e=0;e<c;e++)if(j==a.aoOpenRows[e].nParent){b.push(a.aoOpenRows[e].nTr);break}}}else b[0]=l.createElement("tr"),a.asStripeClasses[0]&&
(b[0].className=a.asStripeClasses[0]),c=a.oLanguage,f=c.sZeroRecords,1==a.iDraw&&null!==a.sAjaxSource&&!a.oFeatures.bServerSide?f=c.sLoadingRecords:c.sEmptyTable&&0===a.fnRecordsTotal()&&(f=c.sEmptyTable),c=l.createElement("td"),c.setAttribute("valign","top"),c.colSpan=t(a),c.className=a.oClasses.sRowEmpty,c.innerHTML=ja(a,f),b[i].appendChild(c);A(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Z(a),a._iDisplayStart,a.fnDisplayEnd(),a.aiDisplay]);A(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],
Z(a),a._iDisplayStart,a.fnDisplayEnd(),a.aiDisplay]);i=l.createDocumentFragment();c=l.createDocumentFragment();if(a.nTBody){f=a.nTBody.parentNode;c.appendChild(a.nTBody);if(!a.oScroll.bInfinite||!a._bInitComplete||a.bSorted||a.bFiltered)for(;c=a.nTBody.firstChild;)a.nTBody.removeChild(c);c=0;for(d=b.length;c<d;c++)i.appendChild(b[c]);a.nTBody.appendChild(i);null!==f&&f.appendChild(a.nTBody)}A(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1;a.oFeatures.bServerSide&&(E(a,!1),
a._bInitComplete||$(a))}}function aa(a){a.oFeatures.bSort?O(a,a.oPreviousSearch):a.oFeatures.bFilter?K(a,a.oPreviousSearch):(y(a),x(a))}function xa(a){var b=h("<div></div>")[0];a.nTable.parentNode.insertBefore(b,a.nTable);a.nTableWrapper=h('<div id="'+a.sTableId+'_wrapper" class="'+a.oClasses.sWrapper+'" role="grid"></div>')[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var c=a.nTableWrapper,d=a.sDom.split(""),i,f,g,e,w,o,k,m=0;m<d.length;m++){f=0;g=d[m];if("<"==g){e=h("<div></div>")[0];w=d[m+
1];if("'"==w||'"'==w){o="";for(k=2;d[m+k]!=w;)o+=d[m+k],k++;"H"==o?o=a.oClasses.sJUIHeader:"F"==o&&(o=a.oClasses.sJUIFooter);-1!=o.indexOf(".")?(w=o.split("."),e.id=w[0].substr(1,w[0].length-1),e.className=w[1]):"#"==o.charAt(0)?e.id=o.substr(1,o.length-1):e.className=o;m+=k}c.appendChild(e);c=e}else if(">"==g)c=c.parentNode;else if("l"==g&&a.oFeatures.bPaginate&&a.oFeatures.bLengthChange)i=ya(a),f=1;else if("f"==g&&a.oFeatures.bFilter)i=za(a),f=1;else if("r"==g&&a.oFeatures.bProcessing)i=Aa(a),f=
1;else if("t"==g)i=Ba(a),f=1;else if("i"==g&&a.oFeatures.bInfo)i=Ca(a),f=1;else if("p"==g&&a.oFeatures.bPaginate)i=Da(a),f=1;else if(0!==j.ext.aoFeatures.length){e=j.ext.aoFeatures;k=0;for(w=e.length;k<w;k++)if(g==e[k].cFeature){(i=e[k].fnInit(a))&&(f=1);break}}1==f&&null!==i&&("object"!==typeof a.aanFeatures[g]&&(a.aanFeatures[g]=[]),a.aanFeatures[g].push(i),c.appendChild(i))}b.parentNode.replaceChild(a.nTableWrapper,b)}function V(a,b){var c=h(b).children("tr"),d,i,f,g,e,j,o,k,m,p;a.splice(0,a.length);
f=0;for(j=c.length;f<j;f++)a.push([]);f=0;for(j=c.length;f<j;f++){d=c[f];for(i=d.firstChild;i;){if("TD"==i.nodeName.toUpperCase()||"TH"==i.nodeName.toUpperCase()){k=1*i.getAttribute("colspan");m=1*i.getAttribute("rowspan");k=!k||0===k||1===k?1:k;m=!m||0===m||1===m?1:m;g=0;for(e=a[f];e[g];)g++;o=g;p=1===k?!0:!1;for(e=0;e<k;e++)for(g=0;g<m;g++)a[f+g][o+e]={cell:i,unique:p},a[f+g].nTr=d}i=i.nextSibling}}}function N(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],V(c,b)));for(var b=0,i=c.length;b<i;b++)for(var f=
0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function wa(a){if(a.bAjaxDataGet){a.iDraw++;E(a,!0);var b=Ea(a);ka(a,b);a.fnServerData.call(a.oInstance,a.sAjaxSource,b,function(b){Fa(a,b)},a);return!1}return!0}function Ea(a){var b=a.aoColumns.length,c=[],d,i,f,g;c.push({name:"sEcho",value:a.iDraw});c.push({name:"iColumns",value:b});c.push({name:"sColumns",value:M(a)});c.push({name:"iDisplayStart",value:a._iDisplayStart});c.push({name:"iDisplayLength",
value:!1!==a.oFeatures.bPaginate?a._iDisplayLength:-1});for(f=0;f<b;f++)d=a.aoColumns[f].mData,c.push({name:"mDataProp_"+f,value:"function"===typeof d?"function":d});if(!1!==a.oFeatures.bFilter){c.push({name:"sSearch",value:a.oPreviousSearch.sSearch});c.push({name:"bRegex",value:a.oPreviousSearch.bRegex});for(f=0;f<b;f++)c.push({name:"sSearch_"+f,value:a.aoPreSearchCols[f].sSearch}),c.push({name:"bRegex_"+f,value:a.aoPreSearchCols[f].bRegex}),c.push({name:"bSearchable_"+f,value:a.aoColumns[f].bSearchable})}if(!1!==
a.oFeatures.bSort){var e=0;d=null!==a.aaSortingFixed?a.aaSortingFixed.concat(a.aaSorting):a.aaSorting.slice();for(f=0;f<d.length;f++){i=a.aoColumns[d[f][0]].aDataSort;for(g=0;g<i.length;g++)c.push({name:"iSortCol_"+e,value:i[g]}),c.push({name:"sSortDir_"+e,value:d[f][1]}),e++}c.push({name:"iSortingCols",value:e});for(f=0;f<b;f++)c.push({name:"bSortable_"+f,value:a.aoColumns[f].bSortable})}return c}function ka(a,b){A(a,"aoServerParams","serverParams",[b])}function Fa(a,b){if(b.sEcho!==n){if(1*b.sEcho<
a.iDraw)return;a.iDraw=1*b.sEcho}(!a.oScroll.bInfinite||a.oScroll.bInfinite&&(a.bSorted||a.bFiltered))&&ga(a);a._iRecordsTotal=parseInt(b.iTotalRecords,10);a._iRecordsDisplay=parseInt(b.iTotalDisplayRecords,10);var c=M(a),c=b.sColumns!==n&&""!==c&&b.sColumns!=c,d;c&&(d=u(a,b.sColumns));for(var i=Q(a.sAjaxDataProp)(b),f=0,g=i.length;f<g;f++)if(c){for(var e=[],h=0,j=a.aoColumns.length;h<j;h++)e.push(i[f][d[h]]);H(a,e)}else H(a,i[f]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;x(a);a.bAjaxDataGet=
!0;E(a,!1)}function za(a){var b=a.oPreviousSearch,c=a.oLanguage.sSearch,c=-1!==c.indexOf("_INPUT_")?c.replace("_INPUT_",'<input type="text" />'):""===c?'<input type="text" />':c+' <input type="text" />',d=l.createElement("div");d.className=a.oClasses.sFilter;d.innerHTML="<label>"+c+"</label>";a.aanFeatures.f||(d.id=a.sTableId+"_filter");c=h('input[type="text"]',d);d._DT_Input=c[0];c.val(b.sSearch.replace('"',"&quot;"));c.bind("keyup.DT",function(){for(var c=a.aanFeatures.f,d=this.value===""?"":this.value,
g=0,e=c.length;g<e;g++)c[g]!=h(this).parents("div.dataTables_filter")[0]&&h(c[g]._DT_Input).val(d);d!=b.sSearch&&K(a,{sSearch:d,bRegex:b.bRegex,bSmart:b.bSmart,bCaseInsensitive:b.bCaseInsensitive})});c.attr("aria-controls",a.sTableId).bind("keypress.DT",function(a){if(a.keyCode==13)return false});return d}function K(a,b,c){var d=a.oPreviousSearch,i=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};if(a.oFeatures.bServerSide)f(b);
else{Ga(a,b.sSearch,c,b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<a.aoPreSearchCols.length;b++)Ha(a,i[b].sSearch,b,i[b].bRegex,i[b].bSmart,i[b].bCaseInsensitive);Ia(a)}a.bFiltered=!0;h(a.oInstance).trigger("filter",a);a._iDisplayStart=0;y(a);x(a);la(a,0)}function Ia(a){for(var b=j.ext.afnFiltering,c=r(a,"bSearchable"),d=0,i=b.length;d<i;d++)for(var f=0,g=0,e=a.aiDisplay.length;g<e;g++){var h=a.aiDisplay[g-f];b[d](a,Y(a,h,"filter",c),h)||(a.aiDisplay.splice(g-f,1),f++)}}function Ha(a,b,c,
d,i,f){if(""!==b)for(var g=0,b=ma(b,d,i,f),d=a.aiDisplay.length-1;0<=d;d--)i=Ja(v(a,a.aiDisplay[d],c,"filter"),a.aoColumns[c].sType),b.test(i)||(a.aiDisplay.splice(d,1),g++)}function Ga(a,b,c,d,i,f){d=ma(b,d,i,f);i=a.oPreviousSearch;c||(c=0);0!==j.ext.afnFiltering.length&&(c=1);if(0>=b.length)a.aiDisplay.splice(0,a.aiDisplay.length),a.aiDisplay=a.aiDisplayMaster.slice();else if(a.aiDisplay.length==a.aiDisplayMaster.length||i.sSearch.length>b.length||1==c||0!==b.indexOf(i.sSearch)){a.aiDisplay.splice(0,
a.aiDisplay.length);la(a,1);for(b=0;b<a.aiDisplayMaster.length;b++)d.test(a.asDataSearch[b])&&a.aiDisplay.push(a.aiDisplayMaster[b])}else for(b=c=0;b<a.asDataSearch.length;b++)d.test(a.asDataSearch[b])||(a.aiDisplay.splice(b-c,1),c++)}function la(a,b){if(!a.oFeatures.bServerSide){a.asDataSearch=[];for(var c=r(a,"bSearchable"),d=1===b?a.aiDisplayMaster:a.aiDisplay,i=0,f=d.length;i<f;i++)a.asDataSearch[i]=na(a,Y(a,d[i],"filter",c))}}function na(a,b){var c=b.join(" ");-1!==c.indexOf("&")&&(c=h("<div>").html(c).text());
return c.replace(/[\n\r]/g," ")}function ma(a,b,c,d){if(c)return a=b?a.split(" "):oa(a).split(" "),a="^(?=.*?"+a.join(")(?=.*?")+").*$",RegExp(a,d?"i":"");a=b?a:oa(a);return RegExp(a,d?"i":"")}function Ja(a,b){return"function"===typeof j.ext.ofnSearch[b]?j.ext.ofnSearch[b](a):null===a?"":"html"==b?a.replace(/[\r\n]/g," ").replace(/<.*?>/g,""):"string"===typeof a?a.replace(/[\r\n]/g," "):a}function oa(a){return a.replace(RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),
"\\$1")}function Ca(a){var b=l.createElement("div");b.className=a.oClasses.sInfo;a.aanFeatures.i||(a.aoDrawCallback.push({fn:Ka,sName:"information"}),b.id=a.sTableId+"_info");a.nTable.setAttribute("aria-describedby",a.sTableId+"_info");return b}function Ka(a){if(a.oFeatures.bInfo&&0!==a.aanFeatures.i.length){var b=a.oLanguage,c=a._iDisplayStart+1,d=a.fnDisplayEnd(),i=a.fnRecordsTotal(),f=a.fnRecordsDisplay(),g;g=0===f?b.sInfoEmpty:b.sInfo;f!=i&&(g+=" "+b.sInfoFiltered);g+=b.sInfoPostFix;g=ja(a,g);
null!==b.fnInfoCallback&&(g=b.fnInfoCallback.call(a.oInstance,a,c,d,i,f,g));a=a.aanFeatures.i;b=0;for(c=a.length;b<c;b++)h(a[b]).html(g)}}function ja(a,b){var c=a.fnFormatNumber(a._iDisplayStart+1),d=a.fnDisplayEnd(),d=a.fnFormatNumber(d),i=a.fnRecordsDisplay(),i=a.fnFormatNumber(i),f=a.fnRecordsTotal(),f=a.fnFormatNumber(f);a.oScroll.bInfinite&&(c=a.fnFormatNumber(1));return b.replace(/_START_/g,c).replace(/_END_/g,d).replace(/_TOTAL_/g,i).replace(/_MAX_/g,f)}function ba(a){var b,c,d=a.iInitDisplayStart;
if(!1===a.bInitialised)setTimeout(function(){ba(a)},200);else{xa(a);va(a);W(a,a.aoHeader);a.nTFoot&&W(a,a.aoFooter);E(a,!0);a.oFeatures.bAutoWidth&&da(a);b=0;for(c=a.aoColumns.length;b<c;b++)null!==a.aoColumns[b].sWidth&&(a.aoColumns[b].nTh.style.width=q(a.aoColumns[b].sWidth));a.oFeatures.bSort?O(a):a.oFeatures.bFilter?K(a,a.oPreviousSearch):(a.aiDisplay=a.aiDisplayMaster.slice(),y(a),x(a));null!==a.sAjaxSource&&!a.oFeatures.bServerSide?(c=[],ka(a,c),a.fnServerData.call(a.oInstance,a.sAjaxSource,
c,function(c){var f=a.sAjaxDataProp!==""?Q(a.sAjaxDataProp)(c):c;for(b=0;b<f.length;b++)H(a,f[b]);a.iInitDisplayStart=d;if(a.oFeatures.bSort)O(a);else{a.aiDisplay=a.aiDisplayMaster.slice();y(a);x(a)}E(a,false);$(a,c)},a)):a.oFeatures.bServerSide||(E(a,!1),$(a))}}function $(a,b){a._bInitComplete=!0;A(a,"aoInitComplete","init",[a,b])}function pa(a){var b=j.defaults.oLanguage;!a.sEmptyTable&&(a.sZeroRecords&&"No data available in table"===b.sEmptyTable)&&p(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&
(a.sZeroRecords&&"Loading..."===b.sLoadingRecords)&&p(a,a,"sZeroRecords","sLoadingRecords")}function ya(a){if(a.oScroll.bInfinite)return null;var b='<select size="1" '+('name="'+a.sTableId+'_length"')+">",c,d,i=a.aLengthMenu;if(2==i.length&&"object"===typeof i[0]&&"object"===typeof i[1]){c=0;for(d=i[0].length;c<d;c++)b+='<option value="'+i[0][c]+'">'+i[1][c]+"</option>"}else{c=0;for(d=i.length;c<d;c++)b+='<option value="'+i[c]+'">'+i[c]+"</option>"}b+="</select>";i=l.createElement("div");a.aanFeatures.l||
(i.id=a.sTableId+"_length");i.className=a.oClasses.sLength;i.innerHTML="<label>"+a.oLanguage.sLengthMenu.replace("_MENU_",b)+"</label>";h('select option[value="'+a._iDisplayLength+'"]',i).attr("selected",!0);h("select",i).bind("change.DT",function(){var b=h(this).val(),i=a.aanFeatures.l;c=0;for(d=i.length;c<d;c++)i[c]!=this.parentNode&&h("select",i[c]).val(b);a._iDisplayLength=parseInt(b,10);y(a);if(a.fnDisplayEnd()==a.fnRecordsDisplay()){a._iDisplayStart=a.fnDisplayEnd()-a._iDisplayLength;if(a._iDisplayStart<
0)a._iDisplayStart=0}if(a._iDisplayLength==-1)a._iDisplayStart=0;x(a)});h("select",i).attr("aria-controls",a.sTableId);return i}function y(a){a._iDisplayEnd=!1===a.oFeatures.bPaginate?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength>a.aiDisplay.length||-1==a._iDisplayLength?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength}function Da(a){if(a.oScroll.bInfinite)return null;var b=l.createElement("div");b.className=a.oClasses.sPaging+a.sPaginationType;j.ext.oPagination[a.sPaginationType].fnInit(a,
b,function(a){y(a);x(a)});a.aanFeatures.p||a.aoDrawCallback.push({fn:function(a){j.ext.oPagination[a.sPaginationType].fnUpdate(a,function(a){y(a);x(a)})},sName:"pagination"});return b}function qa(a,b){var c=a._iDisplayStart;if("number"===typeof b)a._iDisplayStart=b*a._iDisplayLength,a._iDisplayStart>a.fnRecordsDisplay()&&(a._iDisplayStart=0);else if("first"==b)a._iDisplayStart=0;else if("previous"==b)a._iDisplayStart=0<=a._iDisplayLength?a._iDisplayStart-a._iDisplayLength:0,0>a._iDisplayStart&&(a._iDisplayStart=
0);else if("next"==b)0<=a._iDisplayLength?a._iDisplayStart+a._iDisplayLength<a.fnRecordsDisplay()&&(a._iDisplayStart+=a._iDisplayLength):a._iDisplayStart=0;else if("last"==b)if(0<=a._iDisplayLength){var d=parseInt((a.fnRecordsDisplay()-1)/a._iDisplayLength,10)+1;a._iDisplayStart=(d-1)*a._iDisplayLength}else a._iDisplayStart=0;else D(a,0,"Unknown paging action: "+b);h(a.oInstance).trigger("page",a);return c!=a._iDisplayStart}function Aa(a){var b=l.createElement("div");a.aanFeatures.r||(b.id=a.sTableId+
"_processing");b.innerHTML=a.oLanguage.sProcessing;b.className=a.oClasses.sProcessing;a.nTable.parentNode.insertBefore(b,a.nTable);return b}function E(a,b){if(a.oFeatures.bProcessing)for(var c=a.aanFeatures.r,d=0,i=c.length;d<i;d++)c[d].style.visibility=b?"visible":"hidden";h(a.oInstance).trigger("processing",[a,b])}function Ba(a){if(""===a.oScroll.sX&&""===a.oScroll.sY)return a.nTable;var b=l.createElement("div"),c=l.createElement("div"),d=l.createElement("div"),i=l.createElement("div"),f=l.createElement("div"),
g=l.createElement("div"),e=a.nTable.cloneNode(!1),j=a.nTable.cloneNode(!1),o=a.nTable.getElementsByTagName("thead")[0],k=0===a.nTable.getElementsByTagName("tfoot").length?null:a.nTable.getElementsByTagName("tfoot")[0],m=a.oClasses;c.appendChild(d);f.appendChild(g);i.appendChild(a.nTable);b.appendChild(c);b.appendChild(i);d.appendChild(e);e.appendChild(o);null!==k&&(b.appendChild(f),g.appendChild(j),j.appendChild(k));b.className=m.sScrollWrapper;c.className=m.sScrollHead;d.className=m.sScrollHeadInner;
i.className=m.sScrollBody;f.className=m.sScrollFoot;g.className=m.sScrollFootInner;a.oScroll.bAutoCss&&(c.style.overflow="hidden",c.style.position="relative",f.style.overflow="hidden",i.style.overflow="auto");c.style.border="0";c.style.width="100%";f.style.border="0";d.style.width=""!==a.oScroll.sXInner?a.oScroll.sXInner:"100%";e.removeAttribute("id");e.style.marginLeft="0";a.nTable.style.marginLeft="0";null!==k&&(j.removeAttribute("id"),j.style.marginLeft="0");d=h(a.nTable).children("caption");0<
d.length&&(d=d[0],"top"===d._captionSide?e.appendChild(d):"bottom"===d._captionSide&&k&&j.appendChild(d));""!==a.oScroll.sX&&(c.style.width=q(a.oScroll.sX),i.style.width=q(a.oScroll.sX),null!==k&&(f.style.width=q(a.oScroll.sX)),h(i).scroll(function(){c.scrollLeft=this.scrollLeft;if(k!==null)f.scrollLeft=this.scrollLeft}));""!==a.oScroll.sY&&(i.style.height=q(a.oScroll.sY));a.aoDrawCallback.push({fn:La,sName:"scrolling"});a.oScroll.bInfinite&&h(i).scroll(function(){if(!a.bDrawing&&h(this).scrollTop()!==
0&&h(this).scrollTop()+h(this).height()>h(a.nTable).height()-a.oScroll.iLoadGap&&a.fnDisplayEnd()<a.fnRecordsDisplay()){qa(a,"next");y(a);x(a)}});a.nScrollHead=c;a.nScrollFoot=f;return b}function La(a){var b=a.nScrollHead.getElementsByTagName("div")[0],c=b.getElementsByTagName("table")[0],d=a.nTable.parentNode,i,f,g,e,j,o,k,m,p=[],n=[],l=null!==a.nTFoot?a.nScrollFoot.getElementsByTagName("div")[0]:null,R=null!==a.nTFoot?l.getElementsByTagName("table")[0]:null,r=a.oBrowser.bScrollOversize,s=function(a){k=
a.style;k.paddingTop="0";k.paddingBottom="0";k.borderTopWidth="0";k.borderBottomWidth="0";k.height=0};h(a.nTable).children("thead, tfoot").remove();i=h(a.nTHead).clone()[0];a.nTable.insertBefore(i,a.nTable.childNodes[0]);g=a.nTHead.getElementsByTagName("tr");e=i.getElementsByTagName("tr");null!==a.nTFoot&&(j=h(a.nTFoot).clone()[0],a.nTable.insertBefore(j,a.nTable.childNodes[1]),o=a.nTFoot.getElementsByTagName("tr"),j=j.getElementsByTagName("tr"));""===a.oScroll.sX&&(d.style.width="100%",b.parentNode.style.width=
"100%");var t=N(a,i);i=0;for(f=t.length;i<f;i++)m=G(a,i),t[i].style.width=a.aoColumns[m].sWidth;null!==a.nTFoot&&C(function(a){a.style.width=""},j);a.oScroll.bCollapse&&""!==a.oScroll.sY&&(d.style.height=d.offsetHeight+a.nTHead.offsetHeight+"px");i=h(a.nTable).outerWidth();if(""===a.oScroll.sX){if(a.nTable.style.width="100%",r&&(h("tbody",d).height()>d.offsetHeight||"scroll"==h(d).css("overflow-y")))a.nTable.style.width=q(h(a.nTable).outerWidth()-a.oScroll.iBarWidth)}else""!==a.oScroll.sXInner?a.nTable.style.width=
q(a.oScroll.sXInner):i==h(d).width()&&h(d).height()<h(a.nTable).height()?(a.nTable.style.width=q(i-a.oScroll.iBarWidth),h(a.nTable).outerWidth()>i-a.oScroll.iBarWidth&&(a.nTable.style.width=q(i))):a.nTable.style.width=q(i);i=h(a.nTable).outerWidth();C(s,e);C(function(a){p.push(q(h(a).width()))},e);C(function(a,b){a.style.width=p[b]},g);h(e).height(0);null!==a.nTFoot&&(C(s,j),C(function(a){n.push(q(h(a).width()))},j),C(function(a,b){a.style.width=n[b]},o),h(j).height(0));C(function(a,b){a.innerHTML=
"";a.style.width=p[b]},e);null!==a.nTFoot&&C(function(a,b){a.innerHTML="";a.style.width=n[b]},j);if(h(a.nTable).outerWidth()<i){g=d.scrollHeight>d.offsetHeight||"scroll"==h(d).css("overflow-y")?i+a.oScroll.iBarWidth:i;if(r&&(d.scrollHeight>d.offsetHeight||"scroll"==h(d).css("overflow-y")))a.nTable.style.width=q(g-a.oScroll.iBarWidth);d.style.width=q(g);a.nScrollHead.style.width=q(g);null!==a.nTFoot&&(a.nScrollFoot.style.width=q(g));""===a.oScroll.sX?D(a,1,"The table cannot fit into the current element which will cause column misalignment. The table has been drawn at its minimum possible width."):
""!==a.oScroll.sXInner&&D(a,1,"The table cannot fit into the current element which will cause column misalignment. Increase the sScrollXInner value or remove it to allow automatic calculation")}else d.style.width=q("100%"),a.nScrollHead.style.width=q("100%"),null!==a.nTFoot&&(a.nScrollFoot.style.width=q("100%"));""===a.oScroll.sY&&r&&(d.style.height=q(a.nTable.offsetHeight+a.oScroll.iBarWidth));""!==a.oScroll.sY&&a.oScroll.bCollapse&&(d.style.height=q(a.oScroll.sY),r=""!==a.oScroll.sX&&a.nTable.offsetWidth>
d.offsetWidth?a.oScroll.iBarWidth:0,a.nTable.offsetHeight<d.offsetHeight&&(d.style.height=q(a.nTable.offsetHeight+r)));r=h(a.nTable).outerWidth();c.style.width=q(r);b.style.width=q(r);c=h(a.nTable).height()>d.clientHeight||"scroll"==h(d).css("overflow-y");b.style.paddingRight=c?a.oScroll.iBarWidth+"px":"0px";null!==a.nTFoot&&(R.style.width=q(r),l.style.width=q(r),l.style.paddingRight=c?a.oScroll.iBarWidth+"px":"0px");h(d).scroll();if(a.bSorted||a.bFiltered)d.scrollTop=0}function C(a,b,c){for(var d=
0,i=0,f=b.length,g,e;i<f;){g=b[i].firstChild;for(e=c?c[i].firstChild:null;g;)1===g.nodeType&&(c?a(g,e,d):a(g,d),d++),g=g.nextSibling,e=c?e.nextSibling:null;i++}}function Ma(a,b){if(!a||null===a||""===a)return 0;b||(b=l.body);var c,d=l.createElement("div");d.style.width=q(a);b.appendChild(d);c=d.offsetWidth;b.removeChild(d);return c}function da(a){var b=0,c,d=0,i=a.aoColumns.length,f,e,j=h("th",a.nTHead),o=a.nTable.getAttribute("width");e=a.nTable.parentNode;for(f=0;f<i;f++)a.aoColumns[f].bVisible&&
(d++,null!==a.aoColumns[f].sWidth&&(c=Ma(a.aoColumns[f].sWidthOrig,e),null!==c&&(a.aoColumns[f].sWidth=q(c)),b++));if(i==j.length&&0===b&&d==i&&""===a.oScroll.sX&&""===a.oScroll.sY)for(f=0;f<a.aoColumns.length;f++)c=h(j[f]).width(),null!==c&&(a.aoColumns[f].sWidth=q(c));else{b=a.nTable.cloneNode(!1);f=a.nTHead.cloneNode(!0);d=l.createElement("tbody");c=l.createElement("tr");b.removeAttribute("id");b.appendChild(f);null!==a.nTFoot&&(b.appendChild(a.nTFoot.cloneNode(!0)),C(function(a){a.style.width=
""},b.getElementsByTagName("tr")));b.appendChild(d);d.appendChild(c);d=h("thead th",b);0===d.length&&(d=h("tbody tr:eq(0)>td",b));j=N(a,f);for(f=d=0;f<i;f++){var k=a.aoColumns[f];k.bVisible&&null!==k.sWidthOrig&&""!==k.sWidthOrig?j[f-d].style.width=q(k.sWidthOrig):k.bVisible?j[f-d].style.width="":d++}for(f=0;f<i;f++)a.aoColumns[f].bVisible&&(d=Na(a,f),null!==d&&(d=d.cloneNode(!0),""!==a.aoColumns[f].sContentPadding&&(d.innerHTML+=a.aoColumns[f].sContentPadding),c.appendChild(d)));e.appendChild(b);
""!==a.oScroll.sX&&""!==a.oScroll.sXInner?b.style.width=q(a.oScroll.sXInner):""!==a.oScroll.sX?(b.style.width="",h(b).width()<e.offsetWidth&&(b.style.width=q(e.offsetWidth))):""!==a.oScroll.sY?b.style.width=q(e.offsetWidth):o&&(b.style.width=q(o));b.style.visibility="hidden";Oa(a,b);i=h("tbody tr:eq(0)",b).children();0===i.length&&(i=N(a,h("thead",b)[0]));if(""!==a.oScroll.sX){for(f=d=e=0;f<a.aoColumns.length;f++)a.aoColumns[f].bVisible&&(e=null===a.aoColumns[f].sWidthOrig?e+h(i[d]).outerWidth():
e+(parseInt(a.aoColumns[f].sWidth.replace("px",""),10)+(h(i[d]).outerWidth()-h(i[d]).width())),d++);b.style.width=q(e);a.nTable.style.width=q(e)}for(f=d=0;f<a.aoColumns.length;f++)a.aoColumns[f].bVisible&&(e=h(i[d]).width(),null!==e&&0<e&&(a.aoColumns[f].sWidth=q(e)),d++);i=h(b).css("width");a.nTable.style.width=-1!==i.indexOf("%")?i:q(h(b).outerWidth());b.parentNode.removeChild(b)}o&&(a.nTable.style.width=q(o))}function Oa(a,b){""===a.oScroll.sX&&""!==a.oScroll.sY?(h(b).width(),b.style.width=q(h(b).outerWidth()-
a.oScroll.iBarWidth)):""!==a.oScroll.sX&&(b.style.width=q(h(b).outerWidth()))}function Na(a,b){var c=Pa(a,b);if(0>c)return null;if(null===a.aoData[c].nTr){var d=l.createElement("td");d.innerHTML=v(a,c,b,"");return d}return J(a,c)[b]}function Pa(a,b){for(var c=-1,d=-1,i=0;i<a.aoData.length;i++){var e=v(a,i,b,"display")+"",e=e.replace(/<.*?>/g,"");e.length>c&&(c=e.length,d=i)}return d}function q(a){if(null===a)return"0px";if("number"==typeof a)return 0>a?"0px":a+"px";var b=a.charCodeAt(a.length-1);
return 48>b||57<b?a:a+"px"}function Qa(){var a=l.createElement("p"),b=a.style;b.width="100%";b.height="200px";b.padding="0px";var c=l.createElement("div"),b=c.style;b.position="absolute";b.top="0px";b.left="0px";b.visibility="hidden";b.width="200px";b.height="150px";b.padding="0px";b.overflow="hidden";c.appendChild(a);l.body.appendChild(c);b=a.offsetWidth;c.style.overflow="scroll";a=a.offsetWidth;b==a&&(a=c.clientWidth);l.body.removeChild(c);return b-a}function O(a,b){var c,d,i,e,g,k,o=[],m=[],p=
j.ext.oSort,l=a.aoData,q=a.aoColumns,G=a.oLanguage.oAria;if(!a.oFeatures.bServerSide&&(0!==a.aaSorting.length||null!==a.aaSortingFixed)){o=null!==a.aaSortingFixed?a.aaSortingFixed.concat(a.aaSorting):a.aaSorting.slice();for(c=0;c<o.length;c++)if(d=o[c][0],i=R(a,d),e=a.aoColumns[d].sSortDataType,j.ext.afnSortData[e])if(g=j.ext.afnSortData[e].call(a.oInstance,a,d,i),g.length===l.length){i=0;for(e=l.length;i<e;i++)F(a,i,d,g[i])}else D(a,0,"Returned data sort array (col "+d+") is the wrong length");c=
0;for(d=a.aiDisplayMaster.length;c<d;c++)m[a.aiDisplayMaster[c]]=c;var r=o.length,s;c=0;for(d=l.length;c<d;c++)for(i=0;i<r;i++){s=q[o[i][0]].aDataSort;g=0;for(k=s.length;g<k;g++)e=q[s[g]].sType,e=p[(e?e:"string")+"-pre"],l[c]._aSortData[s[g]]=e?e(v(a,c,s[g],"sort")):v(a,c,s[g],"sort")}a.aiDisplayMaster.sort(function(a,b){var c,d,e,i,f;for(c=0;c<r;c++){f=q[o[c][0]].aDataSort;d=0;for(e=f.length;d<e;d++)if(i=q[f[d]].sType,i=p[(i?i:"string")+"-"+o[c][1]](l[a]._aSortData[f[d]],l[b]._aSortData[f[d]]),0!==
i)return i}return p["numeric-asc"](m[a],m[b])})}(b===n||b)&&!a.oFeatures.bDeferRender&&P(a);c=0;for(d=a.aoColumns.length;c<d;c++)e=q[c].sTitle.replace(/<.*?>/g,""),i=q[c].nTh,i.removeAttribute("aria-sort"),i.removeAttribute("aria-label"),q[c].bSortable?0<o.length&&o[0][0]==c?(i.setAttribute("aria-sort","asc"==o[0][1]?"ascending":"descending"),i.setAttribute("aria-label",e+("asc"==(q[c].asSorting[o[0][2]+1]?q[c].asSorting[o[0][2]+1]:q[c].asSorting[0])?G.sSortAscending:G.sSortDescending))):i.setAttribute("aria-label",
e+("asc"==q[c].asSorting[0]?G.sSortAscending:G.sSortDescending)):i.setAttribute("aria-label",e);a.bSorted=!0;h(a.oInstance).trigger("sort",a);a.oFeatures.bFilter?K(a,a.oPreviousSearch,1):(a.aiDisplay=a.aiDisplayMaster.slice(),a._iDisplayStart=0,y(a),x(a))}function ia(a,b,c,d){Ra(b,{},function(b){if(!1!==a.aoColumns[c].bSortable){var e=function(){var d,e;if(b.shiftKey){for(var f=!1,h=0;h<a.aaSorting.length;h++)if(a.aaSorting[h][0]==c){f=!0;d=a.aaSorting[h][0];e=a.aaSorting[h][2]+1;a.aoColumns[d].asSorting[e]?
(a.aaSorting[h][1]=a.aoColumns[d].asSorting[e],a.aaSorting[h][2]=e):a.aaSorting.splice(h,1);break}!1===f&&a.aaSorting.push([c,a.aoColumns[c].asSorting[0],0])}else 1==a.aaSorting.length&&a.aaSorting[0][0]==c?(d=a.aaSorting[0][0],e=a.aaSorting[0][2]+1,a.aoColumns[d].asSorting[e]||(e=0),a.aaSorting[0][1]=a.aoColumns[d].asSorting[e],a.aaSorting[0][2]=e):(a.aaSorting.splice(0,a.aaSorting.length),a.aaSorting.push([c,a.aoColumns[c].asSorting[0],0]));O(a)};a.oFeatures.bProcessing?(E(a,!0),setTimeout(function(){e();
a.oFeatures.bServerSide||E(a,!1)},0)):e();"function"==typeof d&&d(a)}})}function P(a){var b,c,d,e,f,g=a.aoColumns.length,j=a.oClasses;for(b=0;b<g;b++)a.aoColumns[b].bSortable&&h(a.aoColumns[b].nTh).removeClass(j.sSortAsc+" "+j.sSortDesc+" "+a.aoColumns[b].sSortingClass);c=null!==a.aaSortingFixed?a.aaSortingFixed.concat(a.aaSorting):a.aaSorting.slice();for(b=0;b<a.aoColumns.length;b++)if(a.aoColumns[b].bSortable){f=a.aoColumns[b].sSortingClass;e=-1;for(d=0;d<c.length;d++)if(c[d][0]==b){f="asc"==c[d][1]?
j.sSortAsc:j.sSortDesc;e=d;break}h(a.aoColumns[b].nTh).addClass(f);a.bJUI&&(f=h("span."+j.sSortIcon,a.aoColumns[b].nTh),f.removeClass(j.sSortJUIAsc+" "+j.sSortJUIDesc+" "+j.sSortJUI+" "+j.sSortJUIAscAllowed+" "+j.sSortJUIDescAllowed),f.addClass(-1==e?a.aoColumns[b].sSortingClassJUI:"asc"==c[e][1]?j.sSortJUIAsc:j.sSortJUIDesc))}else h(a.aoColumns[b].nTh).addClass(a.aoColumns[b].sSortingClass);f=j.sSortColumn;if(a.oFeatures.bSort&&a.oFeatures.bSortClasses){a=J(a);e=[];for(b=0;b<g;b++)e.push("");b=0;
for(d=1;b<c.length;b++)j=parseInt(c[b][0],10),e[j]=f+d,3>d&&d++;f=RegExp(f+"[123]");var o;b=0;for(c=a.length;b<c;b++)j=b%g,d=a[b].className,o=e[j],j=d.replace(f,o),j!=d?a[b].className=h.trim(j):0<o.length&&-1==d.indexOf(o)&&(a[b].className=d+" "+o)}}function ra(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b,c;b=a.oScroll.bInfinite;var d={iCreate:(new Date).getTime(),iStart:b?0:a._iDisplayStart,iEnd:b?a._iDisplayLength:a._iDisplayEnd,iLength:a._iDisplayLength,aaSorting:h.extend(!0,[],a.aaSorting),
oSearch:h.extend(!0,{},a.oPreviousSearch),aoSearchCols:h.extend(!0,[],a.aoPreSearchCols),abVisCols:[]};b=0;for(c=a.aoColumns.length;b<c;b++)d.abVisCols.push(a.aoColumns[b].bVisible);A(a,"aoStateSaveParams","stateSaveParams",[a,d]);a.fnStateSave.call(a.oInstance,a,d)}}function Sa(a,b){if(a.oFeatures.bStateSave){var c=a.fnStateLoad.call(a.oInstance,a);if(c){var d=A(a,"aoStateLoadParams","stateLoadParams",[a,c]);if(-1===h.inArray(!1,d)){a.oLoadedState=h.extend(!0,{},c);a._iDisplayStart=c.iStart;a.iInitDisplayStart=
c.iStart;a._iDisplayEnd=c.iEnd;a._iDisplayLength=c.iLength;a.aaSorting=c.aaSorting.slice();a.saved_aaSorting=c.aaSorting.slice();h.extend(a.oPreviousSearch,c.oSearch);h.extend(!0,a.aoPreSearchCols,c.aoSearchCols);b.saved_aoColumns=[];for(d=0;d<c.abVisCols.length;d++)b.saved_aoColumns[d]={},b.saved_aoColumns[d].bVisible=c.abVisCols[d];A(a,"aoStateLoaded","stateLoaded",[a,c])}}}}function s(a){for(var b=0;b<j.settings.length;b++)if(j.settings[b].nTable===a)return j.settings[b];return null}function T(a){for(var b=
[],a=a.aoData,c=0,d=a.length;c<d;c++)null!==a[c].nTr&&b.push(a[c].nTr);return b}function J(a,b){var c=[],d,e,f,g,h,j;e=0;var o=a.aoData.length;b!==n&&(e=b,o=b+1);for(f=e;f<o;f++)if(j=a.aoData[f],null!==j.nTr){e=[];for(d=j.nTr.firstChild;d;)g=d.nodeName.toLowerCase(),("td"==g||"th"==g)&&e.push(d),d=d.nextSibling;g=d=0;for(h=a.aoColumns.length;g<h;g++)a.aoColumns[g].bVisible?c.push(e[g-d]):(c.push(j._anHidden[g]),d++)}return c}function D(a,b,c){a=null===a?"DataTables warning: "+c:"DataTables warning (table id = '"+
a.sTableId+"'): "+c;if(0===b)if("alert"==j.ext.sErrMode)alert(a);else throw Error(a);else X.console&&console.log&&console.log(a)}function p(a,b,c,d){d===n&&(d=c);b[c]!==n&&(a[d]=b[c])}function Ta(a,b){var c,d;for(d in b)b.hasOwnProperty(d)&&(c=b[d],"object"===typeof e[d]&&null!==c&&!1===h.isArray(c)?h.extend(!0,a[d],c):a[d]=c);return a}function Ra(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&c(a)}).bind("selectstart.DT",function(){return!1})}
function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function A(a,b,c,d){for(var b=a[b],e=[],f=b.length-1;0<=f;f--)e.push(b[f].fn.apply(a.oInstance,d));null!==c&&h(a.oInstance).trigger(c,d);return e}function Ua(a){var b=h('<div style="position:absolute; top:0; left:0; height:1px; width:1px; overflow:hidden"><div style="position:absolute; top:1px; left:1px; width:100px; overflow:scroll;"><div id="DT_BrowserTest" style="width:100%; height:10px;"></div></div></div>')[0];l.body.appendChild(b);a.oBrowser.bScrollOversize=
100===h("#DT_BrowserTest",b)[0].offsetWidth?!0:!1;l.body.removeChild(b)}function Va(a){return function(){var b=[s(this[j.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return j.ext.oApi[a].apply(this,b)}}var U=/\[.*?\]$/,Wa=X.JSON?JSON.stringify:function(a){var b=typeof a;if("object"!==b||null===a)return"string"===b&&(a='"'+a+'"'),a+"";var c,d,e=[],f=h.isArray(a);for(c in a)d=a[c],b=typeof d,"string"===b?d='"'+d+'"':"object"===b&&null!==d&&(d=Wa(d)),e.push((f?"":'"'+c+'":')+d);return(f?
"[":"{")+e+(f?"]":"}")};this.$=function(a,b){var c,d,e=[],f;d=s(this[j.ext.iApiIndex]);var g=d.aoData,o=d.aiDisplay,k=d.aiDisplayMaster;b||(b={});b=h.extend({},{filter:"none",order:"current",page:"all"},b);if("current"==b.page){c=d._iDisplayStart;for(d=d.fnDisplayEnd();c<d;c++)(f=g[o[c]].nTr)&&e.push(f)}else if("current"==b.order&&"none"==b.filter){c=0;for(d=k.length;c<d;c++)(f=g[k[c]].nTr)&&e.push(f)}else if("current"==b.order&&"applied"==b.filter){c=0;for(d=o.length;c<d;c++)(f=g[o[c]].nTr)&&e.push(f)}else if("original"==
b.order&&"none"==b.filter){c=0;for(d=g.length;c<d;c++)(f=g[c].nTr)&&e.push(f)}else if("original"==b.order&&"applied"==b.filter){c=0;for(d=g.length;c<d;c++)f=g[c].nTr,-1!==h.inArray(c,o)&&f&&e.push(f)}else D(d,1,"Unknown selection options");e=h(e);c=e.filter(a);e=e.find(a);return h([].concat(h.makeArray(c),h.makeArray(e)))};this._=function(a,b){var c=[],d,e,f=this.$(a,b);d=0;for(e=f.length;d<e;d++)c.push(this.fnGetData(f[d]));return c};this.fnAddData=function(a,b){if(0===a.length)return[];var c=[],
d,e=s(this[j.ext.iApiIndex]);if("object"===typeof a[0]&&null!==a[0])for(var f=0;f<a.length;f++){d=H(e,a[f]);if(-1==d)return c;c.push(d)}else{d=H(e,a);if(-1==d)return c;c.push(d)}e.aiDisplay=e.aiDisplayMaster.slice();(b===n||b)&&aa(e);return c};this.fnAdjustColumnSizing=function(a){var b=s(this[j.ext.iApiIndex]);k(b);a===n||a?this.fnDraw(!1):(""!==b.oScroll.sX||""!==b.oScroll.sY)&&this.oApi._fnScrollDraw(b)};this.fnClearTable=function(a){var b=s(this[j.ext.iApiIndex]);ga(b);(a===n||a)&&x(b)};this.fnClose=
function(a){for(var b=s(this[j.ext.iApiIndex]),c=0;c<b.aoOpenRows.length;c++)if(b.aoOpenRows[c].nParent==a)return(a=b.aoOpenRows[c].nTr.parentNode)&&a.removeChild(b.aoOpenRows[c].nTr),b.aoOpenRows.splice(c,1),0;return 1};this.fnDeleteRow=function(a,b,c){var d=s(this[j.ext.iApiIndex]),e,f,a="object"===typeof a?I(d,a):a,g=d.aoData.splice(a,1);e=0;for(f=d.aoData.length;e<f;e++)null!==d.aoData[e].nTr&&(d.aoData[e].nTr._DT_RowIndex=e);e=h.inArray(a,d.aiDisplay);d.asDataSearch.splice(e,1);ha(d.aiDisplayMaster,
a);ha(d.aiDisplay,a);"function"===typeof b&&b.call(this,d,g);d._iDisplayStart>=d.fnRecordsDisplay()&&(d._iDisplayStart-=d._iDisplayLength,0>d._iDisplayStart&&(d._iDisplayStart=0));if(c===n||c)y(d),x(d);return g};this.fnDestroy=function(a){var b=s(this[j.ext.iApiIndex]),c=b.nTableWrapper.parentNode,d=b.nTBody,i,f,a=a===n?!1:a;b.bDestroying=!0;A(b,"aoDestroyCallback","destroy",[b]);if(!a){i=0;for(f=b.aoColumns.length;i<f;i++)!1===b.aoColumns[i].bVisible&&this.fnSetColumnVis(i,!0)}h(b.nTableWrapper).find("*").andSelf().unbind(".DT");
h("tbody>tr>td."+b.oClasses.sRowEmpty,b.nTable).parent().remove();b.nTable!=b.nTHead.parentNode&&(h(b.nTable).children("thead").remove(),b.nTable.appendChild(b.nTHead));b.nTFoot&&b.nTable!=b.nTFoot.parentNode&&(h(b.nTable).children("tfoot").remove(),b.nTable.appendChild(b.nTFoot));b.nTable.parentNode.removeChild(b.nTable);h(b.nTableWrapper).remove();b.aaSorting=[];b.aaSortingFixed=[];P(b);h(T(b)).removeClass(b.asStripeClasses.join(" "));h("th, td",b.nTHead).removeClass([b.oClasses.sSortable,b.oClasses.sSortableAsc,
b.oClasses.sSortableDesc,b.oClasses.sSortableNone].join(" "));b.bJUI&&(h("th span."+b.oClasses.sSortIcon+", td span."+b.oClasses.sSortIcon,b.nTHead).remove(),h("th, td",b.nTHead).each(function(){var a=h("div."+b.oClasses.sSortJUIWrapper,this),c=a.contents();h(this).append(c);a.remove()}));!a&&b.nTableReinsertBefore?c.insertBefore(b.nTable,b.nTableReinsertBefore):a||c.appendChild(b.nTable);i=0;for(f=b.aoData.length;i<f;i++)null!==b.aoData[i].nTr&&d.appendChild(b.aoData[i].nTr);!0===b.oFeatures.bAutoWidth&&
(b.nTable.style.width=q(b.sDestroyWidth));if(f=b.asDestroyStripes.length){a=h(d).children("tr");for(i=0;i<f;i++)a.filter(":nth-child("+f+"n + "+i+")").addClass(b.asDestroyStripes[i])}i=0;for(f=j.settings.length;i<f;i++)j.settings[i]==b&&j.settings.splice(i,1);e=b=null};this.fnDraw=function(a){var b=s(this[j.ext.iApiIndex]);!1===a?(y(b),x(b)):aa(b)};this.fnFilter=function(a,b,c,d,e,f){var g=s(this[j.ext.iApiIndex]);if(g.oFeatures.bFilter){if(c===n||null===c)c=!1;if(d===n||null===d)d=!0;if(e===n||null===
e)e=!0;if(f===n||null===f)f=!0;if(b===n||null===b){if(K(g,{sSearch:a+"",bRegex:c,bSmart:d,bCaseInsensitive:f},1),e&&g.aanFeatures.f){b=g.aanFeatures.f;c=0;for(d=b.length;c<d;c++)try{b[c]._DT_Input!=l.activeElement&&h(b[c]._DT_Input).val(a)}catch(o){h(b[c]._DT_Input).val(a)}}}else h.extend(g.aoPreSearchCols[b],{sSearch:a+"",bRegex:c,bSmart:d,bCaseInsensitive:f}),K(g,g.oPreviousSearch,1)}};this.fnGetData=function(a,b){var c=s(this[j.ext.iApiIndex]);if(a!==n){var d=a;if("object"===typeof a){var e=a.nodeName.toLowerCase();
"tr"===e?d=I(c,a):"td"===e&&(d=I(c,a.parentNode),b=fa(c,d,a))}return b!==n?v(c,d,b,""):c.aoData[d]!==n?c.aoData[d]._aData:null}return Z(c)};this.fnGetNodes=function(a){var b=s(this[j.ext.iApiIndex]);return a!==n?b.aoData[a]!==n?b.aoData[a].nTr:null:T(b)};this.fnGetPosition=function(a){var b=s(this[j.ext.iApiIndex]),c=a.nodeName.toUpperCase();return"TR"==c?I(b,a):"TD"==c||"TH"==c?(c=I(b,a.parentNode),a=fa(b,c,a),[c,R(b,a),a]):null};this.fnIsOpen=function(a){for(var b=s(this[j.ext.iApiIndex]),c=0;c<
b.aoOpenRows.length;c++)if(b.aoOpenRows[c].nParent==a)return!0;return!1};this.fnOpen=function(a,b,c){var d=s(this[j.ext.iApiIndex]),e=T(d);if(-1!==h.inArray(a,e)){this.fnClose(a);var e=l.createElement("tr"),f=l.createElement("td");e.appendChild(f);f.className=c;f.colSpan=t(d);"string"===typeof b?f.innerHTML=b:h(f).html(b);b=h("tr",d.nTBody);-1!=h.inArray(a,b)&&h(e).insertAfter(a);d.aoOpenRows.push({nTr:e,nParent:a});return e}};this.fnPageChange=function(a,b){var c=s(this[j.ext.iApiIndex]);qa(c,a);
y(c);(b===n||b)&&x(c)};this.fnSetColumnVis=function(a,b,c){var d=s(this[j.ext.iApiIndex]),e,f,g=d.aoColumns,h=d.aoData,o,m;if(g[a].bVisible!=b){if(b){for(e=f=0;e<a;e++)g[e].bVisible&&f++;m=f>=t(d);if(!m)for(e=a;e<g.length;e++)if(g[e].bVisible){o=e;break}e=0;for(f=h.length;e<f;e++)null!==h[e].nTr&&(m?h[e].nTr.appendChild(h[e]._anHidden[a]):h[e].nTr.insertBefore(h[e]._anHidden[a],J(d,e)[o]))}else{e=0;for(f=h.length;e<f;e++)null!==h[e].nTr&&(o=J(d,e)[a],h[e]._anHidden[a]=o,o.parentNode.removeChild(o))}g[a].bVisible=
b;W(d,d.aoHeader);d.nTFoot&&W(d,d.aoFooter);e=0;for(f=d.aoOpenRows.length;e<f;e++)d.aoOpenRows[e].nTr.colSpan=t(d);if(c===n||c)k(d),x(d);ra(d)}};this.fnSettings=function(){return s(this[j.ext.iApiIndex])};this.fnSort=function(a){var b=s(this[j.ext.iApiIndex]);b.aaSorting=a;O(b)};this.fnSortListener=function(a,b,c){ia(s(this[j.ext.iApiIndex]),a,b,c)};this.fnUpdate=function(a,b,c,d,e){var f=s(this[j.ext.iApiIndex]),b="object"===typeof b?I(f,b):b;if(h.isArray(a)&&c===n){f.aoData[b]._aData=a.slice();
for(c=0;c<f.aoColumns.length;c++)this.fnUpdate(v(f,b,c),b,c,!1,!1)}else if(h.isPlainObject(a)&&c===n){f.aoData[b]._aData=h.extend(!0,{},a);for(c=0;c<f.aoColumns.length;c++)this.fnUpdate(v(f,b,c),b,c,!1,!1)}else{F(f,b,c,a);var a=v(f,b,c,"display"),g=f.aoColumns[c];null!==g.fnRender&&(a=S(f,b,c),g.bUseRendered&&F(f,b,c,a));null!==f.aoData[b].nTr&&(J(f,b)[c].innerHTML=a)}c=h.inArray(b,f.aiDisplay);f.asDataSearch[c]=na(f,Y(f,b,"filter",r(f,"bSearchable")));(e===n||e)&&k(f);(d===n||d)&&aa(f);return 0};
this.fnVersionCheck=j.ext.fnVersionCheck;this.oApi={_fnExternApiFunc:Va,_fnInitialise:ba,_fnInitComplete:$,_fnLanguageCompat:pa,_fnAddColumn:o,_fnColumnOptions:m,_fnAddData:H,_fnCreateTr:ea,_fnGatherData:ua,_fnBuildHead:va,_fnDrawHead:W,_fnDraw:x,_fnReDraw:aa,_fnAjaxUpdate:wa,_fnAjaxParameters:Ea,_fnAjaxUpdateDraw:Fa,_fnServerParams:ka,_fnAddOptionsHtml:xa,_fnFeatureHtmlTable:Ba,_fnScrollDraw:La,_fnAdjustColumnSizing:k,_fnFeatureHtmlFilter:za,_fnFilterComplete:K,_fnFilterCustom:Ia,_fnFilterColumn:Ha,
_fnFilter:Ga,_fnBuildSearchArray:la,_fnBuildSearchRow:na,_fnFilterCreateSearch:ma,_fnDataToSearch:Ja,_fnSort:O,_fnSortAttachListener:ia,_fnSortingClasses:P,_fnFeatureHtmlPaginate:Da,_fnPageChange:qa,_fnFeatureHtmlInfo:Ca,_fnUpdateInfo:Ka,_fnFeatureHtmlLength:ya,_fnFeatureHtmlProcessing:Aa,_fnProcessingDisplay:E,_fnVisibleToColumnIndex:G,_fnColumnIndexToVisible:R,_fnNodeToDataIndex:I,_fnVisbleColumns:t,_fnCalculateEnd:y,_fnConvertToWidth:Ma,_fnCalculateColumnWidths:da,_fnScrollingWidthAdjust:Oa,_fnGetWidestNode:Na,
_fnGetMaxLenString:Pa,_fnStringToCss:q,_fnDetectType:B,_fnSettingsFromNode:s,_fnGetDataMaster:Z,_fnGetTrNodes:T,_fnGetTdNodes:J,_fnEscapeRegex:oa,_fnDeleteIndex:ha,_fnReOrderIndex:u,_fnColumnOrdering:M,_fnLog:D,_fnClearTable:ga,_fnSaveState:ra,_fnLoadState:Sa,_fnCreateCookie:function(a,b,c,d,e){var f=new Date;f.setTime(f.getTime()+1E3*c);var c=X.location.pathname.split("/"),a=a+"_"+c.pop().replace(/[\/:]/g,"").toLowerCase(),g;null!==e?(g="function"===typeof h.parseJSON?h.parseJSON(b):eval("("+b+")"),
b=e(a,g,f.toGMTString(),c.join("/")+"/")):b=a+"="+encodeURIComponent(b)+"; expires="+f.toGMTString()+"; path="+c.join("/")+"/";a=l.cookie.split(";");e=b.split(";")[0].length;f=[];if(4096<e+l.cookie.length+10){for(var j=0,o=a.length;j<o;j++)if(-1!=a[j].indexOf(d)){var k=a[j].split("=");try{(g=eval("("+decodeURIComponent(k[1])+")"))&&g.iCreate&&f.push({name:k[0],time:g.iCreate})}catch(m){}}for(f.sort(function(a,b){return b.time-a.time});4096<e+l.cookie.length+10;){if(0===f.length)return;d=f.pop();l.cookie=
d.name+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path="+c.join("/")+"/"}}l.cookie=b},_fnReadCookie:function(a){for(var b=X.location.pathname.split("/"),a=a+"_"+b[b.length-1].replace(/[\/:]/g,"").toLowerCase()+"=",b=l.cookie.split(";"),c=0;c<b.length;c++){for(var d=b[c];" "==d.charAt(0);)d=d.substring(1,d.length);if(0===d.indexOf(a))return decodeURIComponent(d.substring(a.length,d.length))}return null},_fnDetectHeader:V,_fnGetUniqueThs:N,_fnScrollBarWidth:Qa,_fnApplyToChildren:C,_fnMap:p,_fnGetRowData:Y,
_fnGetCellData:v,_fnSetCellData:F,_fnGetObjectDataFn:Q,_fnSetObjectDataFn:L,_fnApplyColumnDefs:ta,_fnBindAction:Ra,_fnExtend:Ta,_fnCallbackReg:z,_fnCallbackFire:A,_fnJsonString:Wa,_fnRender:S,_fnNodeToColumnIndex:fa,_fnInfoMacros:ja,_fnBrowserDetect:Ua,_fnGetColumns:r};h.extend(j.ext.oApi,this.oApi);for(var sa in j.ext.oApi)sa&&(this[sa]=Va(sa));var ca=this;this.each(function(){var a=0,b,c,d;c=this.getAttribute("id");var i=!1,f=!1;if("table"!=this.nodeName.toLowerCase())D(null,0,"Attempted to initialise DataTables on a node which is not a table: "+
this.nodeName);else{a=0;for(b=j.settings.length;a<b;a++){if(j.settings[a].nTable==this){if(e===n||e.bRetrieve)return j.settings[a].oInstance;if(e.bDestroy){j.settings[a].oInstance.fnDestroy();break}else{D(j.settings[a],0,"Cannot reinitialise DataTable.\n\nTo retrieve the DataTables object for this table, pass no arguments or see the docs for bRetrieve and bDestroy");return}}if(j.settings[a].sTableId==this.id){j.settings.splice(a,1);break}}if(null===c||""===c)this.id=c="DataTables_Table_"+j.ext._oExternConfig.iNextUnique++;
var g=h.extend(!0,{},j.models.oSettings,{nTable:this,oApi:ca.oApi,oInit:e,sDestroyWidth:h(this).width(),sInstance:c,sTableId:c});j.settings.push(g);g.oInstance=1===ca.length?ca:h(this).dataTable();e||(e={});e.oLanguage&&pa(e.oLanguage);e=Ta(h.extend(!0,{},j.defaults),e);p(g.oFeatures,e,"bPaginate");p(g.oFeatures,e,"bLengthChange");p(g.oFeatures,e,"bFilter");p(g.oFeatures,e,"bSort");p(g.oFeatures,e,"bInfo");p(g.oFeatures,e,"bProcessing");p(g.oFeatures,e,"bAutoWidth");p(g.oFeatures,e,"bSortClasses");
p(g.oFeatures,e,"bServerSide");p(g.oFeatures,e,"bDeferRender");p(g.oScroll,e,"sScrollX","sX");p(g.oScroll,e,"sScrollXInner","sXInner");p(g.oScroll,e,"sScrollY","sY");p(g.oScroll,e,"bScrollCollapse","bCollapse");p(g.oScroll,e,"bScrollInfinite","bInfinite");p(g.oScroll,e,"iScrollLoadGap","iLoadGap");p(g.oScroll,e,"bScrollAutoCss","bAutoCss");p(g,e,"asStripeClasses");p(g,e,"asStripClasses","asStripeClasses");p(g,e,"fnServerData");p(g,e,"fnFormatNumber");p(g,e,"sServerMethod");p(g,e,"aaSorting");p(g,
e,"aaSortingFixed");p(g,e,"aLengthMenu");p(g,e,"sPaginationType");p(g,e,"sAjaxSource");p(g,e,"sAjaxDataProp");p(g,e,"iCookieDuration");p(g,e,"sCookiePrefix");p(g,e,"sDom");p(g,e,"bSortCellsTop");p(g,e,"iTabIndex");p(g,e,"oSearch","oPreviousSearch");p(g,e,"aoSearchCols","aoPreSearchCols");p(g,e,"iDisplayLength","_iDisplayLength");p(g,e,"bJQueryUI","bJUI");p(g,e,"fnCookieCallback");p(g,e,"fnStateLoad");p(g,e,"fnStateSave");p(g.oLanguage,e,"fnInfoCallback");z(g,"aoDrawCallback",e.fnDrawCallback,"user");
z(g,"aoServerParams",e.fnServerParams,"user");z(g,"aoStateSaveParams",e.fnStateSaveParams,"user");z(g,"aoStateLoadParams",e.fnStateLoadParams,"user");z(g,"aoStateLoaded",e.fnStateLoaded,"user");z(g,"aoRowCallback",e.fnRowCallback,"user");z(g,"aoRowCreatedCallback",e.fnCreatedRow,"user");z(g,"aoHeaderCallback",e.fnHeaderCallback,"user");z(g,"aoFooterCallback",e.fnFooterCallback,"user");z(g,"aoInitComplete",e.fnInitComplete,"user");z(g,"aoPreDrawCallback",e.fnPreDrawCallback,"user");g.oFeatures.bServerSide&&
g.oFeatures.bSort&&g.oFeatures.bSortClasses?z(g,"aoDrawCallback",P,"server_side_sort_classes"):g.oFeatures.bDeferRender&&z(g,"aoDrawCallback",P,"defer_sort_classes");e.bJQueryUI?(h.extend(g.oClasses,j.ext.oJUIClasses),e.sDom===j.defaults.sDom&&"lfrtip"===j.defaults.sDom&&(g.sDom='<"H"lfr>t<"F"ip>')):h.extend(g.oClasses,j.ext.oStdClasses);h(this).addClass(g.oClasses.sTable);if(""!==g.oScroll.sX||""!==g.oScroll.sY)g.oScroll.iBarWidth=Qa();g.iInitDisplayStart===n&&(g.iInitDisplayStart=e.iDisplayStart,
g._iDisplayStart=e.iDisplayStart);e.bStateSave&&(g.oFeatures.bStateSave=!0,Sa(g,e),z(g,"aoDrawCallback",ra,"state_save"));null!==e.iDeferLoading&&(g.bDeferLoading=!0,a=h.isArray(e.iDeferLoading),g._iRecordsDisplay=a?e.iDeferLoading[0]:e.iDeferLoading,g._iRecordsTotal=a?e.iDeferLoading[1]:e.iDeferLoading);null!==e.aaData&&(f=!0);""!==e.oLanguage.sUrl?(g.oLanguage.sUrl=e.oLanguage.sUrl,h.getJSON(g.oLanguage.sUrl,null,function(a){pa(a);h.extend(true,g.oLanguage,e.oLanguage,a);ba(g)}),i=!0):h.extend(!0,
g.oLanguage,e.oLanguage);null===e.asStripeClasses&&(g.asStripeClasses=[g.oClasses.sStripeOdd,g.oClasses.sStripeEven]);b=g.asStripeClasses.length;g.asDestroyStripes=[];if(b){c=!1;d=h(this).children("tbody").children("tr:lt("+b+")");for(a=0;a<b;a++)d.hasClass(g.asStripeClasses[a])&&(c=!0,g.asDestroyStripes.push(g.asStripeClasses[a]));c&&d.removeClass(g.asStripeClasses.join(" "))}c=[];a=this.getElementsByTagName("thead");0!==a.length&&(V(g.aoHeader,a[0]),c=N(g));if(null===e.aoColumns){d=[];a=0;for(b=
c.length;a<b;a++)d.push(null)}else d=e.aoColumns;a=0;for(b=d.length;a<b;a++)e.saved_aoColumns!==n&&e.saved_aoColumns.length==b&&(null===d[a]&&(d[a]={}),d[a].bVisible=e.saved_aoColumns[a].bVisible),o(g,c?c[a]:null);ta(g,e.aoColumnDefs,d,function(a,b){m(g,a,b)});a=0;for(b=g.aaSorting.length;a<b;a++){g.aaSorting[a][0]>=g.aoColumns.length&&(g.aaSorting[a][0]=0);var k=g.aoColumns[g.aaSorting[a][0]];g.aaSorting[a][2]===n&&(g.aaSorting[a][2]=0);e.aaSorting===n&&g.saved_aaSorting===n&&(g.aaSorting[a][1]=
k.asSorting[0]);c=0;for(d=k.asSorting.length;c<d;c++)if(g.aaSorting[a][1]==k.asSorting[c]){g.aaSorting[a][2]=c;break}}P(g);Ua(g);a=h(this).children("caption").each(function(){this._captionSide=h(this).css("caption-side")});b=h(this).children("thead");0===b.length&&(b=[l.createElement("thead")],this.appendChild(b[0]));g.nTHead=b[0];b=h(this).children("tbody");0===b.length&&(b=[l.createElement("tbody")],this.appendChild(b[0]));g.nTBody=b[0];g.nTBody.setAttribute("role","alert");g.nTBody.setAttribute("aria-live",
"polite");g.nTBody.setAttribute("aria-relevant","all");b=h(this).children("tfoot");if(0===b.length&&0<a.length&&(""!==g.oScroll.sX||""!==g.oScroll.sY))b=[l.createElement("tfoot")],this.appendChild(b[0]);0<b.length&&(g.nTFoot=b[0],V(g.aoFooter,g.nTFoot));if(f)for(a=0;a<e.aaData.length;a++)H(g,e.aaData[a]);else ua(g);g.aiDisplay=g.aiDisplayMaster.slice();g.bInitialised=!0;!1===i&&ba(g)}});ca=null;return this};j.fnVersionCheck=function(e){for(var h=function(e,h){for(;e.length<h;)e+="0";return e},m=j.ext.sVersion.split("."),
e=e.split("."),k="",n="",l=0,t=e.length;l<t;l++)k+=h(m[l],3),n+=h(e[l],3);return parseInt(k,10)>=parseInt(n,10)};j.fnIsDataTable=function(e){for(var h=j.settings,m=0;m<h.length;m++)if(h[m].nTable===e||h[m].nScrollHead===e||h[m].nScrollFoot===e)return!0;return!1};j.fnTables=function(e){var o=[];jQuery.each(j.settings,function(j,k){(!e||!0===e&&h(k.nTable).is(":visible"))&&o.push(k.nTable)});return o};j.version="1.9.4";j.settings=[];j.models={};j.models.ext={afnFiltering:[],afnSortData:[],aoFeatures:[],
aTypes:[],fnVersionCheck:j.fnVersionCheck,iApiIndex:0,ofnSearch:{},oApi:{},oStdClasses:{},oJUIClasses:{},oPagination:{},oSort:{},sVersion:j.version,sErrMode:"alert",_oExternConfig:{iNextUnique:0}};j.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};j.models.oRow={nTr:null,_aData:[],_aSortData:[],_anHidden:[],_sRowStripe:""};j.models.oColumn={aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bUseRendered:null,bVisible:null,_bAutoType:!0,fnCreatedCell:null,fnGetData:null,
fnRender:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};j.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,
bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollAutoCss:!0,bScrollCollapse:!1,bScrollInfinite:!1,bServerSide:!1,bSort:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCookieCallback:null,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(e){if(1E3>e)return e;for(var h=e+"",e=h.split(""),j="",h=h.length,k=0;k<h;k++)0===k%3&&0!==k&&(j=this.oLanguage.sInfoThousands+j),j=e[h-k-1]+j;return j},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,
fnRowCallback:null,fnServerData:function(e,j,m,k){k.jqXHR=h.ajax({url:e,data:j,success:function(e){e.sError&&k.oApi._fnLog(k,0,e.sError);h(k.oInstance).trigger("xhr",[k,e]);m(e)},dataType:"json",cache:!1,type:k.sServerMethod,error:function(e,h){"parsererror"==h&&k.oApi._fnLog(k,0,"DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.")}})},fnServerParams:null,fnStateLoad:function(e){var e=this.oApi._fnReadCookie(e.sCookiePrefix+e.sInstance),j;try{j=
"function"===typeof h.parseJSON?h.parseJSON(e):eval("("+e+")")}catch(m){j=null}return j},fnStateLoadParams:null,fnStateLoaded:null,fnStateSave:function(e,h){this.oApi._fnCreateCookie(e.sCookiePrefix+e.sInstance,this.oApi._fnJsonString(h),e.iCookieDuration,e.sCookiePrefix,e.fnCookieCallback)},fnStateSaveParams:null,iCookieDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iScrollLoadGap:100,iTabIndex:0,oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},
oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sInfoThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},j.models.oSearch),sAjaxDataProp:"aaData",
sAjaxSource:null,sCookiePrefix:"SpryMedia_DataTables_",sDom:"lfrtip",sPaginationType:"two_button",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET"};j.defaults.columns={aDataSort:null,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bUseRendered:!0,bVisible:!0,fnCreatedCell:null,fnRender:null,iDataSort:-1,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};j.models.oSettings={oFeatures:{bAutoWidth:null,
bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortClasses:null,bStateSave:null},oScroll:{bAutoCss:null,bCollapse:null,bInfinite:null,iBarWidth:0,iLoadGap:null,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1},aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aoColumns:[],aoHeader:[],aoFooter:[],asDataSearch:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:null,
asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,sPaginationType:"two_button",iCookieDuration:0,sCookiePrefix:"",fnCookieCallback:null,aoStateSave:[],aoStateLoad:[],
oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iDisplayEnd:10,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsTotal,10):this.aiDisplayMaster.length},
fnRecordsDisplay:function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsDisplay,10):this.aiDisplay.length},fnDisplayEnd:function(){return this.oFeatures.bServerSide?!1===this.oFeatures.bPaginate||-1==this._iDisplayLength?this._iDisplayStart+this.aiDisplay.length:Math.min(this._iDisplayStart+this._iDisplayLength,this._iRecordsDisplay):this._iDisplayEnd},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null};j.ext=h.extend(!0,{},j.models.ext);h.extend(j.ext.oStdClasses,
{sTable:"dataTable",sPagePrevEnabled:"paginate_enabled_previous",sPagePrevDisabled:"paginate_disabled_previous",sPageNextEnabled:"paginate_enabled_next",sPageNextDisabled:"paginate_disabled_next",sPageJUINext:"",sPageJUIPrev:"",sPageButton:"paginate_button",sPageButtonActive:"paginate_active",sPageButtonStaticDisabled:"paginate_button paginate_button_disabled",sPageFirst:"first",sPagePrevious:"previous",sPageNext:"next",sPageLast:"last",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",
sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",
sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sFooterTH:"",sJUIHeader:"",sJUIFooter:""});h.extend(j.ext.oJUIClasses,j.ext.oStdClasses,{sPagePrevEnabled:"fg-button ui-button ui-state-default ui-corner-left",sPagePrevDisabled:"fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",sPageNextEnabled:"fg-button ui-button ui-state-default ui-corner-right",
sPageNextDisabled:"fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",sPageJUINext:"ui-icon ui-icon-circle-arrow-e",sPageJUIPrev:"ui-icon ui-icon-circle-arrow-w",sPageButton:"fg-button ui-button ui-state-default",sPageButtonActive:"fg-button ui-button ui-state-default ui-state-disabled",sPageButtonStaticDisabled:"fg-button ui-button ui-state-default ui-state-disabled",sPageFirst:"first ui-corner-tl ui-corner-bl",sPageLast:"last ui-corner-tr ui-corner-br",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",
sSortAsc:"ui-state-default",sSortDesc:"ui-state-default",sSortable:"ui-state-default",sSortableAsc:"ui-state-default",sSortableDesc:"ui-state-default",sSortableNone:"ui-state-default",sSortJUIAsc:"css_right ui-icon ui-icon-triangle-1-n",sSortJUIDesc:"css_right ui-icon ui-icon-triangle-1-s",sSortJUI:"css_right ui-icon ui-icon-carat-2-n-s",sSortJUIAscAllowed:"css_right ui-icon ui-icon-carat-1-n",sSortJUIDescAllowed:"css_right ui-icon ui-icon-carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",
sScrollHead:"dataTables_scrollHead ui-state-default",sScrollFoot:"dataTables_scrollFoot ui-state-default",sFooterTH:"ui-state-default",sJUIHeader:"fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix",sJUIFooter:"fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix"});h.extend(j.ext.oPagination,{two_button:{fnInit:function(e,j,m){var k=e.oLanguage.oPaginate,n=function(h){e.oApi._fnPageChange(e,h.data.action)&&m(e)},k=!e.bJUI?'<a class="'+
e.oClasses.sPagePrevDisabled+'" tabindex="'+e.iTabIndex+'" role="button">'+k.sPrevious+'</a><a class="'+e.oClasses.sPageNextDisabled+'" tabindex="'+e.iTabIndex+'" role="button">'+k.sNext+"</a>":'<a class="'+e.oClasses.sPagePrevDisabled+'" tabindex="'+e.iTabIndex+'" role="button"><span class="'+e.oClasses.sPageJUIPrev+'"></span></a><a class="'+e.oClasses.sPageNextDisabled+'" tabindex="'+e.iTabIndex+'" role="button"><span class="'+e.oClasses.sPageJUINext+'"></span></a>';h(j).append(k);var l=h("a",j),
k=l[0],l=l[1];e.oApi._fnBindAction(k,{action:"previous"},n);e.oApi._fnBindAction(l,{action:"next"},n);e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_previous",l.id=e.sTableId+"_next",k.setAttribute("aria-controls",e.sTableId),l.setAttribute("aria-controls",e.sTableId))},fnUpdate:function(e){if(e.aanFeatures.p)for(var h=e.oClasses,j=e.aanFeatures.p,k,l=0,n=j.length;l<n;l++)if(k=j[l].firstChild)k.className=0===e._iDisplayStart?h.sPagePrevDisabled:h.sPagePrevEnabled,k=k.nextSibling,
k.className=e.fnDisplayEnd()==e.fnRecordsDisplay()?h.sPageNextDisabled:h.sPageNextEnabled}},iFullNumbersShowPages:5,full_numbers:{fnInit:function(e,j,m){var k=e.oLanguage.oPaginate,l=e.oClasses,n=function(h){e.oApi._fnPageChange(e,h.data.action)&&m(e)};h(j).append('<a tabindex="'+e.iTabIndex+'" class="'+l.sPageButton+" "+l.sPageFirst+'">'+k.sFirst+'</a><a tabindex="'+e.iTabIndex+'" class="'+l.sPageButton+" "+l.sPagePrevious+'">'+k.sPrevious+'</a><span></span><a tabindex="'+e.iTabIndex+'" class="'+
l.sPageButton+" "+l.sPageNext+'">'+k.sNext+'</a><a tabindex="'+e.iTabIndex+'" class="'+l.sPageButton+" "+l.sPageLast+'">'+k.sLast+"</a>");var t=h("a",j),k=t[0],l=t[1],r=t[2],t=t[3];e.oApi._fnBindAction(k,{action:"first"},n);e.oApi._fnBindAction(l,{action:"previous"},n);e.oApi._fnBindAction(r,{action:"next"},n);e.oApi._fnBindAction(t,{action:"last"},n);e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_first",l.id=e.sTableId+"_previous",r.id=e.sTableId+"_next",t.id=e.sTableId+"_last")},
fnUpdate:function(e,o){if(e.aanFeatures.p){var m=j.ext.oPagination.iFullNumbersShowPages,k=Math.floor(m/2),l=Math.ceil(e.fnRecordsDisplay()/e._iDisplayLength),n=Math.ceil(e._iDisplayStart/e._iDisplayLength)+1,t="",r,B=e.oClasses,u,M=e.aanFeatures.p,L=function(h){e.oApi._fnBindAction(this,{page:h+r-1},function(h){e.oApi._fnPageChange(e,h.data.page);o(e);h.preventDefault()})};-1===e._iDisplayLength?n=k=r=1:l<m?(r=1,k=l):n<=k?(r=1,k=m):n>=l-k?(r=l-m+1,k=l):(r=n-Math.ceil(m/2)+1,k=r+m-1);for(m=r;m<=k;m++)t+=
n!==m?'<a tabindex="'+e.iTabIndex+'" class="'+B.sPageButton+'">'+e.fnFormatNumber(m)+"</a>":'<a tabindex="'+e.iTabIndex+'" class="'+B.sPageButtonActive+'">'+e.fnFormatNumber(m)+"</a>";m=0;for(k=M.length;m<k;m++)u=M[m],u.hasChildNodes()&&(h("span:eq(0)",u).html(t).children("a").each(L),u=u.getElementsByTagName("a"),u=[u[0],u[1],u[u.length-2],u[u.length-1]],h(u).removeClass(B.sPageButton+" "+B.sPageButtonActive+" "+B.sPageButtonStaticDisabled),h([u[0],u[1]]).addClass(1==n?B.sPageButtonStaticDisabled:
B.sPageButton),h([u[2],u[3]]).addClass(0===l||n===l||-1===e._iDisplayLength?B.sPageButtonStaticDisabled:B.sPageButton))}}}});h.extend(j.ext.oSort,{"string-pre":function(e){"string"!=typeof e&&(e=null!==e&&e.toString?e.toString():"");return e.toLowerCase()},"string-asc":function(e,h){return e<h?-1:e>h?1:0},"string-desc":function(e,h){return e<h?1:e>h?-1:0},"html-pre":function(e){return e.replace(/<.*?>/g,"").toLowerCase()},"html-asc":function(e,h){return e<h?-1:e>h?1:0},"html-desc":function(e,h){return e<
h?1:e>h?-1:0},"date-pre":function(e){e=Date.parse(e);if(isNaN(e)||""===e)e=Date.parse("01/01/1970 00:00:00");return e},"date-asc":function(e,h){return e-h},"date-desc":function(e,h){return h-e},"numeric-pre":function(e){return"-"==e||""===e?0:1*e},"numeric-asc":function(e,h){return e-h},"numeric-desc":function(e,h){return h-e}});h.extend(j.ext.aTypes,[function(e){if("number"===typeof e)return"numeric";if("string"!==typeof e)return null;var h,j=!1;h=e.charAt(0);if(-1=="0123456789-".indexOf(h))return null;
for(var k=1;k<e.length;k++){h=e.charAt(k);if(-1=="0123456789.".indexOf(h))return null;if("."==h){if(j)return null;j=!0}}return"numeric"},function(e){var h=Date.parse(e);return null!==h&&!isNaN(h)||"string"===typeof e&&0===e.length?"date":null},function(e){return"string"===typeof e&&-1!=e.indexOf("<")&&-1!=e.indexOf(">")?"html":null}]);h.fn.DataTable=j;h.fn.dataTable=j;h.fn.dataTableSettings=j.settings;h.fn.dataTableExt=j.ext};"function"===typeof define&&define.amd?define(["jquery"],L):jQuery&&!jQuery.fn.dataTable&&
L(jQuery)})(window,document);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,549 @@
/*!
* $ Templates Plugin 1.1
* https://github.com/KanbanSolutions/jquery-tmpl
* Requires $ 1.4.2
*
* Copyright Software Freedom Conservancy, Inc.
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
/*
Tags:
{%if <condition> %}<action>{%/if%}
{%if <condition> %}<action>{%else%}<action>{%/if%}
{%if <condition> %}<action>{%elif <condition> %}<action>{%else%}<action>{%/if%}
{%each <array_or_object> %}$value, $index{%/each%}
{%tmpl <template>%}
{%= js call %}
{%html js call %}
*/
(function($, undefined) {
var oldManip = $.fn.domManip, tmplItmAtt = "_tmplitem",
newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = [];
var regex = {
sq_escape: /([\\'])/g,
sq_unescape: /\\'/g,
dq_unescape: /\\\\/g,
nl_strip: /[\r\t\n]/g,
shortcut_replace: /\$\{([^\}]*)\}/g,
lang_parse: /\{\%(\/?)(\w+|.)(?:\(((?:[^\%]|\%(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\%]|\%(?!\}))*?)\))?\s*\%\}/g,
old_lang_parse: /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,
template_anotate: /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,
text_only_template: /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,
html_expr: /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! |\{\%! /,
last_word: /\w$/
};
function newTmplItem(options, parentItem, fn, data) {
// Returns a template item data structure for a new rendered instance of a template (a 'template item').
// The content field is a hierarchical array of strings and nested items (to be
// removed and replaced by nodes field of dom elements, once inserted in DOM).
var newItem = {
data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}),
_wrap: parentItem ? parentItem._wrap : null,
tmpl: null,
parent: parentItem || null,
nodes: [],
calls: tiCalls,
nest: tiNest,
wrap: tiWrap,
html: tiHtml,
update: tiUpdate
};
if(options) {
$.extend(newItem, options, { nodes: [], parent: parentItem });
}
if(fn) {
// Build the hierarchical content to be used during insertion into DOM
newItem.tmpl = fn;
newItem._ctnt = newItem._ctnt || $.isFunction(newItem.tmpl) && newItem.tmpl($, newItem) || fn;
newItem.key = ++itemKey;
// Keep track of new template item, until it is stored as $ Data on DOM element
(stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem;
}
return newItem;
}
// Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core).
$.each({
appendTo: "append",
prependTo: "prepend",
insertBefore: "before",
insertAfter: "after",
replaceAll: "replaceWith"
}, function(name, original) {
$.fn[ name ] = function(selector) {
var ret = [], insert = $(selector), elems, i, l, tmplItems,
parent = this.length === 1 && this[0].parentNode;
appendToTmplItems = newTmplItems || {};
if(parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1) {
insert[ original ](this[0]);
ret = this;
} else {
for(i = 0,l = insert.length; i < l; i++) {
cloneIndex = i;
elems = (i > 0 ? this.clone(true) : this).get();
$(insert[i])[ original ](elems);
ret = ret.concat(elems);
}
cloneIndex = 0;
ret = this.pushStack(ret, name, insert.selector);
}
tmplItems = appendToTmplItems;
appendToTmplItems = null;
$.tmpl.complete(tmplItems);
return ret;
};
});
$.fn.extend({
// Use first wrapped element as template markup.
// Return wrapped set of template items, obtained by rendering template against data.
tmpl: function(data, options, parentItem) {
var ret = $.tmpl(this[0], data, options, parentItem);
return ret;
},
// Find which rendered template item the first wrapped DOM element belongs to
tmplItem: function() {
var ret = $.tmplItem(this[0]);
return ret;
},
// Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
template: function(name) {
var ret = $.template(name, this[0]);
return ret;
},
domManip: function(args, table, callback, options) {
if(args[0] && $.isArray(args[0])) {
var dmArgs = $.makeArray(arguments), elems = args[0], elemsLength = elems.length, i = 0, tmplItem;
while(i < elemsLength && !(tmplItem = $.data(elems[i++], "tmplItem"))) {
}
if(tmplItem && cloneIndex) {
dmArgs[2] = function(fragClone) {
// Handler called by oldManip when rendered template has been inserted into DOM.
$.tmpl.afterManip(this, fragClone, callback);
};
}
oldManip.apply(this, dmArgs);
} else {
oldManip.apply(this, arguments);
}
cloneIndex = 0;
if(!appendToTmplItems) {
$.tmpl.complete(newTmplItems);
}
return this;
}
});
$.extend({
// Return wrapped set of template items, obtained by rendering template against data.
tmpl: function(tmpl, data, options, parentItem) {
var ret, topLevel = !parentItem;
if(topLevel) {
// This is a top-level tmpl call (not from a nested template using {{tmpl}})
parentItem = topTmplItem;
tmpl = $.template[tmpl] || $.template(null, tmpl);
wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level
} else if(!tmpl) {
// The template item is already associated with DOM - this is a refresh.
// Re-evaluate rendered template for the parentItem
tmpl = parentItem.tmpl;
newTmplItems[parentItem.key] = parentItem;
parentItem.nodes = [];
if(parentItem.wrapped) {
updateWrapped(parentItem, parentItem.wrapped);
}
// Rebuild, without creating a new template item
return $(build(parentItem, null, parentItem.tmpl($, parentItem)));
}
if(!tmpl) {
return []; // Could throw...
}
if(typeof data === "function") {
data = data.call(parentItem || {});
}
if(options && options.wrapped) {
updateWrapped(options, options.wrapped);
}
ret = $.isArray(data) ?
$.map(data, function(dataItem) {
return dataItem ? newTmplItem(options, parentItem, tmpl, dataItem) : null;
}) :
[ newTmplItem(options, parentItem, tmpl, data) ];
return topLevel ? $(build(parentItem, null, ret)) : ret;
},
// Return rendered template item for an element.
tmplItem: function(elem) {
var tmplItem;
if(elem instanceof $) {
elem = elem[0];
}
while(elem && elem.nodeType === 1 && !(tmplItem = $.data(elem,
"tmplItem")) && (elem = elem.parentNode)) {
}
return tmplItem || topTmplItem;
},
// Set:
// Use $.template( name, tmpl ) to cache a named template,
// where tmpl is a template string, a script element or a $ instance wrapping a script element, etc.
// Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.
// Get:
// Use $.template( name ) to access a cached template.
// Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
// will return the compiled template, without adding a name reference.
// If templateString includes at least one HTML tag, $.template( templateString ) is equivalent
// to $.template( null, templateString )
template: function(name, tmpl) {
if(tmpl) {
// Compile template and associate with name
if(typeof tmpl === "string") {
// This is an HTML string being passed directly in.
tmpl = buildTmplFn(tmpl)
} else if(tmpl instanceof $) {
tmpl = tmpl[0] || {};
}
if(tmpl.nodeType) {
// If this is a template block, use cached copy, or generate tmpl function and cache.
tmpl = $.data(tmpl, "tmpl") || $.data(tmpl, "tmpl", buildTmplFn(tmpl.innerHTML));
// Issue: In IE, if the container element is not a script block, the innerHTML will remove quotes from attribute values whenever the value does not include white space.
// This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x.
// To correct this, include space in tag: foo="${ x }" -> foo="value of x"
}
return typeof name === "string" ? ($.template[name] = tmpl) : tmpl;
}
// Return named compiled template
return name ? (typeof name !== "string" ? $.template(null, name) :
($.template[name] ||
// If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
$.template(null, name))) : null;
},
encode: function(text) {
// Do HTML encoding replacing < > & and ' and " by corresponding entities.
return ("" + text).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;");
}
});
$.extend($.tmpl, {
tag: {
"tmpl": {
_default: { $2: "null" },
open: "if($notnull_1){__=__.concat($item.nest($1,$2));}"
// tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions)
// This means that {{tmpl foo}} treats foo as a template (which IS a function).
// Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}.
},
"wrap": {
_default: { $2: "null" },
open: "$item.calls(__,$1,$2);__=[];",
close: "call=$item.calls();__=call._.concat($item.wrap(call,__));"
},
"each": {
_default: { $2: "$index, $value" },
open: "if($notnull_1){$.each($1a,function($2){with(this){",
close: "}});}"
},
"if": {
open: "if(($notnull_1) && $1a){",
close: "}"
},
"else": {
open: "}else{"
},
"elif": {
open: "}else if(($notnull_1) && $1a){"
},
"elseif": {
open: "}else if(($notnull_1) && $1a){"
},
"html": {
// Unecoded expression evaluation.
open: "if($notnull_1){__.push($1a);}"
},
"=": {
// Encoded expression evaluation. Abbreviated form is ${}.
_default: { $1: "$data" },
open: "if($notnull_1){__.push($.encode($1a));}"
},
"!": {
// Comment tag. Skipped by parser
open: ""
}
},
// This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events
complete: function(items) {
newTmplItems = {};
},
// Call this from code which overrides domManip, or equivalent
// Manage cloning/storing template items etc.
afterManip: function afterManip(elem, fragClone, callback) {
// Provides cloned fragment ready for fixup prior to and after insertion into DOM
var content = fragClone.nodeType === 11 ?
$.makeArray(fragClone.childNodes) :
fragClone.nodeType === 1 ? [fragClone] : [];
// Return fragment to original caller (e.g. append) for DOM insertion
callback.call(elem, fragClone);
// Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by $.data.
storeTmplItems(content);
cloneIndex++;
}
});
//========================== Private helper functions, used by code above ==========================
function build(tmplItem, nested, content) {
// Convert hierarchical content into flat string array
// and finally return array of fragments ready for DOM insertion
var frag, ret = content ? $.map(content, function(item) {
return (typeof item === "string") ?
// Insert template item annotations, to be converted to $.data( "tmplItem" ) when elems are inserted into DOM.
(tmplItem.key ? item.replace(regex.template_anotate,
"$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2") : item) :
// This is a child template item. Build nested template.
build(item, tmplItem, item._ctnt);
}) :
// If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}.
tmplItem;
if(nested) {
return ret;
}
// top-level template
ret = ret.join("");
// Support templates which have initial or final text nodes, or consist only of text
// Also support HTML entities within the HTML markup.
ret.replace(regex.text_only_template, function(all, before, middle, after) {
frag = $(middle).get();
storeTmplItems(frag);
if(before) {
frag = unencode(before).concat(frag);
}
if(after) {
frag = frag.concat(unencode(after));
}
});
return frag ? frag : unencode(ret);
}
function unencode(text) {
// Use createElement, since createTextNode will not render HTML entities correctly
var el = document.createElement("div");
el.innerHTML = text;
return $.makeArray(el.childNodes);
}
// Generate a reusable function that will serve to render a template against data
function buildTmplFn(markup) {
var parse_tag = function(all, slash, type, fnargs, target, parens, args) {
if(!type) {
return "');__.push('";
}
var tag = $.tmpl.tag[ type ], def, expr, exprAutoFnDetect;
if(!tag) {
console.group("Exception");
console.error(markup);
console.error('Unknown tag: ', type);
console.error(all);
console.groupEnd("Exception");
return "');__.push('";
}
def = tag._default || [];
if(parens && !regex.last_word.test(target)) {
target += parens;
parens = "";
}
if(target) {
target = unescape(target);
args = args ? ("," + unescape(args) + ")") : (parens ? ")" : "");
// Support for target being things like a.toLowerCase();
// In that case don't call with template item as 'this' pointer. Just evaluate...
expr = parens ? (target.indexOf(".") > -1 ? target + unescape(parens) : ("(" + target + ").call($item" + args)) : target;
exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))";
} else {
exprAutoFnDetect = expr = def.$1 || "null";
}
fnargs = unescape(fnargs);
return "');" +
tag[ slash ? "close" : "open" ]
.split("$notnull_1").join(target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true")
.split("$1a").join(exprAutoFnDetect)
.split("$1").join(expr)
.split("$2").join(fnargs || def.$2 || "") +
"__.push('";
};
var depreciated_parse = function() {
if($.tmpl.tag[arguments[2]]) {
console.group("Depreciated");
console.info(markup);
console.info('Markup has old style indicators, use {% %} instead of {{ }}');
console.info(arguments[0]);
console.groupEnd("Depreciated");
return parse_tag.apply(this, arguments);
} else {
return "');__.push('{{" + arguments[2] + "}}');__.push('";
}
};
// Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10).
// Introduce the data as local variables using with(){}
var parsed_markup_data = "var $=$,call,__=[],$data=$item.data; with($data){__.push('";
// Convert the template into pure JavaScript
var parsed_markup = $.trim(markup);
parsed_markup = parsed_markup.replace(regex.sq_escape, "\\$1");
parsed_markup = parsed_markup.replace(regex.nl_strip, " ");
parsed_markup = parsed_markup.replace(regex.shortcut_replace, "{%= $1%}");
parsed_markup = parsed_markup.replace(regex.lang_parse, parse_tag);
parsed_markup = parsed_markup.replace(regex.old_lang_parse, depreciated_parse);
parsed_markup_data += parsed_markup;
parsed_markup_data += "');}return __;";
return new Function("$", "$item", parsed_markup_data);
}
function updateWrapped(options, wrapped) {
// Build the wrapped content.
options._wrap = build(options, true,
// Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string.
$.isArray(wrapped) ? wrapped : [regex.html_expr.test(wrapped) ? wrapped : $(wrapped).html()]
).join("");
}
function unescape(args) {
return args ? args.replace(regex.sq_unescape, "'").replace(regex.dq_unescape, "\\") : null;
}
function outerHtml(elem) {
var div = document.createElement("div");
div.appendChild(elem.cloneNode(true));
return div.innerHTML;
}
// Store template items in $.data(), ensuring a unique tmplItem data data structure for each rendered template instance.
function storeTmplItems(content) {
var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m;
for(i = 0,l = content.length; i < l; i++) {
if((elem = content[i]).nodeType !== 1) {
continue;
}
elems = elem.getElementsByTagName("*");
for(m = elems.length - 1; m >= 0; m--) {
processItemKey(elems[m]);
}
processItemKey(elem);
}
function processItemKey(el) {
var pntKey, pntNode = el, pntItem, tmplItem, key;
// Ensure that each rendered template inserted into the DOM has its own template item,
if((key = el.getAttribute(tmplItmAtt))) {
while(pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute(tmplItmAtt))) {
}
if(pntKey !== key) {
// The next ancestor with a _tmplitem expando is on a different key than this one.
// So this is a top-level element within this template item
// Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment.
pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute(tmplItmAtt) || 0)) : 0;
if(!(tmplItem = newTmplItems[key])) {
// The item is for wrapped content, and was copied from the temporary parent wrappedItem.
tmplItem = wrappedItems[key];
tmplItem = newTmplItem(tmplItem, newTmplItems[pntNode] || wrappedItems[pntNode]);
tmplItem.key = ++itemKey;
newTmplItems[itemKey] = tmplItem;
}
if(cloneIndex) {
cloneTmplItem(key);
}
}
el.removeAttribute(tmplItmAtt);
} else if(cloneIndex && (tmplItem = $.data(el, "tmplItem"))) {
// This was a rendered element, cloned during append or appendTo etc.
// TmplItem stored in $ data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem.
cloneTmplItem(tmplItem.key);
newTmplItems[tmplItem.key] = tmplItem;
pntNode = $.data(el.parentNode, "tmplItem");
pntNode = pntNode ? pntNode.key : 0;
}
if(tmplItem) {
pntItem = tmplItem;
// Find the template item of the parent element.
// (Using !=, not !==, since pntItem.key is number, and pntNode may be a string)
while(pntItem && pntItem.key != pntNode) {
// Add this element as a top-level node for this rendered template item, as well as for any
// ancestor items between this item and the item of its parent element
pntItem.nodes.push(el);
pntItem = pntItem.parent;
}
// Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering...
delete tmplItem._ctnt;
delete tmplItem._wrap;
// Store template item as $ data on the element
$.data(el, "tmplItem", tmplItem);
}
function cloneTmplItem(key) {
key = key + keySuffix;
tmplItem = newClonedItems[key] =
(newClonedItems[key] || newTmplItem(tmplItem,
newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent));
}
}
}
//---- Helper functions for template item ----
function tiCalls(content, tmpl, data, options) {
if(!content) {
return stack.pop();
}
stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options });
}
function tiNest(tmpl, data, options) {
// nested template, using {{tmpl}} tag
return $.tmpl($.template(tmpl), data, options, this);
}
function tiWrap(call, wrapped) {
// nested template, using {{wrap}} tag
var options = call.options || {};
options.wrapped = wrapped;
// Apply the template, which may incorporate wrapped content,
return $.tmpl($.template(call.tmpl), call.data, options, call.item);
}
function tiHtml(filter, textOnly) {
var wrapped = this._wrap;
return $.map(
$($.isArray(wrapped) ? wrapped.join("") : wrapped).filter(filter || "*"),
function(e) {
return textOnly ?
e.innerText || e.textContent :
e.outerHTML || outerHtml(e);
});
}
function tiUpdate() {
var coll = this.nodes;
$.tmpl(null, null, null, this).insertBefore(coll[0]);
$(coll).remove();
}
})(jQuery);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
{% block head %}
<meta http-equiv="refresh" content="5; url=/">
{% endblock %}
{% block body %}
<h2>404 Not Found</h2>
<div>The requested page is not found. The page will be automatically redirected to <a href="/">Main</a>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US">
<head profile="http://gmpg.org/xfn/11">
{% block head %}{% endblock %}
</head>
<body>
<div id="content">
{% block body %}{% endblock %}
</div>
</body>
</html>

View File

@ -0,0 +1,107 @@
{% extends "layout.html" %}
{% block content %}
<script type="text/javascript">
$(document).ready(function() {
$("#data_table").dataTable();
$(document).tooltip();
});
function show_instructions() {
$("#verification_instructions").dialog("open");
}
$(function () {
$("#verification_instructions").dialog({
autoOpen: false,
modal: true,
width: 500,
buttons: {
Ok: function () {
$(this).dialog("close");
}
}
});
});
</script>
<h2><a href="{{ url_for('summary') }}">&#x2302;</a> / {{ driver.vendor }} {{ driver.name }}</h2>
<div style="margin-top: 1em; margin-bottom: 2em;">
{% if driver.maintainer %}
<div><strong>Driver Maintainer:</strong> {{ driver.maintainer.name }}</div>
{% endif %}
{% if driver.description %}
<div><strong>Description:</strong> {{ driver.description }}</div>
{% endif %}
</div>
<table id="data_table">
<thead>
<tr>
<th>OS Version</th>
<th>Verification Level</th>
<th>Verification Status</th>
</tr>
</thead>
<tbody>
{% for version in driver.os_versions %}
<tr>
<td>
{{ version.os_version }}
</td>
<td style="text-align: center;"><span
class="verification_{{ version.level }}">{{ version.verification_name }}</span>
{% if version.verification == '3rd_party_verification' %}
by {{ version.verifiers | join_plus(', ', field='name') }}
{% elif version.verification == 'self_verification' %}
(<a href="#" onclick="show_instructions();">be the first to verify!</a>)
{% endif %}
</td>
<td style="text-align: center;">
{% if version.review_url %}
{% set title = "Click to see test results" %}
{% if version.comment %}
{% set title = version.comment %}
{% endif %}
<a href="{{ version.review_url }}" title="{{ title }}" target="_blank">
{% endif %}
{% if version.success == True %}
<span style="color: #008000">&#x2714;</span>
{% elif version.success == False %}
<span style="color: red">&#x2716;</span>
{% else %}
<span style="color: darkblue">n/a</span>
{% endif %}
{% if version.review_url %}
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div id="verification_instructions" title="Verification Instructions">
<div>To verify the driver:</div>
<ol>
<li>Install driver according to <a href="{{ driver.wiki }}">{{ driver.wiki }}</a></li>
<li>Configure your OpenStack to use the selected driver</li>
<li>Run full Tempest suite</li>
<li>Create patch to <a href="https://github.com/Mirantis/driverlog/blob/master/etc/default_data.json">default_data.json</a>
describing verification status:
<pre>
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": ["&lt;enter your name here&gt;"],
"success": true
}
</pre>
</li>
</ol>
</div>
{% endblock %}

View File

@ -0,0 +1,57 @@
{% extends "base.html" %}
{% block head %}
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>DriverLog | vendor drivers for OpenStack {% if page_title %}| {{ page_title }} {% endif %}</title>
{% if not page_title %}
<meta name="description" content="Vendor drivers for OpenStack"/>
{% else %}
<meta name="description" content="vendor drivers for OpenStack | {{ page_title }}"/>
{% endif %}
<meta name="keywords" content="openstack, driverlog, vendor, driver, cinder, neutron"/>
<link href='http://fonts.googleapis.com/css?family=PT+Sans:400,700,400italic&subset=latin,cyrillic' rel='stylesheet' type='text/css' />
<link href='http://fonts.googleapis.com/css?family=PT+Sans+Caption&subset=latin,cyrillic' rel='stylesheet' type='text/css' />
<link href='http://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700&subset=latin,cyrillic' rel='stylesheet' type='text/css' />
<link rel="icon" href="{{ url_for('static', filename='images/favicon.png') }}" type="image/png"/>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/jquery.dataTables.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/ui-lightness/jquery-ui-1.10.4.custom.min.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/select2.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/style.css') }}">
<script type="text/javascript" src="{{ url_for('.static', filename='js/jquery-1.9.1.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-ui-1.10.4.custom.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.dataTables.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/select2.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.tmpl.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/driverlog-ui.js') }}"></script>
{% block scripts %}{% endblock %}
{% endblock %}
{% block body %}
<div class="page">
<div class="aheader">
<div style="float: right; margin-top: 10px; margin-right: 20px;">
<a href="https://wiki.openstack.org/">About</a>
</div>
<div id="analytics_header">
<span id="logo"><a href="{{ url_for('summary') }}">DriverLog</a></span>
<span id="slogan">| vendor drivers for OpenStack</span>
</div>
</div>
<div style="margin-top: 3em;">
{% block content %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,139 @@
{% extends "layout.html" %}
{% block content %}
<script type="text/javascript">
$(document).ready(function() {
$("#data_table").dataTable();
$(document).tooltip();
var projects=[
{% for item in projects %}
{
id: "{{ item.project_id }}",
text: "{{ item.project_name }}"
},
{% endfor %}
];
var projects_map = {};
for (var i in projects) {
projects_map[projects[i].id] = projects[i].text;
}
$("#project_selector").select2({
allowClear: true,
placeholder: "Select Project",
data: { results: projects, text: 'text' },
initSelection: function(element, callback) {
callback({id: element.val(), text: projects_map[element.val()]});
}
}).on("change", function(e) {
reload();
});
var vendors=[
{% for item in vendors %}
{
id: "{{ item }}",
text: "{{ item }}"
},
{% endfor %}
];
$("#vendor_selector").select2({
allowClear: true,
placeholder: "Select Vendor",
data: { results: vendors, text: 'text' },
initSelection: function(element, callback) {
callback({id: element.val(), text: element.val()});
}
}).on("change", function(e) {
reload();
});
var levels=[
{% for item in levels %}
{
id: "{{ item.level_id }}",
text: "{{ item.level_name }}"
},
{% endfor %}
];
var levels_map = {};
for (var i in levels) {
levels_map[levels[i].id] = levels[i].text;
}
$("#level_selector").select2({
allowClear: true,
placeholder: "Select Level",
data: { results: levels, text: 'text' },
initSelection: function(element, callback) {
callback({id: element.val(), text: levels_map[element.val()]});
}
}).on("change", function(e) {
reload();
});
});
</script>
<div class="drops">
<div class="drop">
<label for="project_selector"
title="OpenStack Project">Project</label>
<input type="hidden" id="project_selector" style="width:240px"
{% if project_id %}
value="{{ project_id }}"
{% endif %}/>
</div>
<div class="drop">
<label for="vendor_selector"
title="Vendor">Vendor</label>
<input type="hidden" id="vendor_selector" style="width:240px"
{% if vendor %}
value="{{ vendor }}"
{% endif %}/>
</div>
<div class="drop">
<label for="level_selector"
title="Verification Level">Level</label>
<input type="hidden" id="level_selector" style="width:240px"
{% if level_id %}
value="{{ level_id }}"
{% endif %}/>
</div>
</div>
<div style="margin-top: 2em;">
<table id="data_table" class="display">
<thead>
<tr>
<th>Project</th>
<th>Vendor</th>
<th>Driver</th>
<th>Verification</th>
</tr>
</thead>
<tbody>
{% for driver in drivers %}
<tr>
<td>{{ driver.project_name }}</td>
<td>{{ driver.vendor }}</td>
<td><div>
{% if driver.wiki %}<a href="{{ driver.wiki }}">{% endif %}
<strong>{{ driver.name }}</strong>
{% if driver.wiki %}</a>{% endif %}
</div><div>{{ driver.description }}</div></td>
<td><span class="verification_{{ driver.level }}" title="Click to see details"><a style="color: inherit" href="{{ url_for('details', project_id=driver.project_id, vendor=driver.vendor, driver_name=driver.name)}}">{{ driver.level_name }}</a></span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -0,0 +1,166 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import flask
import memcache
from driverlog.dashboard import memory_storage
from driverlog.openstack.common import log as logging
from driverlog.processor import utils
LOG = logging.getLogger(__name__)
LEVELS = [
{
'level_id': 'self_verification',
'level_name': 'self-verification',
},
{
'level_id': '3rd_party_verification',
'level_name': '3rd-party verification',
},
{
'level_id': 'external_ci_verification',
'level_name': 'verified by external CI'
}
]
def _build_levels_map():
levels_map = dict()
index = 1
for level in LEVELS:
level['level'] = index
levels_map[level['level_id']] = level
index += 1
return levels_map
def _build_projects_map(default_data):
projects_map = {}
for project in default_data['projects']:
projects_map[project['id']] = project
return projects_map
def _build_drivers_map(default_data, levels_map, projects_map):
driver_map = {}
for driver in default_data['drivers']:
driver['project_name'] = projects_map[driver['project_id']]['name']
driver['os_versions_map'] = {}
max_level = LEVELS[0]
for os_version in driver['os_versions']:
level = levels_map[os_version['verification']]
os_version['verification_name'] = level['level_name']
os_version['level'] = level['level']
if 'os_version' not in os_version:
os_version['os_version'] = 'master'
if level['level'] > max_level['level']:
max_level = level
max_level['os_version'] = os_version['os_version']
driver['os_versions_map'][os_version['os_version']] = os_version
driver.update(max_level)
key = (driver['project_id'].lower(),
driver['vendor'].lower(),
driver['name'].lower())
driver_map[key] = driver
return driver_map
def get_vault():
vault = getattr(flask.current_app, 'driverlog_vault', None)
if not vault:
try:
vault = {}
vault['memory_storage'] = memory_storage.get_memory_storage(
memory_storage.MEMORY_STORAGE_CACHED)
if 'CONF' not in flask.current_app.config:
LOG.critical('Configure environment variable DRIVERLOG_CONF '
'with path to config file')
flask.abort(500)
conf = flask.current_app.config['CONF']
dd_uri = conf.default_data_uri
vault['default_data'] = utils.read_json_from_uri(dd_uri)
if not vault['default_data']:
LOG.critical('Default data config file "%s" is not found',
dd_uri)
flask.abort(500)
levels_map = _build_levels_map()
vault['levels_map'] = levels_map
projects_map = _build_projects_map(vault['default_data'])
vault['projects_map'] = projects_map
drivers_map = _build_drivers_map(vault['default_data'], levels_map,
projects_map)
vault['drivers_map'] = drivers_map
MEMCACHED_URI_PREFIX = r'^memcached:\/\/'
stripped = re.sub(MEMCACHED_URI_PREFIX, '',
conf.runtime_storage_uri)
memcached_uri = stripped.split(',')
memcached = memcache.Client(memcached_uri)
vault['memcached'] = memcached
flask.current_app.driverlog_vault = vault
except Exception as e:
LOG.critical('Failed to initialize application: %s', e)
LOG.exception(e)
flask.abort(500)
if not getattr(flask.request, 'driverlog_updated', None):
flask.request.driverlog_updated = True
memcached = vault['memcached']
update = memcached.get('driverlog:update')
if update:
levels_map = vault['levels_map']
for proj_vendor_driver, os_versions_map in update.iteritems():
ovm = os_versions_map['os_versions_map']
if proj_vendor_driver not in vault['drivers_map']:
vault['drivers_map'][proj_vendor_driver] = os_versions_map
else:
for os_version, info in ovm.iteritems():
level = levels_map[info['verification']]
info['verification_name'] = level['level_name']
info['level'] = level['level']
vault['drivers_map'][proj_vendor_driver][
'os_versions_map'].update(ovm)
return vault
def get_memory_storage():
return get_vault()['memory_storage']

173
driverlog/dashboard/web.py Normal file
View File

@ -0,0 +1,173 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import urllib
import flask
from flask.ext import gravatar as gravatar_ext
from oslo.config import cfg
import six
from driverlog.dashboard import api
from driverlog.dashboard import decorators
from driverlog.dashboard import parameters
from driverlog.dashboard import vault
from driverlog.openstack.common import log as logging
from driverlog.processor import config
# Application objects ---------
app = flask.Flask(__name__)
app.config.from_object(__name__)
app.config.from_envvar('DASHBOARD_CONF', silent=True)
app.config['APPLICATION_ROOT'] = '/myapp'
app.register_blueprint(api.blueprint)
LOG = logging.getLogger(__name__)
conf = cfg.CONF
conf.register_opts(config.OPTS)
logging.setup('dashboard')
LOG.info('Logging enabled')
conf_file = os.getenv('DRIVERLOG_CONF')
if conf_file and os.path.isfile(conf_file):
conf(default_config_files=[conf_file])
app.config['DEBUG'] = cfg.CONF.debug
app.config['CONF'] = cfg.CONF
else:
LOG.info('Conf file is empty or not exist')
# Handlers ---------
@app.route('/')
@decorators.templated()
def summary():
selected_project_id = parameters.get_single_parameter({}, 'project_id')
selected_vendor = parameters.get_single_parameter({}, 'vendor')
selected_level_id = parameters.get_single_parameter({}, 'level_id')
drivers = api.get_drivers_internal(project_id=selected_project_id,
vendor=selected_vendor,
level_id=selected_level_id)
vendors = set()
levels_id = set()
projects_id = set()
for driver in api.get_drivers_internal(project_id=selected_project_id,
level_id=selected_level_id):
vendors.add(driver['vendor'])
for driver in api.get_drivers_internal(project_id=selected_project_id,
vendor=selected_vendor):
levels_id.add(driver['level_id'])
for driver in api.get_drivers_internal(vendor=selected_vendor,
level_id=selected_level_id):
projects_id.add(driver['project_id'])
projects_map = vault.get_vault()['projects_map']
projects = [{'project_id': project_id,
'project_name': projects_map[project_id]['name']}
for project_id in projects_id]
levels_map = vault.get_vault()['levels_map']
levels = [{'level_id': level_id,
'level_name': levels_map[level_id]['level_name']}
for level_id in levels_id]
if selected_project_id not in projects_map:
selected_project_id = None
if selected_vendor not in vendors:
selected_vendor = None
if selected_level_id not in levels_map:
selected_level_id = None
return {
'drivers': drivers,
'vendors': sorted(vendors),
'levels': sorted(levels, key=lambda x: x['level_name']),
'projects': sorted(projects, key=lambda x: x['project_name']),
'project_id': selected_project_id,
'vendor': selected_vendor,
'level_id': selected_level_id,
}
@app.route('/details')
@decorators.templated()
def details():
project_id = flask.request.args.get('project_id') or ''
vendor = flask.request.args.get('vendor') or ''
driver_name = flask.request.args.get('driver_name') or ''
drivers_map = vault.get_vault()['drivers_map']
key = (urllib.unquote_plus(project_id).lower(),
urllib.unquote_plus(vendor).lower(),
urllib.unquote_plus(driver_name).lower())
if key not in drivers_map:
flask.abort(404)
driver = drivers_map[key]
os_versions_list = []
for os_version, os_version_info in driver['os_versions_map'].iteritems():
os_version_info['os_version'] = os_version
os_versions_list.append(os_version_info)
sorted(os_versions_list, key=lambda x: x['os_version'])
driver['os_versions'] = os_versions_list
return {
'driver': driver,
}
@app.errorhandler(404)
@decorators.templated('404.html', 404)
def page_not_found(e):
pass
# AJAX Handlers ---------
gravatar = gravatar_ext.Gravatar(app, size=64, rating='g', default='wavatar')
@app.template_filter('make_url')
def to_url_params(dict_params, base_url):
return base_url + '?' + '&'.join(
['%s=%s' % (k, v) for k, v in six.iteritems(dict_params)])
@app.template_filter('join_plus')
def filter_join_plus(value, separator, field=None):
if field:
return separator.join([item[field] for item in value])
else:
return separator.join(value)
def main():
app.run(cfg.CONF.listen_host, cfg.CONF.listen_port)
if __name__ == '__main__':
main()

View File

View File

View File

@ -0,0 +1,68 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Import related utilities and helper functions.
"""
import sys
import traceback
def import_class(import_str):
"""Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
try:
__import__(mod_str)
return getattr(sys.modules[mod_str], class_str)
except (ValueError, AttributeError):
raise ImportError('Class %s cannot be found (%s)' %
(class_str,
traceback.format_exception(*sys.exc_info())))
def import_object(import_str, *args, **kwargs):
"""Import a class and return an instance of it."""
return import_class(import_str)(*args, **kwargs)
def import_object_ns(name_space, import_str, *args, **kwargs):
"""Tries to import object from default namespace.
Imports a class and return an instance of it, first by trying
to find the class in a default namespace, then failing back to
a full path if not found in the default namespace.
"""
import_value = "%s.%s" % (name_space, import_str)
try:
return import_class(import_value)(*args, **kwargs)
except ImportError:
return import_class(import_str)(*args, **kwargs)
def import_module(import_str):
"""Import a module."""
__import__(import_str)
return sys.modules[import_str]
def try_import(import_str, default=None):
"""Try to import a module and if it fails return default."""
try:
return import_module(import_str)
except ImportError:
return default

View File

@ -0,0 +1,169 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Justin Santa Barbara
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
'''
JSON related utilities.
This module provides a few things:
1) A handy function for getting an object down to something that can be
JSON serialized. See to_primitive().
2) Wrappers around loads() and dumps(). The dumps() wrapper will
automatically use to_primitive() for you if needed.
3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
is available.
'''
import datetime
import functools
import inspect
import itertools
import json
import xmlrpclib
import six
from driverlog.openstack.common import timeutils
_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod,
inspect.isfunction, inspect.isgeneratorfunction,
inspect.isgenerator, inspect.istraceback, inspect.isframe,
inspect.iscode, inspect.isbuiltin, inspect.isroutine,
inspect.isabstract]
_simple_types = (six.string_types + six.integer_types
+ (type(None), bool, float))
def to_primitive(value, convert_instances=False, convert_datetime=True,
level=0, max_depth=3):
"""Convert a complex object into primitives.
Handy for JSON serialization. We can optionally handle instances,
but since this is a recursive function, we could have cyclical
data structures.
To handle cyclical data structures we could track the actual objects
visited in a set, but not all objects are hashable. Instead we just
track the depth of the object inspections and don't go too deep.
Therefore, convert_instances=True is lossy ... be aware.
"""
# handle obvious types first - order of basic types determined by running
# full tests on nova project, resulting in the following counts:
# 572754 <type 'NoneType'>
# 460353 <type 'int'>
# 379632 <type 'unicode'>
# 274610 <type 'str'>
# 199918 <type 'dict'>
# 114200 <type 'datetime.datetime'>
# 51817 <type 'bool'>
# 26164 <type 'list'>
# 6491 <type 'float'>
# 283 <type 'tuple'>
# 19 <type 'long'>
if isinstance(value, _simple_types):
return value
if isinstance(value, datetime.datetime):
if convert_datetime:
return timeutils.strtime(value)
else:
return value
# value of itertools.count doesn't get caught by nasty_type_tests
# and results in infinite loop when list(value) is called.
if type(value) == itertools.count:
return six.text_type(value)
# FIXME(vish): Workaround for LP bug 852095. Without this workaround,
# tests that raise an exception in a mocked method that
# has a @wrap_exception with a notifier will fail. If
# we up the dependency to 0.5.4 (when it is released) we
# can remove this workaround.
if getattr(value, '__module__', None) == 'mox':
return 'mock'
if level > max_depth:
return '?'
# The try block may not be necessary after the class check above,
# but just in case ...
try:
recursive = functools.partial(to_primitive,
convert_instances=convert_instances,
convert_datetime=convert_datetime,
level=level,
max_depth=max_depth)
if isinstance(value, dict):
return dict((k, recursive(v)) for k, v in six.iteritems(value))
elif isinstance(value, (list, tuple)):
return [recursive(lv) for lv in value]
# It's not clear why xmlrpclib created their own DateTime type, but
# for our purposes, make it a datetime type which is explicitly
# handled
if isinstance(value, xmlrpclib.DateTime):
value = datetime.datetime(*tuple(value.timetuple())[:6])
if convert_datetime and isinstance(value, datetime.datetime):
return timeutils.strtime(value)
elif hasattr(value, 'iteritems'):
return recursive(dict(value.iteritems()), level=level + 1)
elif hasattr(value, '__iter__'):
return recursive(list(value))
elif convert_instances and hasattr(value, '__dict__'):
# Likely an instance of something. Watch for cycles.
# Ignore class member vars.
return recursive(value.__dict__, level=level + 1)
else:
if any(test(value) for test in _nasty_type_tests):
return six.text_type(value)
return value
except TypeError:
# Class objects are tricky since they may define something like
# __iter__ defined but it isn't callable as list().
return six.text_type(value)
def dumps(value, default=to_primitive, **kwargs):
return json.dumps(value, default=default, **kwargs)
def loads(s):
return json.loads(s)
def load(s):
return json.load(s)
try:
import anyjson
except ImportError:
pass
else:
anyjson._modules.append((__name__, 'dumps', TypeError,
'loads', ValueError, 'load'))
anyjson.force_implementation(__name__)

View File

@ -0,0 +1,557 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack Foundation.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Openstack logging handler.
This module adds to logging functionality by adding the option to specify
a context object when calling the various log methods. If the context object
is not specified, default formatting is used. Additionally, an instance uuid
may be passed as part of the log message, which is intended to make it easier
for admins to find messages related to a specific instance.
It also allows setting of formatting information through conf.
"""
import ConfigParser
import cStringIO
import inspect
import itertools
import logging
import logging.config
import logging.handlers
import os
import sys
import traceback
from oslo.config import cfg
from driverlog.openstack.common import importutils
from driverlog.openstack.common import jsonutils
_DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
common_cli_opts = [
cfg.BoolOpt('debug',
short='d',
default=False,
help='Print debugging output (set logging level to '
'DEBUG instead of default WARNING level).'),
cfg.BoolOpt('verbose',
short='v',
default=False,
help='Print more verbose output (set logging level to '
'INFO instead of default WARNING level).'),
]
logging_cli_opts = [
cfg.StrOpt('log-config',
metavar='PATH',
help='If this option is specified, the logging configuration '
'file specified is used and overrides any other logging '
'options specified. Please see the Python logging module '
'documentation for details on logging configuration '
'files.'),
cfg.StrOpt('log-format',
default=None,
metavar='FORMAT',
help='A logging.Formatter log message format string which may '
'use any of the available logging.LogRecord attributes. '
'This option is deprecated. Please use '
'logging_context_format_string and '
'logging_default_format_string instead.'),
cfg.StrOpt('log-date-format',
default=_DEFAULT_LOG_DATE_FORMAT,
metavar='DATE_FORMAT',
help='Format string for %%(asctime)s in log records. '
'Default: %(default)s'),
cfg.StrOpt('log-file',
metavar='PATH',
deprecated_name='logfile',
help='(Optional) Name of log file to output to. '
'If no default is set, logging will go to stdout.'),
cfg.StrOpt('log-dir',
deprecated_name='logdir',
help='(Optional) The base directory used for relative '
'--log-file paths'),
cfg.BoolOpt('use-syslog',
default=False,
help='Use syslog for logging.'),
cfg.StrOpt('syslog-log-facility',
default='LOG_USER',
help='syslog facility to receive log lines')
]
generic_log_opts = [
cfg.BoolOpt('use_stderr',
default=True,
help='Log output to standard error')
]
log_opts = [
cfg.StrOpt('logging_context_format_string',
default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [%(request_id)s %(user)s %(tenant)s] '
'%(instance)s%(message)s',
help='format string to use for log messages with context'),
cfg.StrOpt('logging_default_format_string',
default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [-] %(instance)s%(message)s',
help='format string to use for log messages without context'),
cfg.StrOpt('logging_debug_format_suffix',
default='%(funcName)s %(pathname)s:%(lineno)d',
help='data to append to log format when level is DEBUG'),
cfg.StrOpt('logging_exception_prefix',
default='%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s '
'%(instance)s',
help='prefix each line of exception output with this format'),
cfg.ListOpt('default_log_levels',
default=[
'amqplib=WARN',
'sqlalchemy=WARN',
'boto=WARN',
'suds=INFO',
'keystone=INFO',
'eventlet.wsgi.server=WARN'
],
help='list of logger=LEVEL pairs'),
cfg.BoolOpt('publish_errors',
default=False,
help='publish error events'),
cfg.BoolOpt('fatal_deprecations',
default=False,
help='make deprecations fatal'),
# NOTE(mikal): there are two options here because sometimes we are handed
# a full instance (and could include more information), and other times we
# are just handed a UUID for the instance.
cfg.StrOpt('instance_format',
default='[instance: %(uuid)s] ',
help='If an instance is passed with the log message, format '
'it like this'),
cfg.StrOpt('instance_uuid_format',
default='[instance: %(uuid)s] ',
help='If an instance UUID is passed with the log message, '
'format it like this'),
]
CONF = cfg.CONF
CONF.register_cli_opts(common_cli_opts)
CONF.register_cli_opts(logging_cli_opts)
CONF.register_opts(generic_log_opts)
CONF.register_opts(log_opts)
# our new audit level
# NOTE(jkoelker) Since we synthesized an audit level, make the logging
# module aware of it so it acts like other levels.
logging.AUDIT = logging.INFO + 1
logging.addLevelName(logging.AUDIT, 'AUDIT')
try:
NullHandler = logging.NullHandler
except AttributeError: # NOTE(jkoelker) NullHandler added in Python 2.7
class NullHandler(logging.Handler):
def handle(self, record):
pass
def emit(self, record):
pass
def createLock(self):
self.lock = None
def _dictify_context(context):
if context is None:
return None
if not isinstance(context, dict) and getattr(context, 'to_dict', None):
context = context.to_dict()
return context
def _get_binary_name():
return os.path.basename(inspect.stack()[-1][1])
def _get_log_file_path(binary=None):
logfile = CONF.log_file
logdir = CONF.log_dir
if logfile and not logdir:
return logfile
if logfile and logdir:
return os.path.join(logdir, logfile)
if logdir:
binary = binary or _get_binary_name()
return '%s.log' % (os.path.join(logdir, binary),)
class BaseLoggerAdapter(logging.LoggerAdapter):
def audit(self, msg, *args, **kwargs):
self.log(logging.AUDIT, msg, *args, **kwargs)
class LazyAdapter(BaseLoggerAdapter):
def __init__(self, name='unknown', version='unknown'):
self._logger = None
self.extra = {}
self.name = name
self.version = version
@property
def logger(self):
if not self._logger:
self._logger = getLogger(self.name, self.version)
return self._logger
class ContextAdapter(BaseLoggerAdapter):
warn = logging.LoggerAdapter.warning
def __init__(self, logger, project_name, version_string):
self.logger = logger
self.project = project_name
self.version = version_string
@property
def handlers(self):
return self.logger.handlers
def deprecated(self, msg, *args, **kwargs):
stdmsg = _("Deprecated: %s") % msg
if CONF.fatal_deprecations:
self.critical(stdmsg, *args, **kwargs)
raise DeprecatedConfig(msg=stdmsg)
else:
self.warn(stdmsg, *args, **kwargs)
def process(self, msg, kwargs):
if 'extra' not in kwargs:
kwargs['extra'] = {}
extra = kwargs['extra']
context = kwargs.pop('context', None)
# if not context:
# context = getattr(local.store, 'context', None)
if context:
extra.update(_dictify_context(context))
instance = kwargs.pop('instance', None)
instance_extra = ''
if instance:
instance_extra = CONF.instance_format % instance
else:
instance_uuid = kwargs.pop('instance_uuid', None)
if instance_uuid:
instance_extra = (CONF.instance_uuid_format
% {'uuid': instance_uuid})
extra.update({'instance': instance_extra})
extra.update({"project": self.project})
extra.update({"version": self.version})
extra['extra'] = extra.copy()
return msg, kwargs
class JSONFormatter(logging.Formatter):
def __init__(self, fmt=None, datefmt=None):
# NOTE(jkoelker) we ignore the fmt argument, but its still there
# since logging.config.fileConfig passes it.
self.datefmt = datefmt
def formatException(self, ei, strip_newlines=True):
lines = traceback.format_exception(*ei)
if strip_newlines:
lines = [itertools.ifilter(
lambda x: x,
line.rstrip().splitlines()) for line in lines]
lines = list(itertools.chain(*lines))
return lines
def format(self, record):
message = {'message': record.getMessage(),
'asctime': self.formatTime(record, self.datefmt),
'name': record.name,
'msg': record.msg,
'args': record.args,
'levelname': record.levelname,
'levelno': record.levelno,
'pathname': record.pathname,
'filename': record.filename,
'module': record.module,
'lineno': record.lineno,
'funcname': record.funcName,
'created': record.created,
'msecs': record.msecs,
'relative_created': record.relativeCreated,
'thread': record.thread,
'thread_name': record.threadName,
'process_name': record.processName,
'process': record.process,
'traceback': None}
if hasattr(record, 'extra'):
message['extra'] = record.extra
if record.exc_info:
message['traceback'] = self.formatException(record.exc_info)
return jsonutils.dumps(message)
def _create_logging_excepthook(product_name):
def logging_excepthook(type, value, tb):
extra = {}
if CONF.verbose:
extra['exc_info'] = (type, value, tb)
getLogger(product_name).critical(str(value), **extra)
return logging_excepthook
class LogConfigError(Exception):
message = ('Error loading logging config %(log_config)s: %(err_msg)s')
def __init__(self, log_config, err_msg):
self.log_config = log_config
self.err_msg = err_msg
def __str__(self):
return self.message % dict(log_config=self.log_config,
err_msg=self.err_msg)
def _load_log_config(log_config):
try:
logging.config.fileConfig(log_config)
except ConfigParser.Error as exc:
raise LogConfigError(log_config, str(exc))
def setup(product_name):
"""Setup logging."""
if CONF.log_config:
_load_log_config(CONF.log_config)
else:
_setup_logging_from_conf()
sys.excepthook = _create_logging_excepthook(product_name)
def set_defaults(logging_context_format_string):
cfg.set_defaults(log_opts,
logging_context_format_string=
logging_context_format_string)
def _find_facility_from_conf():
facility_names = logging.handlers.SysLogHandler.facility_names
facility = getattr(logging.handlers.SysLogHandler,
CONF.syslog_log_facility,
None)
if facility is None and CONF.syslog_log_facility in facility_names:
facility = facility_names.get(CONF.syslog_log_facility)
if facility is None:
valid_facilities = facility_names.keys()
consts = ['LOG_AUTH', 'LOG_AUTHPRIV', 'LOG_CRON', 'LOG_DAEMON',
'LOG_FTP', 'LOG_KERN', 'LOG_LPR', 'LOG_MAIL', 'LOG_NEWS',
'LOG_AUTH', 'LOG_SYSLOG', 'LOG_USER', 'LOG_UUCP',
'LOG_LOCAL0', 'LOG_LOCAL1', 'LOG_LOCAL2', 'LOG_LOCAL3',
'LOG_LOCAL4', 'LOG_LOCAL5', 'LOG_LOCAL6', 'LOG_LOCAL7']
valid_facilities.extend(consts)
raise TypeError(('syslog facility must be one of: %s') %
', '.join("'%s'" % fac
for fac in valid_facilities))
return facility
def _setup_logging_from_conf():
log_root = getLogger(None).logger
for handler in log_root.handlers:
log_root.removeHandler(handler)
if CONF.use_syslog:
facility = _find_facility_from_conf()
syslog = logging.handlers.SysLogHandler(address='/dev/log',
facility=facility)
log_root.addHandler(syslog)
logpath = _get_log_file_path()
if logpath:
filelog = logging.handlers.WatchedFileHandler(logpath)
log_root.addHandler(filelog)
if CONF.use_stderr:
streamlog = ColorHandler()
log_root.addHandler(streamlog)
elif not CONF.log_file:
# pass sys.stdout as a positional argument
# python2.6 calls the argument strm, in 2.7 it's stream
streamlog = logging.StreamHandler(sys.stdout)
log_root.addHandler(streamlog)
if CONF.publish_errors:
handler = importutils.import_object(
"quantum.openstack.common.log_handler.PublishErrorsHandler",
logging.ERROR)
log_root.addHandler(handler)
datefmt = CONF.log_date_format
for handler in log_root.handlers:
# NOTE(alaski): CONF.log_format overrides everything currently. This
# should be deprecated in favor of context aware formatting.
if CONF.log_format:
handler.setFormatter(logging.Formatter(fmt=CONF.log_format,
datefmt=datefmt))
log_root.info('Deprecated: log_format is now deprecated and will '
'be removed in the next release')
else:
handler.setFormatter(ContextFormatter(datefmt=datefmt))
if CONF.debug:
log_root.setLevel(logging.DEBUG)
elif CONF.verbose:
log_root.setLevel(logging.INFO)
else:
log_root.setLevel(logging.WARNING)
for pair in CONF.default_log_levels:
mod, _sep, level_name = pair.partition('=')
level = logging.getLevelName(level_name)
logger = logging.getLogger(mod)
logger.setLevel(level)
_loggers = {}
def getLogger(name='unknown', version='unknown'):
if name not in _loggers:
_loggers[name] = ContextAdapter(logging.getLogger(name),
name,
version)
return _loggers[name]
def getLazyLogger(name='unknown', version='unknown'):
"""Returns lazy logger.
Creates a pass-through logger that does not create the real logger
until it is really needed and delegates all calls to the real logger
once it is created.
"""
return LazyAdapter(name, version)
class WritableLogger(object):
"""A thin wrapper that responds to `write` and logs."""
def __init__(self, logger, level=logging.INFO):
self.logger = logger
self.level = level
def write(self, msg):
self.logger.log(self.level, msg)
class ContextFormatter(logging.Formatter):
"""A context.RequestContext aware formatter configured through flags.
The flags used to set format strings are: logging_context_format_string
and logging_default_format_string. You can also specify
logging_debug_format_suffix to append extra formatting if the log level is
debug.
For information about what variables are available for the formatter see:
http://docs.python.org/library/logging.html#formatter
"""
def format(self, record):
"""Uses contextstring if request_id is set, otherwise default."""
# NOTE(sdague): default the fancier formating params
# to an empty string so we don't throw an exception if
# they get used
for key in ('instance', 'color'):
if key not in record.__dict__:
record.__dict__[key] = ''
if record.__dict__.get('request_id', None):
self._fmt = CONF.logging_context_format_string
else:
self._fmt = CONF.logging_default_format_string
if (record.levelno == logging.DEBUG and
CONF.logging_debug_format_suffix):
self._fmt += " " + CONF.logging_debug_format_suffix
# Cache this on the record, Logger will respect our formated copy
if record.exc_info:
record.exc_text = self.formatException(record.exc_info, record)
return logging.Formatter.format(self, record)
def formatException(self, exc_info, record=None):
"""Format exception output with CONF.logging_exception_prefix."""
if not record:
return logging.Formatter.formatException(self, exc_info)
stringbuffer = cStringIO.StringIO()
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2],
None, stringbuffer)
lines = stringbuffer.getvalue().split('\n')
stringbuffer.close()
if CONF.logging_exception_prefix.find('%(asctime)') != -1:
record.asctime = self.formatTime(record, self.datefmt)
formatted_lines = []
for line in lines:
pl = CONF.logging_exception_prefix % record.__dict__
fl = '%s%s' % (pl, line)
formatted_lines.append(fl)
return '\n'.join(formatted_lines)
class ColorHandler(logging.StreamHandler):
LEVEL_COLORS = {
logging.DEBUG: '\033[00;32m', # GREEN
logging.INFO: '\033[00;36m', # CYAN
logging.AUDIT: '\033[01;36m', # BOLD CYAN
logging.WARN: '\033[01;33m', # BOLD YELLOW
logging.ERROR: '\033[01;31m', # BOLD RED
logging.CRITICAL: '\033[01;31m', # BOLD RED
}
def format(self, record):
record.color = self.LEVEL_COLORS[record.levelno]
return logging.StreamHandler.format(self, record)
class DeprecatedConfig(Exception):
message = ("Fatal call to deprecated config: %(msg)s")
def __init__(self, msg):
super(Exception, self).__init__(self.message % dict(msg=msg))

View File

@ -0,0 +1,210 @@
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Time related utilities and helper functions.
"""
import calendar
import datetime
import time
import iso8601
import six
# ISO 8601 extended time format with microseconds
_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f'
_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND
def isotime(at=None, subsecond=False):
"""Stringify time in ISO 8601 format."""
if not at:
at = utcnow()
st = at.strftime(_ISO8601_TIME_FORMAT
if not subsecond
else _ISO8601_TIME_FORMAT_SUBSECOND)
tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
st += ('Z' if tz == 'UTC' else tz)
return st
def parse_isotime(timestr):
"""Parse time from ISO 8601 format."""
try:
return iso8601.parse_date(timestr)
except iso8601.ParseError as e:
raise ValueError(six.text_type(e))
except TypeError as e:
raise ValueError(six.text_type(e))
def strtime(at=None, fmt=PERFECT_TIME_FORMAT):
"""Returns formatted utcnow."""
if not at:
at = utcnow()
return at.strftime(fmt)
def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT):
"""Turn a formatted time back into a datetime."""
return datetime.datetime.strptime(timestr, fmt)
def normalize_time(timestamp):
"""Normalize time in arbitrary timezone to UTC naive object."""
offset = timestamp.utcoffset()
if offset is None:
return timestamp
return timestamp.replace(tzinfo=None) - offset
def is_older_than(before, seconds):
"""Return True if before is older than seconds."""
if isinstance(before, six.string_types):
before = parse_strtime(before).replace(tzinfo=None)
else:
before = before.replace(tzinfo=None)
return utcnow() - before > datetime.timedelta(seconds=seconds)
def is_newer_than(after, seconds):
"""Return True if after is newer than seconds."""
if isinstance(after, six.string_types):
after = parse_strtime(after).replace(tzinfo=None)
else:
after = after.replace(tzinfo=None)
return after - utcnow() > datetime.timedelta(seconds=seconds)
def utcnow_ts():
"""Timestamp version of our utcnow function."""
if utcnow.override_time is None:
# NOTE(kgriffs): This is several times faster
# than going through calendar.timegm(...)
return int(time.time())
return calendar.timegm(utcnow().timetuple())
def utcnow():
"""Overridable version of utils.utcnow."""
if utcnow.override_time:
try:
return utcnow.override_time.pop(0)
except AttributeError:
return utcnow.override_time
return datetime.datetime.utcnow()
def iso8601_from_timestamp(timestamp):
"""Returns a iso8601 formated date from timestamp."""
return isotime(datetime.datetime.utcfromtimestamp(timestamp))
utcnow.override_time = None
def set_time_override(override_time=None):
"""Overrides utils.utcnow.
Make it return a constant time or a list thereof, one at a time.
:param override_time: datetime instance or list thereof. If not
given, defaults to the current UTC time.
"""
utcnow.override_time = override_time or datetime.datetime.utcnow()
def advance_time_delta(timedelta):
"""Advance overridden time using a datetime.timedelta."""
assert(not utcnow.override_time is None)
try:
for dt in utcnow.override_time:
dt += timedelta
except TypeError:
utcnow.override_time += timedelta
def advance_time_seconds(seconds):
"""Advance overridden time by seconds."""
advance_time_delta(datetime.timedelta(0, seconds))
def clear_time_override():
"""Remove the overridden time."""
utcnow.override_time = None
def marshall_now(now=None):
"""Make an rpc-safe datetime with microseconds.
Note: tzinfo is stripped, but not required for relative times.
"""
if not now:
now = utcnow()
return dict(day=now.day, month=now.month, year=now.year, hour=now.hour,
minute=now.minute, second=now.second,
microsecond=now.microsecond)
def unmarshall_time(tyme):
"""Unmarshall a datetime dict."""
return datetime.datetime(day=tyme['day'],
month=tyme['month'],
year=tyme['year'],
hour=tyme['hour'],
minute=tyme['minute'],
second=tyme['second'],
microsecond=tyme['microsecond'])
def delta_seconds(before, after):
"""Return the difference between two timing objects.
Compute the difference in seconds between two date, time, or
datetime objects (as a float, to microsecond resolution).
"""
delta = after - before
return total_seconds(delta)
def total_seconds(delta):
"""Return the total seconds of datetime.timedelta object.
Compute total seconds of datetime.timedelta, datetime.timedelta
doesn't have method total_seconds in Python2.6, calculate it manually.
"""
try:
return delta.total_seconds()
except AttributeError:
return ((delta.days * 24 * 3600) + delta.seconds +
float(delta.microseconds) / (10 ** 6))
def is_soon(dt, window):
"""Determines if time is going to happen in the next window seconds.
:param dt: the time
:param window: minimum seconds to remain to consider the time not soon
:return: True if expiration is within the given duration
"""
soon = (utcnow() + datetime.timedelta(seconds=window))
return normalize_time(dt) <= soon

View File

@ -0,0 +1 @@
__author__ = 'ishakhat'

View File

@ -0,0 +1,36 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo.config import cfg
OPTS = [
cfg.StrOpt('default-data-uri',
help='URI for default data'),
cfg.StrOpt('listen-host', default='127.0.0.1',
help='The address dashboard listens on'),
cfg.IntOpt('listen-port', default=8080,
help='The port dashboard listens on'),
cfg.StrOpt('runtime-storage-uri', default='memcached://127.0.0.1:11211',
help='Storage URI'),
cfg.StrOpt('review-uri', default='gerrit://review.openstack.org',
help='URI of review system'),
cfg.StrOpt('ssh-key-filename', default='/home/user/.ssh/id_rsa',
help='SSH key for gerrit review system access'),
cfg.StrOpt('ssh-username', default='user',
help='SSH username for gerrit review system access'),
cfg.BoolOpt('force-update', default=False,
help='Forcibly read default data and update records'),
]

158
driverlog/processor/main.py Normal file
View File

@ -0,0 +1,158 @@
# Copyright (c) 2013 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import memcache
from oslo.config import cfg
from six.moves.urllib import parse
from driverlog.openstack.common import log as logging
from driverlog.processor import config
from driverlog.processor import rcs
from driverlog.processor import utils
LOG = logging.getLogger(__name__)
def update_generator(memcached, default_data, ci_ids_map, force_update=False):
for project in default_data['projects']:
project_id = project['id']
rcs_inst = rcs.get_rcs(project_id, cfg.CONF.review_uri)
rcs_inst.setup(key_filename=cfg.CONF.ssh_key_filename,
username=cfg.CONF.ssh_username)
LOG.debug('Processing reviews for project: %s', project_id)
rcs_key = 'rcs:' + parse.quote_plus(project_id)
last_id = None
if not force_update:
last_id = memcached.get(rcs_key)
review_iterator = rcs_inst.log(last_id)
branch_ci_set = set()
for review in review_iterator:
review_url = review['url']
branch = review['branch']
for approval in review['currentPatchSet']['approvals']:
if approval['type'] != 'VRIF':
continue
ci = approval['by']['username']
if ci not in ci_ids_map:
continue
branch_ci = (branch, ci)
if branch_ci in branch_ci_set:
continue # already seen, ignore
branch_ci_set.add(branch_ci)
patch_number = review['currentPatchSet']['number']
message = ''
for comment in reversed(review['comments']):
prefix = 'Patch Set %s:' % patch_number
if ((comment['reviewer']['username'] == ci) and
(comment['message'].find(prefix) == 0)):
message = comment['message'][len(prefix):].strip()
break
success = approval['value'] in ['1', '2']
vendor = ci_ids_map[ci][0]
driver_name = ci_ids_map[ci][1]
yield {
(project_id.lower(), vendor.lower(),
driver_name.lower()): {
'os_versions_map': {
branch: {
'project_id': project_id,
'vendor': vendor,
'name': driver_name,
'verification': 'external_ci_verification',
'success': success,
'comment': message,
'timestamp': approval['grantedOn'],
'review_url': review_url
}
}
}
}
last_id = rcs_inst.get_last_id()
LOG.debug('RCS last id is: %s', last_id)
memcached.set(rcs_key, last_id)
def main():
# init conf and logging
conf = cfg.CONF
conf.register_cli_opts(config.OPTS)
conf.register_opts(config.OPTS)
conf()
logging.setup('driverlog')
LOG.info('Logging enabled')
MEMCACHED_URI_PREFIX = r'^memcached:\/\/'
stripped = re.sub(MEMCACHED_URI_PREFIX, '', cfg.CONF.runtime_storage_uri)
if not stripped:
exit(1)
memcached_uri = stripped.split(',')
memcached = memcache.Client(memcached_uri)
default_data = utils.read_json_from_uri(cfg.CONF.default_data_uri)
if not default_data:
LOG.critical('Unable to load default data')
return not 0
ci_ids_map = {}
for driver in default_data['drivers']:
vendor = driver['vendor']
driver_name = driver['name']
for os_version in driver['os_versions']:
if os_version['verification'] == 'external_ci_verification':
ci_id = os_version['ci_id']
ci_ids_map[ci_id] = (vendor, driver_name)
persisted_data = {}
if not cfg.CONF.force_update:
persisted_data = memcached.get('driverlog:update') or {}
for record in update_generator(memcached, default_data, ci_ids_map,
force_update=cfg.CONF.force_update):
LOG.info('Got new record from Gerrit: %s', record)
key = record.keys()[0]
if key not in persisted_data:
persisted_data.update(record)
else:
persisted_os_versions = persisted_data[key]['os_versions_map']
for os_version, info in record[key]['os_versions_map'].iteritems():
if os_version not in persisted_os_versions:
persisted_os_versions[os_version] = info
else:
persisted_os_versions[os_version].update(info)
memcached.set('driverlog:update', persisted_data)
if __name__ == '__main__':
main()

178
driverlog/processor/rcs.py Normal file
View File

@ -0,0 +1,178 @@
# Copyright (c) 2013 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import re
import paramiko
from driverlog.openstack.common import log as logging
LOG = logging.getLogger(__name__)
DEFAULT_PORT = 29418
GERRIT_URI_PREFIX = r'^gerrit:\/\/'
PAGE_LIMIT = 100
class Rcs(object):
def __init__(self, project_id, uri):
self.project_id = project_id
def setup(self, **kwargs):
pass
def log(self, last_id):
return []
def get_last_id(self):
return -1
class Gerrit(Rcs):
def __init__(self, project_id, uri):
super(Gerrit, self).__init__(project_id, uri)
stripped = re.sub(GERRIT_URI_PREFIX, '', uri)
if stripped:
self.hostname, semicolon, self.port = stripped.partition(':')
if not self.port:
self.port = DEFAULT_PORT
else:
raise Exception('Invalid rcs uri %s' % uri)
self.client = paramiko.SSHClient()
self.client.load_system_host_keys()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
def setup(self, **kwargs):
if 'key_filename' in kwargs:
self.key_filename = kwargs['key_filename']
else:
self.key_filename = None
if 'username' in kwargs:
self.username = kwargs['username']
else:
self.username = None
def _connect(self):
try:
self.client.connect(self.hostname, port=self.port,
key_filename=self.key_filename,
username=self.username)
LOG.debug('Successfully connected to Gerrit')
return True
except Exception as e:
LOG.error('Failed to connect to gerrit %(host)s:%(port)s. '
'Error: %(err)s', {'host': self.hostname,
'port': self.port, 'err': e})
LOG.exception(e)
return False
def _get_cmd(self, project_id, sort_key=None, limit=PAGE_LIMIT):
cmd = ('gerrit query --format JSON '
'project:\'%(project_id)s\' limit:%(limit)s '
'--current-patch-set --comments ' %
{'project_id': project_id, 'limit': limit})
cmd += ' is:merged'
if sort_key:
cmd += ' resume_sortkey:%016x' % sort_key
return cmd
def _exec_command(self, cmd):
try:
return self.client.exec_command(cmd)
except Exception as e:
LOG.error('Error %(error)s while execute command %(cmd)s',
{'error': e, 'cmd': cmd})
LOG.exception(e)
return False
def _poll_reviews(self, project_id, start_id=None, last_id=None):
sort_key = start_id
while True:
cmd = self._get_cmd(project_id, sort_key)
LOG.debug('Executing command: %s', cmd)
exec_result = self._exec_command(cmd)
if not exec_result:
break
stdin, stdout, stderr = exec_result
proceed = False
for line in stdout:
review = json.loads(line)
if 'sortKey' in review:
sort_key = int(review['sortKey'], 16)
if sort_key <= last_id:
proceed = False
break
proceed = True
review['project_id'] = project_id
yield review
if not proceed:
break
def log(self, last_id):
if not self._connect():
return
# poll new merged reviews from the top down to last_id
LOG.debug('Poll new reviews for project: %s', self.project_id)
for review in self._poll_reviews(self.project_id, last_id=last_id):
yield review
self.client.close()
def get_last_id(self):
if not self._connect():
return None
LOG.debug('Get last id for project: %s', self.project_id)
cmd = self._get_cmd(self.project_id, limit=1)
LOG.debug('Executing command: %s', cmd)
exec_result = self._exec_command(cmd)
if not exec_result:
return None
stdin, stdout, stderr = exec_result
last_id = None
for line in stdout:
review = json.loads(line)
if 'sortKey' in review:
last_id = int(review['sortKey'], 16)
break
self.client.close()
LOG.debug('Project %(project_id)s last id is %(id)s',
{'project_id': self.project_id, 'id': last_id})
return last_id
def get_rcs(project_id, uri):
LOG.debug('Review control system is requested for uri %s' % uri)
match = re.search(GERRIT_URI_PREFIX, uri)
if match:
return Gerrit(project_id, uri)
else:
LOG.warning('Unsupported review control system, fallback to dummy')
return Rcs(project_id, uri)

View File

@ -0,0 +1,173 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import cgi
import datetime
import json
import re
import time
import iso8601
import six
from six.moves.urllib import parse
from six.moves.urllib import request
from driverlog.openstack.common import log as logging
LOG = logging.getLogger(__name__)
def date_to_timestamp(d):
if not d:
return 0
if d == 'now':
return int(time.time())
return int(time.mktime(
datetime.datetime.strptime(d, '%Y-%b-%d').timetuple()))
def date_to_timestamp_ext(d):
try:
return date_to_timestamp(d)
except ValueError:
return int(d)
def iso8601_to_timestamp(s):
return int(time.mktime(iso8601.parse_date(s).timetuple()))
def timestamp_to_week(timestamp):
# Jan 4th 1970 is the first Sunday in the Epoch
return (timestamp - 3 * 24 * 3600) // (7 * 24 * 3600)
def week_to_date(week):
timestamp = week * 7 * 24 * 3600 + 3 * 24 * 3600
return (datetime.datetime.fromtimestamp(timestamp).
strftime('%Y-%m-%d %H:%M:%S'))
def check_email_validity(email):
if email:
return re.match(r'[\w\d_\.-]+@([\w\d_\.-]+\.)+[\w]+', email)
return False
def read_uri(uri):
try:
fd = request.urlopen(uri)
raw = fd.read()
fd.close()
return raw
except Exception as e:
LOG.warn('Error while reading uri: %s' % e)
def read_json_from_uri(uri):
try:
return json.loads(read_uri(uri))
except Exception as e:
LOG.warn('Error parsing json: %s' % e)
def make_range(start, stop, step):
last_full = stop - ((stop - start) % step)
for i in xrange(start, last_full, step):
yield xrange(i, i + step)
if stop > last_full:
yield xrange(last_full, stop)
def store_user(runtime_storage_inst, user):
if not user.get('seq'):
user['seq'] = runtime_storage_inst.inc_user_count()
runtime_storage_inst.set_by_key('user:%s' % user['seq'], user)
if user.get('user_id'):
runtime_storage_inst.set_by_key('user:%s' % user['user_id'], user)
if user.get('launchpad_id'):
runtime_storage_inst.set_by_key('user:%s' % user['launchpad_id'], user)
for email in user.get('emails') or []:
runtime_storage_inst.set_by_key('user:%s' % email, user)
def load_user(runtime_storage_inst, user_id):
if user_id:
return runtime_storage_inst.get_by_key('user:%s' % user_id)
return None
def delete_user(runtime_storage_inst, user):
runtime_storage_inst.delete_by_key('user:%s' % user['seq'])
def load_repos(runtime_storage_inst):
return runtime_storage_inst.get_by_key('repos') or []
def unwrap_text(text):
res = ''
for line in text.splitlines():
s = line.rstrip()
if not s:
continue
res += line
if (not s[0].isalpha()) or (s[-1] in ['.', '!', '?', '>', ':', ';']):
res += '\n'
else:
res += ' '
return res.rstrip()
def format_text(s):
s = cgi.escape(re.sub(re.compile('\n{2,}', flags=re.MULTILINE), '\n', s))
s = re.sub(r'([/\/\*=]{1,2}|--|\+\+)', r'\1&#8203;', s)
return s
def make_age_string(seconds):
days = seconds / (3600 * 24)
hours = (seconds / 3600) - (days * 24)
minutes = (seconds / 60) - (days * 24 * 60) - (hours * 60)
return '%d days, %d hours, %d minutes' % (days, hours, minutes)
def merge_records(original, new):
need_update = False
for key, value in six.iteritems(new):
if original.get(key) != value:
need_update = True
original[key] = value
return need_update
def get_blueprint_id(module, name):
return module + ':' + name
def add_index(sequence, start=1, item_filter=lambda x: True):
n = start
for item in sequence:
if item_filter(item):
item['index'] = n
n += 1
else:
item['index'] = ''
return sequence
def safe_encode(s):
return parse.quote_plus(s.encode('utf-8'))

18
driverlog/version.py Normal file
View File

@ -0,0 +1,18 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pbr import version
version_info = version.VersionInfo('driverlog')

538
etc/default_data.json Normal file
View File

@ -0,0 +1,538 @@
{
"projects": [
{
"id": "openstack/cinder",
"name": "Cinder (Block Storage)"
},
{
"id": "openstack/neutron",
"name": "Neutron (Networking)"
},
{
"id": "openstack/sahara",
"name": "Sahara (Data Processing)"
}
],
"drivers": [
{
"project_id": "openstack/cinder",
"vendor": "Dell",
"name": "EqualLogic",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "J M Jacob (jacob-jacob)"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "EMC",
"name": "SMI-S (FC)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Xing Yang (xing-yang)"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "EMC",
"name": "SMI-S (iSCSI)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Xing Yang (xing-yang)"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "EMC",
"name": "VNX Direct (iSCSI)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Xing Yang (xing-yang)"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "HP",
"name": "3PAR StoreServ(FC)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Walt Boring"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "HP",
"name": "3PAR StoreServ(iSCSI)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Walt Boring"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "HP",
"name": "LeftHand StoreVirtual",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Jim Branen"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "HP",
"name": "MSA (FC)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Gauvain Pocentek (gpocentek)"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "IBM",
"name": "IBM GPFS",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Bill Owen (billowen)"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "IBM",
"name": "IBM Storwize/SVC (FC)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Avishay Traeger (avishay-il)"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "IBM",
"name": "IBM Storwize/SVC (iSCSI)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Avishay Traeger (avishay-il)"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "IBM",
"name": "NAS (SONAS & Storwize V7000 Unified)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "self_verification",
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "NetApp",
"name": "C-Mode (iSCSI)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Andrew Kerr (andrew-kerr)"
}
],
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "NetApp",
"name": "E-Series (iSCSI)",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "self_verification",
"success": true
}
]
},
{
"project_id": "openstack/cinder",
"vendor": "SolidFire",
"name": "Cinder Driver",
"description": "",
"maintainer": {
"name": "John Doe",
"email": "john_dow@openstack.org"
},
"wiki": "https://wiki.openstack.org/wiki/Cinder/certified-drivers",
"os_versions": [
{
"os_version": "Icehouse",
"verification": "self_verification",
"success": true
}
]
},
{
"project_id": "openstack/neutron",
"vendor": "Arista",
"name": "Neutron ML2 Driver",
"description": "Arista ML2 Mechanism Driver implement ML2 Plugin Mechanism Driver API. This driver can manage all types of Arista switches.",
"maintainer": {
"name": "Sukhdev Kapur",
"email": "arista-openstack-test@aristanetworks.com",
"irc": "Sukhdev"
},
"wiki": "https://wiki.openstack.org/wiki/Arista-neutron-ml2-driver",
"os_versions": [
{
"verification": "external_ci_verification",
"ci_id": "arista-test"
}
]
},
{
"project_id": "openstack/neutron",
"vendor": "Big Switch",
"name": "Neutron Floodlight Plugin",
"description": "BigSwitch RestProxy Neutron plugin with the open source Floodlight controller",
"maintainer": {
"name": "Kevin Benton",
"email": "openstack-ci@bigswitch.com",
"irc": "kevinbenton"
},
"wiki": "https://wiki.openstack.org/wiki/Neutron/FloodlightPluginSetup",
"os_versions": [
{
"verification": "external_ci_verification",
"ci_id": "bsn"
}
]
},
{
"project_id": "openstack/neutron",
"vendor": "Cisco",
"name": "Neutron ML2 Driver For Cisco Nexus Devices",
"description": "The Cisco Nexus ML2 Mechanism Driver implements the ML2 Plugin Mechanism Driver API. This driver manages multiple types of Cisco Nexus switches.",
"maintainer": {
"name": "Kyle Mestery",
"email": "cisco-openstack-neutron-ci@cisco.com",
"irc": "mestery"
},
"wiki": "https://wiki.openstack.org/wiki/Neutron/ML2/MechCiscoNexus",
"os_versions": [
{
"verification": "external_ci_verification",
"ci_id": "cisco_neutron_ci"
}
]
},
{
"project_id": "openstack/neutron",
"vendor": "Mellanox",
"name": "Neutron Plugin",
"description": "Mellanox supports the OpenStack Neutron releases with open source networking components. It delivers higher compute and storage performance and additional functionality, such as NIC based switching to provide better security and isolation for virtual cloud environments. ",
"maintainer": {
"name": "Mellanox External Testing",
"email": "mlnx-openstack-ci@dev.mellanox.co.il"
},
"wiki": "https://wiki.openstack.org/wiki/Mellanox-Neutron",
"os_versions": [
{
"verification": "external_ci_verification",
"ci_id": "mellanox"
}
]
},
{
"project_id": "openstack/neutron",
"vendor": "Midokura",
"name": "Neutron Plugin",
"description": "",
"maintainer": {
"name": "Lucas Eznarriaga",
"email": "lucas@midokura.com",
"irc": "luqas"
},
"os_versions": [
{
"verification": "external_ci_verification",
"ci_id": "midokura"
}
]
},
{
"project_id": "openstack/neutron",
"vendor": "Nuage",
"name": "Neutron Plugin",
"description": "",
"maintainer": {
"name": "Ronak Shah",
"email": "nuage-ci@nuagenetworks.net",
"irc": "rms_13"
},
"os_versions": [
{
"verification": "external_ci_verification",
"ci_id": "nuage-ci"
}
]
},
{
"project_id": "openstack/neutron",
"vendor": "One Convergence",
"name": "Neutron Plugin",
"description": "",
"maintainer": {
"name": "One Convergence CI",
"email": "oc-neutron-test@oneconvergence.com"
},
"os_versions": [
{
"verification": "external_ci_verification",
"ci_id": "oneconvergence"
}
]
},
{
"project_id": "openstack/neutron",
"vendor": "PLUMgrid",
"name": "Neutron Plugin",
"description": "PLUMgrid Neutron Plugin v2 for PLUMgrid IO Visor",
"maintainer": {
"name": "Edgar Magana",
"email": "plumgrid-ci-os@plumgrid.com",
"irc": "emagana"
},
"wiki": "https://wiki.openstack.org/wiki/PLUMgrid-Neutron",
"os_versions": [
{
"verification": "external_ci_verification",
"ci_id": "plumgrid-ci"
}
]
},
{
"project_id": "openstack/neutron",
"vendor": "vArmour",
"name": "Neutron Plugin",
"description": "L3 agent and FWaaS driver for vArmour Firewall",
"maintainer": {
"name": "Gary Duan",
"email": "openstack-ci-test@varmour.com",
"irc": "garyduan"
},
"wiki": "https://wiki.openstack.org/wiki/Neutron/vArmour-Firewall",
"os_versions": [
{
"verification": "external_ci_verification",
"ci_id": "varmourci"
}
]
},
{
"project_id": "openstack/sahara",
"vendor": "OpenStack Community",
"name": "Vanilla Driver",
"description": "Vanilla driver for OpenStack Sahara",
"maintainer": {
"name": "Sergey Lukjanov",
"email": "slukjanov@mirantis.com"
},
"wiki": "https://wiki.openstack.org/wiki/Sahara",
"os_versions": [
{
"os_version": "Havana",
"verification": "self_verification",
"success": true
},
{
"os_version": "Icehouse",
"verification": "3rd_party_verification",
"verifiers": [
{
"name": "Yaroslav Lobankov",
"email": "ylobankov@mirantis.com"
}
],
"success": false
},
{
"verification": "external_ci_verification",
"ci_id": "savanna-ci"
}
]
}
]
}

View File

@ -0,0 +1,133 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"required": ["projects", "drivers"],
"properties": {
"projects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z\\d\\./-]+$"
},
"name": {
"type": "string"
}
},
"required": ["id", "name"],
"additionalProperties": false
}
},
"drivers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"project_id": {
"type": "string"
},
"vendor": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"maintainer": {
"$ref": "#/definitions/user"
},
"wiki": {
"type": "string"
},
"os_versions": {
"type": "array",
"items": {
"type": "object",
"oneOf": [
{ "$ref": "#/definitions/self_verification" },
{ "$ref": "#/definitions/3rd_party_verification" },
{ "$ref": "#/definitions/external_ci_verification" }
]
}
}
},
"required": ["project_id", "vendor", "name", "os_versions", "maintainer"],
"additionalProperties": false
}
}
},
"definitions": {
"user": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string",
"pattern": "^[a-z\\d_\\.-]+@([a-z\\d\\.-]+\\.)+[a-z]+$"
},
"irc": {
"type": "string"
},
"launchpad_id": {
"type": "string"
}
},
"required": ["name"],
"additionalProperties": false
},
"self_verification": {
"properties": {
"verification": {
"enum": ["self_verification"]
},
"os_version": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": ["verification", "os_version", "success"],
"additionalProperties": false
},
"3rd_party_verification": {
"properties": {
"verification": {
"enum": ["3rd_party_verification"]
},
"os_version": {
"type": "string"
},
"success": {
"type": "boolean"
},
"verifiers": {
"type": "array",
"items": {
"$ref": "#/definitions/user"
}
}
},
"required": ["verification", "os_version", "success", "verifiers"],
"additionalProperties": false
},
"external_ci_verification": {
"properties": {
"verification": {
"enum": ["external_ci_verification"]
},
"ci_id": {
"type": "string"
}
},
"required": ["verification", "ci_id"],
"additionalProperties": false
}
}
}

25
etc/driverlog.conf Normal file
View File

@ -0,0 +1,25 @@
[DEFAULT]
# Default data
# default_data_uri =
# Hostname where dashboard listens on
# listen_host = 127.0.0.1
# Port where dashboard listens on
# listen_port = 8080
# Runtime storage URI
# runtime_storage_uri = memcached://127.0.0.1:11211
# URI of review system
# review_uri = gerrit://review.openstack.org
# SSH key for gerrit review system access
# ssh_key_filename = /home/user/.ssh/id_rsa
# SSH username for gerrit review system access
# ssh_username = user
# Forcibly read default data and update records
# force_update = False

13
requirements.txt Normal file
View File

@ -0,0 +1,13 @@
pbr>=0.6,<1.0
Flask>=0.10,<1.0
Flask-Gravatar
iso8601>=0.1.8
oslo.config>=1.2.0
paramiko>=1.8.0
# psutil>=1.1.1
# PyGithub
python-memcached>=1.48
# PyYAML>=3.1.0
# sh
six>=1.5.2

35
setup.cfg Normal file
View File

@ -0,0 +1,35 @@
[metadata]
name = driverlog
version = 0.0.1
summary = DriverLog | Vendor Drivers for OpenStack
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Environment :: OpenStack
Intended Audience :: Developers
Intended Audience :: Information Technology
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
[files]
packages =
driverlog
data_files =
etc/driverlog =
etc/driverlog.conf
etc/default_data.json
[global]
setup-hooks =
pbr.hooks.setup_hook
[entry_points]
console_scripts =
driverlog-dashboard = driverlog.web:main
driverlog-processor = driverlog.processor.main:main

21
setup.py Normal file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import setuptools
setuptools.setup(
setup_requires=['pbr'],
pbr=True)

13
test-requirements.txt Normal file
View File

@ -0,0 +1,13 @@
hacking>=0.8.0,<0.9
coverage>=3.6
discover
# docutils==0.9.1
fixtures>=0.3.14
jsonschema>=2.0.0,<3.0.0
mock>=1.0
python-subunit
# sphinx>=1.1.2,<1.2
# sphinxcontrib-httpdomain
testrepository>=0.0.17
testtools>=0.9.32

1
tests/__init__.py Normal file
View File

@ -0,0 +1 @@
__author__ = 'ilyashakhat'

1
tests/unit/__init__.py Normal file
View File

@ -0,0 +1 @@
__author__ = 'ishakhat'

View File

@ -0,0 +1,79 @@
# Copyright (c) 2013 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import jsonschema
import testtools
def _compare_drivers(x, y):
if x['project_id'] != y['project_id']:
return ((x['project_id'] > y['project_id']) -
(x['project_id'] < y['project_id']))
if x['vendor'] != y['vendor']:
return (x['vendor'] > y['vendor']) - (x['vendor'] < y['vendor'])
return (x['name'] > y['name']) - (x['name'] < y['name'])
class TestConfigFiles(testtools.TestCase):
def setUp(self):
super(TestConfigFiles, self).setUp()
def _read_file(self, file_name):
with open(file_name, 'r') as content_file:
content = content_file.read()
return json.loads(content)
def _verify_ordering(self, array,
comparator=lambda x, y: (x > y) - (x < y), msg=''):
diff_msg = ''
for i in range(len(array) - 1):
if comparator(array[i], array[i + 1]) > 0:
diff_msg = ('Order fails at index %(index)s, '
'elements:\n%(first)s:\n%(second)s' %
{'index': i, 'first': array[i],
'second': array[i + 1]})
break
if diff_msg:
self.fail(msg + '\n' + diff_msg)
def _verify_default_data_by_schema(self, file_name):
default_data = self._read_file(file_name)
schema = self._read_file('etc/default_data.schema.json')
try:
jsonschema.validate(default_data, schema)
except Exception as e:
self.fail(e)
def test_default_data_schema_conformance(self):
self._verify_default_data_by_schema('etc/default_data.json')
def test_projects_in_alphabetical_order(self):
projects = self._read_file('etc/default_data.json')['projects']
self._verify_ordering(
projects,
comparator=lambda x, y: (x['id'] > y['id']) - (x['id'] < y['id']),
msg='List of projects should be ordered by their ids')
def test_drivers_in_alphabetical_order(self):
drivers = self._read_file('etc/default_data.json')['drivers']
self._verify_ordering(
drivers,
comparator=_compare_drivers,
msg='List of drivers should be ordered by project_id, vendor '
'and name')

40
tox.ini Normal file
View File

@ -0,0 +1,40 @@
[tox]
envlist = py27,pep8
minversion = 1.6
skipsdist = True
[testenv]
usedevelop = True
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python setup.py testr --testr-args='{posargs}'
distribute = false
[testenv:pep8]
commands = flake8
distribute = false
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py testr --coverage --testr-args='{posargs}'
[tox:jenkins]
downloadcache = ~/cache/pip
[testenv:docs]
commands = python setup.py build_sphinx
[flake8]
# E125 continuation line does not distinguish itself from next logical line
# H404 multi line docstring should start with a summary
ignore = E125,H404
show-source = true
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools,build