DriverLog initial commit
4
.gitignore
vendored
@ -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
@ -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
@ -0,0 +1,9 @@
|
||||
include AUTHORS
|
||||
include README.rst
|
||||
include ChangeLog
|
||||
include LICENSE
|
||||
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
7
README.rst
Normal 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
0
driverlog/dashboard/__init__.py
Normal file
93
driverlog/dashboard/api.py
Normal 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()
|
156
driverlog/dashboard/decorators.py
Normal 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
|
32
driverlog/dashboard/helpers.py
Normal 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 = '∞'
|
||||
|
||||
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')
|
154
driverlog/dashboard/memory_storage.py
Normal 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)
|
43
driverlog/dashboard/parameters.py
Normal 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 ''
|
252
driverlog/dashboard/static/css/jquery.dataTables.css
Normal 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;
|
||||
}
|
||||
|
1
driverlog/dashboard/static/css/jquery.jqplot.min.css
vendored
Normal 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);}
|
681
driverlog/dashboard/static/css/select2.css
Normal 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;
|
||||
}
|
||||
}
|
417
driverlog/dashboard/static/css/style.css
Normal 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;
|
||||
}
|
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 418 B |
After Width: | Height: | Size: 312 B |
After Width: | Height: | Size: 205 B |
After Width: | Height: | Size: 262 B |
After Width: | Height: | Size: 348 B |
After Width: | Height: | Size: 207 B |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 278 B |
After Width: | Height: | Size: 328 B |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 6.2 KiB |
7
driverlog/dashboard/static/css/ui-lightness/jquery-ui-1.10.4.custom.min.css
vendored
Normal file
BIN
driverlog/dashboard/static/images/back_disabled.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
driverlog/dashboard/static/images/back_enabled.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
driverlog/dashboard/static/images/back_enabled_hover.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
driverlog/dashboard/static/images/calendar.gif
Normal file
After Width: | Height: | Size: 269 B |
BIN
driverlog/dashboard/static/images/favicon.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
driverlog/dashboard/static/images/footer_tile.jpg
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
driverlog/dashboard/static/images/forward_disabled.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
driverlog/dashboard/static/images/forward_enabled.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
driverlog/dashboard/static/images/forward_enabled_hover.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
driverlog/dashboard/static/images/noise_lightblue.jpg
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
driverlog/dashboard/static/images/noise_lightgray.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
driverlog/dashboard/static/images/select2-spinner.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
driverlog/dashboard/static/images/select2.png
Normal file
After Width: | Height: | Size: 613 B |
BIN
driverlog/dashboard/static/images/select2x2.png
Normal file
After Width: | Height: | Size: 845 B |
BIN
driverlog/dashboard/static/images/sort_asc.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
driverlog/dashboard/static/images/sort_asc_disabled.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
driverlog/dashboard/static/images/sort_both.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
driverlog/dashboard/static/images/sort_desc.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
driverlog/dashboard/static/images/sort_desc_disabled.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
279
driverlog/dashboard/static/js/driverlog-ui.js
Normal 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;'>✔</span>"
|
||||
} else {
|
||||
result = "<span style='color: red;'>✘</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();
|
||||
})
|
||||
});
|
||||
}
|
3
driverlog/dashboard/static/js/excanvas.min.js
vendored
Normal file
3
driverlog/dashboard/static/js/jqplot.barRenderer.min.js
vendored
Normal file
3
driverlog/dashboard/static/js/jqplot.bubbleRenderer.min.js
vendored
Normal file
3
driverlog/dashboard/static/js/jqplot.canvasAxisLabelRenderer.min.js
vendored
Normal 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);
|
3
driverlog/dashboard/static/js/jqplot.canvasAxisTickRenderer.min.js
vendored
Normal 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);
|
3
driverlog/dashboard/static/js/jqplot.canvasTextRenderer.min.js
vendored
Normal file
3
driverlog/dashboard/static/js/jqplot.categoryAxisRenderer.min.js
vendored
Normal file
3
driverlog/dashboard/static/js/jqplot.cursor.min.js
vendored
Normal file
3
driverlog/dashboard/static/js/jqplot.dateAxisRenderer.min.js
vendored
Normal file
3
driverlog/dashboard/static/js/jqplot.highlighter.min.js
vendored
Normal file
3
driverlog/dashboard/static/js/jqplot.json2.min.js
vendored
Normal 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);
|
3
driverlog/dashboard/static/js/jqplot.pieRenderer.min.js
vendored
Normal file
5
driverlog/dashboard/static/js/jquery-1.9.1.min.js
vendored
Normal file
7
driverlog/dashboard/static/js/jquery-ui-1.10.4.custom.min.js
vendored
Normal file
155
driverlog/dashboard/static/js/jquery.dataTables.min.js
vendored
Normal 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('"',"""));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);
|
3
driverlog/dashboard/static/js/jquery.jqplot.min.js
vendored
Normal file
549
driverlog/dashboard/static/js/jquery.tmpl.js
Normal 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("<").split(">").join(">").split('"').join(""").split("'").join("'");
|
||||
}
|
||||
});
|
||||
|
||||
$.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);
|
22
driverlog/dashboard/static/js/select2.min.js
vendored
Normal file
12
driverlog/dashboard/templates/404.html
Normal 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 %}
|
17
driverlog/dashboard/templates/base.html
Normal 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>
|
107
driverlog/dashboard/templates/details.html
Normal 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') }}">⌂</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">✔</span>
|
||||
{% elif version.success == False %}
|
||||
<span style="color: red">✖</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": ["<enter your name here>"],
|
||||
"success": true
|
||||
}
|
||||
</pre>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
57
driverlog/dashboard/templates/layout.html
Normal 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 %}
|
139
driverlog/dashboard/templates/summary.html
Normal 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 %}
|
166
driverlog/dashboard/vault.py
Normal 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
@ -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()
|
0
driverlog/openstack/__init__.py
Normal file
0
driverlog/openstack/common/__init__.py
Normal file
68
driverlog/openstack/common/importutils.py
Normal 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
|
169
driverlog/openstack/common/jsonutils.py
Normal 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__)
|
557
driverlog/openstack/common/log.py
Normal 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))
|
210
driverlog/openstack/common/timeutils.py
Normal 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
|
1
driverlog/processor/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
__author__ = 'ishakhat'
|
36
driverlog/processor/config.py
Normal 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
@ -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
@ -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)
|
173
driverlog/processor/utils.py
Normal 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​', 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
@ -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
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
133
etc/default_data.schema.json
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1 @@
|
||||
__author__ = 'ilyashakhat'
|
1
tests/unit/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
__author__ = 'ishakhat'
|
79
tests/unit/test_config_files.py
Normal 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
@ -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
|