All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 01/94] webhob: create main WEBHOB project
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 02/94] webhob: add system start/stop script Alex DAMIAN
                   ` (93 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This creates a basic Django project where the WEBHOB code will live.
The main project had to be renamed from webhob to whbmain in order to
avoid any import issues that might occur.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/__init__.py         |   0
 bitbake/lib/webhob/manage.py           |  10 +++
 bitbake/lib/webhob/whbmain/__init__.py |   0
 bitbake/lib/webhob/whbmain/settings.py | 155 +++++++++++++++++++++++++++++++++
 bitbake/lib/webhob/whbmain/urls.py     |  17 ++++
 bitbake/lib/webhob/whbmain/wsgi.py     |  32 +++++++
 6 files changed, 214 insertions(+)
 create mode 100644 bitbake/lib/webhob/__init__.py
 create mode 100755 bitbake/lib/webhob/manage.py
 create mode 100644 bitbake/lib/webhob/whbmain/__init__.py
 create mode 100644 bitbake/lib/webhob/whbmain/settings.py
 create mode 100644 bitbake/lib/webhob/whbmain/urls.py
 create mode 100644 bitbake/lib/webhob/whbmain/wsgi.py

diff --git a/bitbake/lib/webhob/__init__.py b/bitbake/lib/webhob/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bitbake/lib/webhob/manage.py b/bitbake/lib/webhob/manage.py
new file mode 100755
index 0000000..1b2c1a7
--- /dev/null
+++ b/bitbake/lib/webhob/manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "whbmain.settings")
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)
diff --git a/bitbake/lib/webhob/whbmain/__init__.py b/bitbake/lib/webhob/whbmain/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bitbake/lib/webhob/whbmain/settings.py b/bitbake/lib/webhob/whbmain/settings.py
new file mode 100644
index 0000000..67aaf69
--- /dev/null
+++ b/bitbake/lib/webhob/whbmain/settings.py
@@ -0,0 +1,155 @@
+# Django settings for webhob project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+    # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': '',                      # Or path to database file if using sqlite3.
+        'USER': '',
+        'PASSWORD': '',
+        'HOST': '127.0.0.1',                      # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
+        'PORT': '3306',                      # Set to empty string for default.
+    }
+}
+
+# Hosts/domain names that are valid for this site; required if DEBUG is False
+# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
+ALLOWED_HOSTS = []
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# In a Windows environment this must be set to your system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale.
+USE_L10N = True
+
+# If you set this to False, Django will not use timezone-aware datetimes.
+USE_TZ = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/var/www/example.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://example.com/media/", "http://media.example.com/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/var/www/example.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://example.com/static/", "http://static.example.com/"
+STATIC_URL = '/static/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+    # Put strings here, like "/home/html/static" or "C:/www/django/static".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+    'django.contrib.staticfiles.finders.FileSystemFinder',
+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'fy@5+5w9zn5wa0vsegn1c8p_6w&pa_ms-4t0cna1vle79nzlkn'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.Loader',
+    'django.template.loaders.app_directories.Loader',
+#     'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    # Uncomment the next line for simple clickjacking protection:
+    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+)
+
+ROOT_URLCONF = 'whbmain.urls'
+
+# Python dotted path to the WSGI application used by Django's runserver.
+WSGI_APPLICATION = 'whbmain.wsgi.application'
+
+TEMPLATE_DIRS = (
+    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+)
+
+INSTALLED_APPS = (
+    #'django.contrib.auth',
+    #'django.contrib.contenttypes',
+    #'django.contrib.sessions',
+    #'django.contrib.sites',
+    #'django.contrib.messages',
+    #'django.contrib.staticfiles',
+    # Uncomment the next line to enable the admin:
+    # 'django.contrib.admin',
+    # Uncomment the next line to enable admin documentation:
+    # 'django.contrib.admindocs',
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error when DEBUG=False.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'filters': {
+        'require_debug_false': {
+            '()': 'django.utils.log.RequireDebugFalse'
+        }
+    },
+    'handlers': {
+        'mail_admins': {
+            'level': 'ERROR',
+            'filters': ['require_debug_false'],
+            'class': 'django.utils.log.AdminEmailHandler'
+        }
+    },
+    'loggers': {
+        'django.request': {
+            'handlers': ['mail_admins'],
+            'level': 'ERROR',
+            'propagate': True,
+        },
+    }
+}
diff --git a/bitbake/lib/webhob/whbmain/urls.py b/bitbake/lib/webhob/whbmain/urls.py
new file mode 100644
index 0000000..04e0352
--- /dev/null
+++ b/bitbake/lib/webhob/whbmain/urls.py
@@ -0,0 +1,17 @@
+from django.conf.urls import patterns, include, url
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+    # Examples:
+    # url(r'^$', 'webhob.views.home', name='home'),
+    # url(r'^webhob/', include('webhob.foo.urls')),
+
+    # Uncomment the admin/doc line below to enable admin documentation:
+    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+    # Uncomment the next line to enable the admin:
+    # url(r'^admin/', include(admin.site.urls)),
+)
diff --git a/bitbake/lib/webhob/whbmain/wsgi.py b/bitbake/lib/webhob/whbmain/wsgi.py
new file mode 100644
index 0000000..9595eda
--- /dev/null
+++ b/bitbake/lib/webhob/whbmain/wsgi.py
@@ -0,0 +1,32 @@
+"""
+WSGI config for webhob project.
+
+This module contains the WSGI application used by Django's development server
+and any production WSGI deployments. It should expose a module-level variable
+named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
+this application via the ``WSGI_APPLICATION`` setting.
+
+Usually you will have the standard Django WSGI application here, but it also
+might make sense to replace the whole Django WSGI application with a custom one
+that later delegates to the Django one. For example, you could introduce WSGI
+middleware here, or combine a Django application with an application of another
+framework.
+
+"""
+import os
+
+# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
+# if running multiple sites in the same mod_wsgi process. To fix this, use
+# mod_wsgi daemon mode with each site in its own daemon process, or use
+# os.environ["DJANGO_SETTINGS_MODULE"] = "webhob.settings"
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wbhmain.settings")
+
+# This application object is used by any WSGI server configured to use this
+# file. This includes Django's development server, if the WSGI_APPLICATION
+# setting points here.
+from django.core.wsgi import get_wsgi_application
+application = get_wsgi_application()
+
+# Apply WSGI middleware here.
+# from helloworld.wsgi import HelloWorldApplication
+# application = HelloWorldApplication(application)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 02/94] webhob: add system start/stop script
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 01/94] webhob: create main WEBHOB project Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 03/94] webhob: create Django models for webhob Alex DAMIAN
                   ` (92 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding a script that starts/stops the webhob system.
Sourced, with a parameter, it will start or stop
the bitbake resident, and the webhob server, as well
as point a browser to the webhob home .

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/bin/webhob | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 95 insertions(+)
 create mode 100755 bitbake/bin/webhob

diff --git a/bitbake/bin/webhob b/bitbake/bin/webhob
new file mode 100755
index 0000000..ccaf91f
--- /dev/null
+++ b/bitbake/bin/webhob
@@ -0,0 +1,95 @@
+#!/bin/bash
+# (c) 2013 Intel Corp.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+# This script enables webhob event logging and
+# starts bitbake resident server
+# use as:  source webhob [start|stop]
+
+# Helper function to kill a background webhob development server
+        
+function webserverKillAllComponents() 
+{
+	local pidfile
+	for pidfile in ${BUILDDIR}/whbmain.pid; do
+		if [ -f ${pidfile} ]; then
+		while kill -0 $(< ${pidfile}) 2>/dev/null; do
+			kill -SIGTERM -`ps -p $(< ${pidfile}) -o "%r" --no-headers`	
+			sleep 1;
+		done;
+		rm  ${pidfile}
+		fi
+	done
+}
+
+# We make sure we're running in the current shell and in a good environment
+
+if [ -z "$ZSH_NAME" ] && [ `basename "$0"` = `basename $BASH_SOURCE` ]; then
+    echo "Error: This script needs to be sourced. Please run as 'source webhob [start|stop]'" 1>&2;
+    exit 1
+fi
+    
+if [ -z "$BUILDDIR" ] || [ -z `which bitbake` ]; then
+    echo "Error: Build environment is not setup or bitbake is not in path." 1>&2;
+    return 2
+fi
+
+BBBASEDIR=`dirname ${BASH_SOURCE}`/..
+
+# Determine the action. If specified by arguments, fine, if not, toggle it
+if [ "x$1" == "xstart" ] || [ "x$1" == "xstop" ]; then
+    CMD="$1"
+else
+    if [ -z "$BBSERVER" ]; then
+        CMD="start"
+    else
+        CMD="stop"
+    fi;
+fi
+
+# Make sure it's safe to run by checking bitbake lock
+
+lock=1
+(flock -n 200 ) 200<$BUILDDIR/bitbake.lock || lock=0 
+
+if [ ${CMD} == "start" ] && ( [ $lock -eq 0 ] || [ -e $BUILDDIR/whbmain.pid ] ); then
+    echo "Error: bitbake lock state error. System may be already on." 2>&1
+    return 3 
+elif [ ${CMD} == "stop" ] && ( [ $lock -eq 1 ] || ! [ -e $BUILDDIR/whbmain.pid ] ) ; then
+    echo "Error: bitbake lock state error. System may be already off.
+manually stop system with bitbake -m / webserverKillAllComponents" 2>&1
+    return 3 
+fi
+
+
+# Execute the commands
+
+case $CMD in
+    start )
+        unset BBSERVER
+        python $BBBASEDIR/lib/webhob/manage.py syncdb || (echo "Failed db sync, stopping system start" 1>&2 && return 0)
+        bitbake --server-only -t xmlrpc -B localhost:8200 
+        export BBSERVER=localhost:8200
+        python $BBBASEDIR/lib/webhob/manage.py runserver </dev/null >${BUILDDIR}/whbmain.log 2>&1 & echo $! >${BUILDDIR}/whbmain.pid
+    ;;
+    stop )
+        bitbake -m
+        unset BBSERVER
+        webserverKillAllComponents
+esac
+
+    
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 03/94] webhob: create Django models for webhob
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 01/94] webhob: create main WEBHOB project Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 02/94] webhob: add system start/stop script Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 04/94] bitbake: create Data Store Interface (DSI) file Alex DAMIAN
                   ` (91 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

Patch adds the models we need in order to store all the information
we need about the builds in the database using the Django ORM.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/orm/__init__.py     |   0
 bitbake/lib/webhob/orm/models.py       | 162 +++++++++++++++++++++++++++++++++
 bitbake/lib/webhob/orm/tests.py        |  16 ++++
 bitbake/lib/webhob/orm/views.py        |   1 +
 bitbake/lib/webhob/whbmain/settings.py |   1 +
 5 files changed, 180 insertions(+)
 create mode 100644 bitbake/lib/webhob/orm/__init__.py
 create mode 100644 bitbake/lib/webhob/orm/models.py
 create mode 100644 bitbake/lib/webhob/orm/tests.py
 create mode 100644 bitbake/lib/webhob/orm/views.py

diff --git a/bitbake/lib/webhob/orm/__init__.py b/bitbake/lib/webhob/orm/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
new file mode 100644
index 0000000..43b4319
--- /dev/null
+++ b/bitbake/lib/webhob/orm/models.py
@@ -0,0 +1,162 @@
+from django.db import models
+
+
+class Builds(models.Model):
+
+    BUILD_OUTCOME = (
+        (0, 'Succeded'),
+        (1, 'Failed'),
+    )
+
+    uuid = models.CharField(max_length=100, unique=True)
+    target = models.CharField(max_length=100)
+    host_machine = models.CharField(max_length=100)
+    distro = models.CharField(max_length=100)
+    distro_version = models.CharField(max_length=100)
+    host_system = models.CharField(max_length=100)
+    host_distribution = models.CharField(max_length=100)
+    target_system = models.ForeignKey('Machines', related_name='builds_machines')
+    layers = models.ManyToManyField('Layers')
+    started_on = models.DateTimeField()
+    completed_on = models.DateTimeField()
+    outcome = models.IntegerField(choices=BUILD_OUTCOME)
+    errors_no = models.IntegerField()
+    warnings_no = models.IntegerField()
+    cpu_usage = models.IntegerField()
+    disk_io = models.DecimalField(max_digits=20, decimal_places=10)
+    cooker_log_path = models.CharField(max_length=500)
+    build_name = models.CharField(max_length=100)
+    bitbake_version = models.CharField(max_length=50)
+
+
+class Tasks(models.Model):
+
+    SSTATE_RESULT = (
+        (0, 'Not Applicable'), # For rest of tasks, but they still need checking.
+        (1, 'Unavailable'), # it is a miss
+        (2, 'Failed'), # there was a pkg, but the script failed
+        (3, 'Restored'), # succesfully restored
+    )
+
+    TASK_CODING = (
+        (0, 'Python'),
+        (1, 'Shell'),
+    )
+
+    TASK_OUTCOME = (
+        (0, 'Covered'),
+        (1, 'Sstate'),
+        (2, 'Existing'),
+        (3, 'Succeded'),
+        (4, 'Failed'),
+    )
+
+    uuid = models.ForeignKey(Builds, to_field='uuid', related_name='tasks_builds')
+    order = models.IntegerField()
+    task_executed = models.BooleanField() # True means Executed, False means Prebuilt
+    outcome = models.IntegerField(choices=TASK_OUTCOME)
+    sstate_checksum = models.CharField(max_length=100)
+    path_to_sstate_obj = models.FilePathField(max_length=500, blank=True)
+    recipe = models.ForeignKey('Recipes', related_name='builds_recipes')
+    task_name = models.CharField(max_length=100)
+    source_url = models.FilePathField(max_length=200)
+    log_file = models.FilePathField(max_length=200, blank=True)
+    work_directory = models.FilePathField(max_length=200)
+    script_type = models.IntegerField(choices=TASK_CODING)
+    file_path = models.FilePathField(max_length=200)
+    line_number = models.IntegerField()
+    py_stack_trace = models.TextField()
+    disk_io = models.DecimalField(max_digits=20, decimal_places=10)
+    cpu_usage = models.IntegerField()
+    elapsed_time = models.CharField(max_length=50)
+    errors_no = models.IntegerField()
+    warnings_no = models.IntegerField()
+    error = models.TextField()
+    warning = models.TextField()
+    sstate_result = models.IntegerField(choices=SSTATE_RESULT)
+    diffsigs = models.CharField(max_length=500)
+
+
+class Task_Dependencies(models.Model):
+    task = models.ForeignKey(Tasks, related_name='task_dependencies_task')
+    depends_on = models.ForeignKey(Tasks, related_name='task_dependencies_depends')
+
+
+class Targets(models.Model):
+    uuid = models.ForeignKey(Builds, to_field='uuid',  related_name='targets_builds')
+    is_image = models.BooleanField()
+
+
+class Artifacts(models.Model):
+    uuid = models.ForeignKey(Builds, to_field='uuid', related_name='artifacts_builds')
+    target = models.ForeignKey(Targets, related_name='artifacts_targets')
+    file_name = models.CharField(max_length=100)
+    file_size = models.IntegerField()
+
+
+class Packages_In_Images(models.Model):
+    package = models.ForeignKey('Packages', related_name='packages_in_images_package')
+    target = models.ForeignKey(Targets, related_name='packages_in_images_target')
+
+
+class Packages(models.Model):
+    package_id = models.IntegerField()
+    recipe = models.ForeignKey('Recipes', related_name='packages_recipes')
+    name = models.CharField(max_length=100)
+    version = models.CharField(max_length=100)
+    size = models.IntegerField()
+
+
+class Package_Dependencies(models.Model):
+    package = models.ForeignKey(Packages, related_name='package_dependencies_package')
+    depends_on = models.ForeignKey(Packages, related_name='package_dependencies_depends')
+
+
+class Filelist(models.Model):
+    package = models.ForeignKey(Packages, related_name='filelist_packages')
+    complete_file_path = models.FilePathField(max_length=200, blank=True)
+    file_size = models.IntegerField()
+
+
+class Recipes(models.Model):
+    name = models.CharField(max_length=100)
+    version = models.CharField(max_length=100)
+    layer = models.ForeignKey('Layers', related_name='recipes_layers')
+    summary = models.CharField(max_length=100)
+    description = models.CharField(max_length=100)
+    section = models.CharField(max_length=100)
+    license = models.CharField(max_length=200)
+    licensing_info = models.TextField()
+    homepage = models.URLField()
+    bugtracker = models.URLField()
+    author = models.CharField(max_length=100)
+    file_path = models.FilePathField(max_length=200)
+
+
+class Recipe_Dependencies(models.Model):
+    recipe = models.ForeignKey(Recipes, related_name='recipe_dependencies_recipe')
+    depends_on = models.ForeignKey(Recipes, related_name='recipe_dependencies_depends')
+
+
+class Layers(models.Model):
+    name = models.CharField(max_length=100)
+    branch = models.CharField(max_length=50)
+    commit = models.CharField(max_length=100)
+    priority = models.IntegerField()
+    link_to_oe_core = models.URLField()
+
+
+class Variables(models.Model):
+    uuid = models.ForeignKey(Builds, to_field='uuid', related_name='variables_builds')
+    variable_name = models.CharField(max_length=100)
+    variable_value = models.TextField()
+    file = models.FilePathField(max_length=200)
+    changed = models.BooleanField()
+    human_readable_name = models.CharField(max_length=200)
+    description = models.TextField()
+
+
+class Machines(models.Model):
+    name = models.CharField(max_length=100)
+    description = models.TextField()
+
diff --git a/bitbake/lib/webhob/orm/tests.py b/bitbake/lib/webhob/orm/tests.py
new file mode 100644
index 0000000..501deb7
--- /dev/null
+++ b/bitbake/lib/webhob/orm/tests.py
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.assertEqual(1 + 1, 2)
diff --git a/bitbake/lib/webhob/orm/views.py b/bitbake/lib/webhob/orm/views.py
new file mode 100644
index 0000000..60f00ef
--- /dev/null
+++ b/bitbake/lib/webhob/orm/views.py
@@ -0,0 +1 @@
+# Create your views here.
diff --git a/bitbake/lib/webhob/whbmain/settings.py b/bitbake/lib/webhob/whbmain/settings.py
index 67aaf69..71bba4f 100644
--- a/bitbake/lib/webhob/whbmain/settings.py
+++ b/bitbake/lib/webhob/whbmain/settings.py
@@ -123,6 +123,7 @@ INSTALLED_APPS = (
     # 'django.contrib.admin',
     # Uncomment the next line to enable admin documentation:
     # 'django.contrib.admindocs',
+    'orm',
 )
 
 # A sample logging configuration. The only tangible logging
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 04/94] bitbake: create Data Store Interface (DSI) file
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (2 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 03/94] webhob: create Django models for webhob Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 05/94] bitbake: webhob: updates to Django models Alex DAMIAN
                   ` (90 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

The patch adds the first version of the DSI file. It uses the Knotty
code for now and inserts task related information into the database
using the Django ORM.
It makes use of some test objects needed to create a valid record into
the database based on the definition of the Django models.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/dsi.py | 667 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 667 insertions(+)
 create mode 100644 bitbake/lib/bb/ui/dsi.py

diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
new file mode 100644
index 0000000..f832806
--- /dev/null
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -0,0 +1,667 @@
+#
+# BitBake DSI Implementation
+#
+# Copyright (C) 2013        Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program 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
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import division
+
+import os
+import sys
+import xmlrpclib
+import logging
+import progressbar
+import signal
+import bb.msg
+import time
+import fcntl
+import struct
+import copy
+import datetime
+
+from bb.ui import uihelper
+
+logger = logging.getLogger("BitBake")
+interactive = sys.stdout.isatty()
+
+class WebHOBHelper(object):
+    
+    def __init__(self):
+        self.configure_django()
+        self.task_order = 0
+        self.tasks_information = {}
+
+    def configure_django(self):
+        import webhob.whbmain.settings as whb_django_settings
+        from django.core.management import setup_environ
+        setup_environ(whb_django_settings)
+        # Add webhob to sys path for importing modules
+        sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'webhob'))
+    
+    def store_build(self, uuid):
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Builds, Machines, Layers
+        machine_obj = Machines.objects.create(
+                                name='Test Machine',
+                                description='This is our great test machine'
+                                )
+        machine_obj.save()
+
+        layer_obj = Layers.objects.create(
+                                name='Test Layer',
+                                branch='master_test',
+                                commit='2148u323nsau83smdmbnca3312f',
+                                priority=2,
+                                link_to_oe_core='http://test.oecore.com'
+                                )
+        layer_obj.save()
+
+        build_obj = Builds.objects.create(
+                                uuid=uuid,
+                                target='test target',
+                                host_machine='Host machine',
+                                distro='1.234',
+                                distro_version='v2',
+                                host_system='Test hosting system',
+                                host_distribution='Test host distrib',
+                                target_system=machine_obj,
+                                started_on=datetime.datetime.now(),
+                                completed_on=datetime.datetime.now(),
+                                outcome=3,
+                                errors_no=0,
+                                warnings_no=0,
+                                cpu_usage=54,
+                                disk_io=23.3232,
+                                cooker_log_path='/home/test/cooker_test',
+                                build_name='tester_build',
+                                bitbake_version='1.0.5')
+        build_obj.layers.add(layer_obj)
+        build_obj.save()
+
+        return build_obj, layer_obj
+
+    def store_started_task(self, event, build_object):
+        self.task_order += 1
+        identifier = event._package + event._task
+        self.tasks_information[identifier] = {
+                'uuid': build_object,
+                'task_executed': True,
+                'order': self.task_order,
+                'recipe': event._package,
+                'task_name': event._task,
+                'start_time': time.time(),
+                }
+
+    def write_stored_tasks(self, event, build_object, layer_object):
+        identifier = event._package + event._task
+
+        # fix this
+        if event.getDisplayName().lower() == 'succeded':
+            outcome = 0
+        else:
+            outcome = 1
+
+        self.tasks_information[identifier].update({
+                'outcome': outcome,
+                'end_time': time.time(),
+                })
+
+        task = self.tasks_information[identifier]
+
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Tasks, Recipes
+
+        recipe_obj = Recipes.objects.create(
+                                    name=task['recipe'],
+                                    version='1.0',
+                                    layer=layer_object,
+                                    summary='Short summary',
+                                    description='Longer description',
+                                    section='24',
+                                    license='license title',
+                                    licensing_info='longer license information',
+                                    homepage='http://homepage.com',
+                                    bugtracker='2456',
+                                    author='Mr.Test',
+                                    file_path='home/test/testproject/',
+                                    )
+
+        # Store finished task
+        task_obj = Tasks.objects.create(uuid=task['uuid'],
+                                        order=task['order'],
+                                        outcome=task['outcome'],
+                                        recipe=recipe_obj,
+                                        task_name=task['task_name'],
+                                        elapsed_time=task['end_time'] - task['start_time'],
+                                        # The next lines are just for testing reasons
+                                        sstate_checksum = '2134123sdscsdf232342tgfdv',
+                                        task_executed=True,
+                                        path_to_sstate_obj='/home/calin',
+                                        source_url='/',
+                                        log_file='/',
+                                        work_directory='/',
+                                        script_type=0,
+                                        file_path='/',
+                                        line_number=55,
+                                        py_stack_trace='Testing traceback',
+                                        disk_io = 23.55633,
+                                        cpu_usage=43,
+                                        errors_no=0,
+                                        warnings_no=0,
+                                        error='None',
+                                        warning='None',
+                                        sstate_result=0,
+                                        diffsigs="Testing diffsigs")
+        task_obj.save()
+        # Clean memory
+        del self.tasks_information[identifier]
+
+
+class BBProgress(progressbar.ProgressBar):
+    def __init__(self, msg, maxval):
+        self.msg = msg
+        widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ',
+           progressbar.ETA()]
+
+        try:
+            self._resize_default = signal.getsignal(signal.SIGWINCH)
+        except:
+            self._resize_default = None
+        progressbar.ProgressBar.__init__(self, maxval, [self.msg + ": "] + widgets)
+
+    def _handle_resize(self, signum, frame):
+        progressbar.ProgressBar._handle_resize(self, signum, frame)
+        if self._resize_default:
+            self._resize_default(signum, frame)
+    def finish(self):
+        progressbar.ProgressBar.finish(self)
+        if self._resize_default:
+            signal.signal(signal.SIGWINCH, self._resize_default)
+
+class NonInteractiveProgress(object):
+    fobj = sys.stdout
+
+    def __init__(self, msg, maxval):
+        self.msg = msg
+        self.maxval = maxval
+
+    def start(self):
+        self.fobj.write("%s..." % self.msg)
+        self.fobj.flush()
+        return self
+
+    def update(self, value):
+        pass
+
+    def finish(self):
+        self.fobj.write("done.\n")
+        self.fobj.flush()
+
+def new_progress(msg, maxval):
+    if interactive:
+        return BBProgress(msg, maxval)
+    else:
+        return NonInteractiveProgress(msg, maxval)
+
+def pluralise(singular, plural, qty):
+    if(qty == 1):
+        return singular % qty
+    else:
+        return plural % qty
+
+
+class InteractConsoleLogFilter(logging.Filter):
+    def __init__(self, tf, format):
+        self.tf = tf
+        self.format = format
+
+    def filter(self, record):
+        if record.levelno == self.format.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")):
+            return False
+        self.tf.clearFooter()
+        return True
+
+class TerminalFilter(object):
+    columns = 80
+
+    def sigwinch_handle(self, signum, frame):
+        self.columns = self.getTerminalColumns()
+        if self._sigwinch_default:
+            self._sigwinch_default(signum, frame)
+
+    def getTerminalColumns(self):
+        def ioctl_GWINSZ(fd):
+            try:
+                cr = struct.unpack('hh', fcntl.ioctl(fd, self.termios.TIOCGWINSZ, '1234'))
+            except:
+                return None
+            return cr
+        cr = ioctl_GWINSZ(sys.stdout.fileno())
+        if not cr:
+            try:
+                fd = os.open(os.ctermid(), os.O_RDONLY)
+                cr = ioctl_GWINSZ(fd)
+                os.close(fd)
+            except:
+                pass
+        if not cr:
+            try:
+                cr = (env['LINES'], env['COLUMNS'])
+            except:
+                cr = (25, 80)
+        return cr[1]
+
+    def __init__(self, main, helper, console, format):
+        self.main = main
+        self.helper = helper
+        self.cuu = None
+        self.stdinbackup = None
+        self.interactive = sys.stdout.isatty()
+        self.footer_present = False
+        self.lastpids = []
+
+        if not self.interactive:
+            return
+
+        try:
+            import curses
+        except ImportError:
+            sys.exit("FATAL: The knotty ui could not load the required curses python module.")
+
+        import termios
+        self.curses = curses
+        self.termios = termios
+        try:
+            fd = sys.stdin.fileno()
+            self.stdinbackup = termios.tcgetattr(fd)
+            new = copy.deepcopy(self.stdinbackup)
+            new[3] = new[3] & ~termios.ECHO
+            termios.tcsetattr(fd, termios.TCSADRAIN, new)
+            curses.setupterm()
+            if curses.tigetnum("colors") > 2:
+                format.enable_color()
+            self.ed = curses.tigetstr("ed")
+            if self.ed:
+                self.cuu = curses.tigetstr("cuu")
+            try:
+                self._sigwinch_default = signal.getsignal(signal.SIGWINCH)
+                signal.signal(signal.SIGWINCH, self.sigwinch_handle)
+            except:
+                pass
+            self.columns = self.getTerminalColumns()
+        except:
+            self.cuu = None
+        console.addFilter(InteractConsoleLogFilter(self, format))
+
+    def clearFooter(self):
+        if self.footer_present:
+            lines = self.footer_present
+            sys.stdout.write(self.curses.tparm(self.cuu, lines))
+            sys.stdout.write(self.curses.tparm(self.ed))
+        self.footer_present = False
+
+    def updateFooter(self):
+        if not self.cuu:
+            return
+        activetasks = self.helper.running_tasks
+        failedtasks = self.helper.failed_tasks
+        runningpids = self.helper.running_pids
+        if self.footer_present and (self.lastcount == self.helper.tasknumber_current) and (self.lastpids == runningpids):
+            return
+        if self.footer_present:
+            self.clearFooter()
+        if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks):
+            return
+        tasks = []
+        for t in runningpids:
+            tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
+
+        if self.main.shutdown:
+            content = "Waiting for %s running tasks to finish:" % len(activetasks)
+        elif not len(activetasks):
+            content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total)
+        else:
+            content = "Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total)
+        print(content)
+        lines = 1 + int(len(content) / (self.columns + 1))
+        for tasknum, task in enumerate(tasks):
+            content = "%s: %s" % (tasknum, task)
+            print(content)
+            lines = lines + 1 + int(len(content) / (self.columns + 1))
+        self.footer_present = lines
+        self.lastpids = runningpids[:]
+        self.lastcount = self.helper.tasknumber_current
+
+    def finish(self):
+        if self.stdinbackup:
+            fd = sys.stdin.fileno()
+            self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
+
+def main(server, eventHandler, params, tf = TerminalFilter):
+    print "This is running in DSI mode"
+
+    # Generate an unique ID for this build
+    # TODO: for multiple build commands this might not work as expected
+    import uuid
+    uuid = sessionid = str(uuid.uuid4())
+
+    # Get values of variables which control our output
+    includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
+    if error:
+        logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
+        return 1
+    loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
+    if error:
+        logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
+        return 1
+    consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
+    if error:
+        logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
+        return 1
+
+    if sys.stdin.isatty() and sys.stdout.isatty():
+        log_exec_tty = True
+    else:
+        log_exec_tty = False
+
+    helper = uihelper.BBUIHelper()
+
+    console = logging.StreamHandler(sys.stdout)
+    format_str = "%(levelname)s: %(message)s"
+    format = bb.msg.BBLogFormatter(format_str)
+    bb.msg.addDefaultlogFilter(console)
+    console.setFormatter(format)
+    logger.addHandler(console)
+
+    if consolelogfile and not params.options.show_environment:
+        bb.utils.mkdirhier(os.path.dirname(consolelogfile))
+        conlogformat = bb.msg.BBLogFormatter(format_str)
+        consolelog = logging.FileHandler(consolelogfile)
+        bb.msg.addDefaultlogFilter(consolelog)
+        consolelog.setFormatter(conlogformat)
+        logger.addHandler(consolelog)
+
+    try:
+        params.updateFromServer(server)
+        cmdline = params.parseActions()
+        if not cmdline:
+            print("Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+            return 1
+        if 'msg' in cmdline and cmdline['msg']:
+            logger.error(cmdline['msg'])
+            return 1
+
+        ret, error = server.runCommand(cmdline['action'])
+        if error:
+            logger.error("Command '%s' failed: %s" % (cmdline, error))
+            return 1
+        elif ret != True:
+            logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
+            return 1
+    except xmlrpclib.Fault as x:
+        logger.error("XMLRPC Fault getting commandline:\n %s" % x)
+        return 1
+
+    parseprogress = None
+    cacheprogress = None
+    main.shutdown = 0
+    interrupted = False
+    return_value = 0
+    errors = 0
+    warnings = 0
+    taskfailures = []
+    wbhbhelper = WebHOBHelper()
+    build_object, layer_object = wbhbhelper.store_build(uuid)
+
+    termfilter = tf(main, helper, console, format)
+
+    while True:
+        try:
+            termfilter.updateFooter()
+            event = eventHandler.waitEvent(0.25)
+
+            if event is None:
+                if main.shutdown > 1:
+                    break
+                continue
+
+            helper.eventHandler(event)
+
+            if isinstance(event, bb.runqueue.runQueueExitWait):
+                if not main.shutdown:
+                    main.shutdown = 1
+
+            if isinstance(event, bb.build.TaskStarted):
+                wbhbhelper.store_started_task(event, build_object)
+
+            if isinstance(event, (bb.build.TaskSucceeded, bb.build.TaskFailedSilent, bb.build.TaskFailed)):
+                wbhbhelper.write_stored_tasks(event, build_object, layer_object)
+
+            if isinstance(event, bb.event.LogExecTTY):
+                if log_exec_tty:
+                    tries = event.retries
+                    while tries:
+                        print("Trying to run: %s" % event.prog)
+                        if os.system(event.prog) == 0:
+                            break
+                        time.sleep(event.sleep_delay)
+                        tries -= 1
+                    if tries:
+                        continue
+                logger.warn(event.msg)
+                continue
+
+            if isinstance(event, logging.LogRecord):
+                if event.levelno >= format.ERROR:
+                    errors = errors + 1
+                    return_value = 1
+                elif event.levelno == format.WARNING:
+                    warnings = warnings + 1
+                # For "normal" logging conditions, don't show note logs from tasks
+                # but do show them if the user has changed the default log level to 
+                # include verbose/debug messages
+                if event.taskpid != 0 and event.levelno <= format.NOTE:
+                    continue
+                logger.handle(event)
+                continue
+
+            if isinstance(event, bb.build.TaskFailed):
+                return_value = 1
+                logfile = event.logfile
+                if logfile and os.path.exists(logfile):
+                    termfilter.clearFooter()
+                    bb.error("Logfile of failure stored in: %s" % logfile)
+                    if includelogs and not event.errprinted:
+                        print("Log data follows:")
+                        f = open(logfile, "r")
+                        lines = []
+                        while True:
+                            l = f.readline()
+                            if l == '':
+                                break
+                            l = l.rstrip()
+                            if loglines:
+                                lines.append(' | %s' % l)
+                                if len(lines) > int(loglines):
+                                    lines.pop(0)
+                            else:
+                                print('| %s' % l)
+                        f.close()
+                        if lines:
+                            for line in lines:
+                                print(line)
+            if isinstance(event, bb.build.TaskBase):
+                logger.info(event._message)
+                continue
+            if isinstance(event, bb.event.ParseStarted):
+                if event.total == 0:
+                    continue
+                parseprogress = new_progress("Parsing recipes", event.total).start()
+                continue
+            if isinstance(event, bb.event.ParseProgress):
+                parseprogress.update(event.current)
+                continue
+            if isinstance(event, bb.event.ParseCompleted):
+                if not parseprogress:
+                    continue
+
+                parseprogress.finish()
+                print(("Parsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
+                    % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors)))
+                continue
+
+            if isinstance(event, bb.event.CacheLoadStarted):
+                cacheprogress = new_progress("Loading cache", event.total).start()
+                continue
+            if isinstance(event, bb.event.CacheLoadProgress):
+                cacheprogress.update(event.current)
+                continue
+            if isinstance(event, bb.event.CacheLoadCompleted):
+                cacheprogress.finish()
+                print("Loaded %d entries from dependency cache." % event.num_entries)
+                continue
+
+            if isinstance(event, bb.event.MultipleProviders):
+                logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
+                            event._item,
+                            ", ".join(event._candidates))
+                logger.info("consider defining a PREFERRED_PROVIDER entry to match %s", event._item)
+                continue
+            if isinstance(event, bb.event.NoProvider):
+                return_value = 1
+                errors = errors + 1
+                if event._runtime:
+                    r = "R"
+                else:
+                    r = ""
+
+                if event._dependees:
+                    logger.error("Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)", r, event._item, ", ".join(event._dependees), r)
+                else:
+                    logger.error("Nothing %sPROVIDES '%s'", r, event._item)
+                if event._reasons:
+                    for reason in event._reasons:
+                        logger.error("%s", reason)
+                continue
+
+            if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+                logger.info("Running setscene task %d of %d (%s)" % (event.stats.completed + event.stats.active + event.stats.failed + 1, event.stats.total, event.taskstring))
+                continue
+
+            if isinstance(event, bb.runqueue.runQueueTaskStarted):
+                if event.noexec:
+                    tasktype = 'noexec task'
+                else:
+                    tasktype = 'task'
+                logger.info("Running %s %s of %s (ID: %s, %s)",
+                            tasktype,
+                            event.stats.completed + event.stats.active +
+                                event.stats.failed + 1,
+                            event.stats.total, event.taskid, event.taskstring)
+                continue
+
+            if isinstance(event, bb.runqueue.runQueueTaskFailed):
+                taskfailures.append(event.taskstring)
+                logger.error("Task %s (%s) failed with exit code '%s'",
+                             event.taskid, event.taskstring, event.exitcode)
+                continue
+ 
+            if isinstance(event, bb.runqueue.sceneQueueTaskFailed):
+                logger.warn("Setscene task %s (%s) failed with exit code '%s' - real task will be run instead",
+                             event.taskid, event.taskstring, event.exitcode)
+                continue
+
+            if isinstance(event, bb.event.ConfigParsed):
+                # timestamp should be added for this
+                continue
+            
+            if isinstance(event, bb.event.RecipeParsed):
+                # timestamp should be added for this
+                continue
+                
+            if isinstance(event, bb.event.OperationStarted):
+                # timestamp should be added for this
+                continue
+
+            if isinstance(event, bb.event.OperationCompleted):
+                # timestamp should be added for this
+                # signal a complete operation
+                # calculate timing
+                continue
+
+            if isinstance(event, bb.event.DiskFull):
+                # trigger an error
+                continue
+
+            # ignore
+            if isinstance(event, (bb.event.BuildBase,
+                                  bb.event.StampUpdate,
+                                  bb.event.RecipePreFinalise,
+                                  bb.runqueue.runQueueEvent,
+                                  bb.runqueue.runQueueExitWait,
+                                  bb.event.OperationProgress,
+                                  bb.command.CommandFailed,
+                                  bb.command.CommandExit,
+                                  bb.command.CommandCompleted,
+                                  bb.cooker.CookerExit)):
+                continue
+
+            logger.error("Unknown event: %s", event)
+
+        except EnvironmentError as ioerror:
+            termfilter.clearFooter()
+            # ignore interrupted io
+            if ioerror.args[0] == 4:
+                pass
+        except KeyboardInterrupt:
+            termfilter.clearFooter()
+            if main.shutdown == 1:
+                print("\nSecond Keyboard Interrupt, stopping...\n")
+                _, error = server.runCommand(["stateStop"])
+                if error:
+                    logger.error("Unable to cleanly stop: %s" % error)
+            if main.shutdown == 0:
+                print("\nKeyboard Interrupt, closing down...\n")
+                interrupted = True
+                _, error = server.runCommand(["stateShutdown"])
+                if error:
+                    logger.error("Unable to cleanly shutdown: %s" % error)
+            main.shutdown = main.shutdown + 1
+            pass
+
+    summary = ""
+    if taskfailures:
+        summary += pluralise("\nSummary: %s task failed:",
+                             "\nSummary: %s tasks failed:", len(taskfailures))
+        for failure in taskfailures:
+            summary += "\n  %s" % failure
+    if warnings:
+        summary += pluralise("\nSummary: There was %s WARNING message shown.",
+                             "\nSummary: There were %s WARNING messages shown.", warnings)
+    if return_value:
+        summary += pluralise("\nSummary: There was %s ERROR message shown, returning a non-zero exit code.",
+                             "\nSummary: There were %s ERROR messages shown, returning a non-zero exit code.", errors)
+    if summary:
+        print(summary)
+
+    if interrupted:
+        print("Execution was interrupted, returning a non-zero exit code.")
+        if return_value == 0:
+            return_value = 1
+
+    termfilter.finish()
+
+    return return_value
+
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 05/94] bitbake: webhob: updates to Django models
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (3 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 04/94] bitbake: create Data Store Interface (DSI) file Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 06/94] bitbake: webhob: make DSI store build information Alex DAMIAN
                   ` (89 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

Based on recent discussions with the team I have updated the
Django models we had in order to store information more clearly.
The updates include: renaming of some tables, removing of
unnecessary fields, adding supplementary tables to reflect relations
between objects.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/orm/models.py | 94 ++++++++++++++++++++--------------------
 1 file changed, 47 insertions(+), 47 deletions(-)

diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 43b4319..6b8e2fa 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -1,22 +1,18 @@
 from django.db import models
 
 
-class Builds(models.Model):
+class Build(models.Model):
 
     BUILD_OUTCOME = (
-        (0, 'Succeded'),
+        (0, 'Succeeded'),
         (1, 'Failed'),
     )
 
     uuid = models.CharField(max_length=100, unique=True)
     target = models.CharField(max_length=100)
-    host_machine = models.CharField(max_length=100)
+    machine = models.ForeignKey('Machine', related_name='build_machine')
     distro = models.CharField(max_length=100)
     distro_version = models.CharField(max_length=100)
-    host_system = models.CharField(max_length=100)
-    host_distribution = models.CharField(max_length=100)
-    target_system = models.ForeignKey('Machines', related_name='builds_machines')
-    layers = models.ManyToManyField('Layers')
     started_on = models.DateTimeField()
     completed_on = models.DateTimeField()
     outcome = models.IntegerField(choices=BUILD_OUTCOME)
@@ -29,7 +25,7 @@ class Builds(models.Model):
     bitbake_version = models.CharField(max_length=50)
 
 
-class Tasks(models.Model):
+class Task(models.Model):
 
     SSTATE_RESULT = (
         (0, 'Not Applicable'), # For rest of tasks, but they still need checking.
@@ -47,23 +43,23 @@ class Tasks(models.Model):
         (0, 'Covered'),
         (1, 'Sstate'),
         (2, 'Existing'),
-        (3, 'Succeded'),
+        (3, 'Succeeded'),
         (4, 'Failed'),
     )
 
-    uuid = models.ForeignKey(Builds, to_field='uuid', related_name='tasks_builds')
+    build = models.ForeignKey(Build, related_name='task_build')
     order = models.IntegerField()
     task_executed = models.BooleanField() # True means Executed, False means Prebuilt
     outcome = models.IntegerField(choices=TASK_OUTCOME)
     sstate_checksum = models.CharField(max_length=100)
     path_to_sstate_obj = models.FilePathField(max_length=500, blank=True)
-    recipe = models.ForeignKey('Recipes', related_name='builds_recipes')
+    recipe = models.ForeignKey('Recipe', related_name='build_recipe')
     task_name = models.CharField(max_length=100)
-    source_url = models.FilePathField(max_length=200)
-    log_file = models.FilePathField(max_length=200, blank=True)
-    work_directory = models.FilePathField(max_length=200)
+    source_url = models.FilePathField(max_length=255)
+    log_file = models.FilePathField(max_length=255, blank=True)
+    work_directory = models.FilePathField(max_length=255)
     script_type = models.IntegerField(choices=TASK_CODING)
-    file_path = models.FilePathField(max_length=200)
+    file_path = models.FilePathField(max_length=255)
     line_number = models.IntegerField()
     py_stack_trace = models.TextField()
     disk_io = models.DecimalField(max_digits=20, decimal_places=10)
@@ -74,54 +70,52 @@ class Tasks(models.Model):
     error = models.TextField()
     warning = models.TextField()
     sstate_result = models.IntegerField(choices=SSTATE_RESULT)
-    diffsigs = models.CharField(max_length=500)
 
 
-class Task_Dependencies(models.Model):
-    task = models.ForeignKey(Tasks, related_name='task_dependencies_task')
-    depends_on = models.ForeignKey(Tasks, related_name='task_dependencies_depends')
+class Task_Dependency(models.Model):
+    task = models.ForeignKey(Task, related_name='task_dependencies_task')
+    depends_on = models.ForeignKey(Task, related_name='task_dependencies_depends')
 
 
-class Targets(models.Model):
-    uuid = models.ForeignKey(Builds, to_field='uuid',  related_name='targets_builds')
+class Target(models.Model):
+    build = models.ForeignKey(Build, related_name='target_build')
     is_image = models.BooleanField()
 
 
-class Artifacts(models.Model):
-    uuid = models.ForeignKey(Builds, to_field='uuid', related_name='artifacts_builds')
-    target = models.ForeignKey(Targets, related_name='artifacts_targets')
+class Artifact(models.Model):
+    build = models.ForeignKey(Build, related_name='artifact_build')
+    target = models.ForeignKey(Target, related_name='artifact_target')
     file_name = models.CharField(max_length=100)
     file_size = models.IntegerField()
 
 
-class Packages_In_Images(models.Model):
-    package = models.ForeignKey('Packages', related_name='packages_in_images_package')
-    target = models.ForeignKey(Targets, related_name='packages_in_images_target')
+class Package_In_Image(models.Model):
+    package = models.ForeignKey('Package', related_name='package_in_image_package')
+    target = models.ForeignKey(Target, related_name='package_in_image_target')
 
 
-class Packages(models.Model):
-    package_id = models.IntegerField()
-    recipe = models.ForeignKey('Recipes', related_name='packages_recipes')
+class Package(models.Model):
+    recipe = models.ForeignKey('Recipe', related_name='package_recipe')
     name = models.CharField(max_length=100)
     version = models.CharField(max_length=100)
     size = models.IntegerField()
 
 
-class Package_Dependencies(models.Model):
-    package = models.ForeignKey(Packages, related_name='package_dependencies_package')
-    depends_on = models.ForeignKey(Packages, related_name='package_dependencies_depends')
+class Package_Dependency(models.Model):
+    package = models.ForeignKey(Package, related_name='package_dependencies_package')
+    depends_on = models.ForeignKey(Package, related_name='package_dependencies_depends')
 
 
 class Filelist(models.Model):
-    package = models.ForeignKey(Packages, related_name='filelist_packages')
-    complete_file_path = models.FilePathField(max_length=200, blank=True)
+    package = models.ForeignKey(Package, related_name='filelist_package')
+    complete_file_path = models.FilePathField(max_length=255, blank=True)
     file_size = models.IntegerField()
 
 
-class Recipes(models.Model):
+class Recipe(models.Model):
     name = models.CharField(max_length=100)
     version = models.CharField(max_length=100)
-    layer = models.ForeignKey('Layers', related_name='recipes_layers')
+    layer = models.ForeignKey('Build_Layer', related_name='recipe_build_layer')
     summary = models.CharField(max_length=100)
     description = models.CharField(max_length=100)
     section = models.CharField(max_length=100)
@@ -130,33 +124,39 @@ class Recipes(models.Model):
     homepage = models.URLField()
     bugtracker = models.URLField()
     author = models.CharField(max_length=100)
-    file_path = models.FilePathField(max_length=200)
+    file_path = models.FilePathField(max_length=255)
 
 
-class Recipe_Dependencies(models.Model):
-    recipe = models.ForeignKey(Recipes, related_name='recipe_dependencies_recipe')
-    depends_on = models.ForeignKey(Recipes, related_name='recipe_dependencies_depends')
+class Recipe_Dependency(models.Model):
+    recipe = models.ForeignKey(Recipe, related_name='recipe_dependencies_recipe')
+    depends_on = models.ForeignKey(Recipe, related_name='recipe_dependencies_depends')
 
 
-class Layers(models.Model):
+class Layer(models.Model):
     name = models.CharField(max_length=100)
+    local_path = models.FilePathField(max_length=255)
+    layer_index_url = models.URLField()
+
+
+class Build_Layer(models.Model):
+    build = models.ForeignKey(Build, related_name='build_layer_build')
+    layer = models.ForeignKey(Layer, related_name='build_layer_layer')
     branch = models.CharField(max_length=50)
     commit = models.CharField(max_length=100)
     priority = models.IntegerField()
-    link_to_oe_core = models.URLField()
 
 
-class Variables(models.Model):
-    uuid = models.ForeignKey(Builds, to_field='uuid', related_name='variables_builds')
+class Variable(models.Model):
+    build = models.ForeignKey(Build, related_name='variable_build')
     variable_name = models.CharField(max_length=100)
     variable_value = models.TextField()
-    file = models.FilePathField(max_length=200)
+    file = models.FilePathField(max_length=255)
     changed = models.BooleanField()
     human_readable_name = models.CharField(max_length=200)
     description = models.TextField()
 
 
-class Machines(models.Model):
+class Machine(models.Model):
     name = models.CharField(max_length=100)
     description = models.TextField()
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 06/94] bitbake: webhob: make DSI store build information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (4 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 05/94] bitbake: webhob: updates to Django models Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 07/94] bitbake: webhob: make DSI store task information Alex DAMIAN
                   ` (88 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch will enable DSI to store build information defined in
the Django models. This will be done for each executed build and it
will use a helper file to save this information.

Moved code from DSI interface to the helper file in to classes,
one which wraps around Django model interface operations, and one that
fills in information from the event system into the model data.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 127 ++++++++++++++++++++++++
 bitbake/lib/bb/ui/dsi.py             | 180 ++++-------------------------------
 2 files changed, 148 insertions(+), 159 deletions(-)
 create mode 100644 bitbake/lib/bb/ui/buildinfohelper.py

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
new file mode 100644
index 0000000..c77248c
--- /dev/null
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -0,0 +1,127 @@
+import datetime
+import sys
+import uuid
+
+
+class ORMWrapper(object):
+    """ This class creates the dictionaries needed to store information in the database
+        following the format defined by the Django models. It is also used to save this
+        information in the database.
+    """
+
+    def __init__(self):
+        self.uuid = None
+
+    def get_machine_information(self, server):
+        machine_info = {}
+
+        machine_info['name'] = server.runCommand(["getVariable", "MACHINE"])[0]
+        machine_info['description'] = 'Not Available'
+
+        return machine_info
+
+    def create_machine_object(self, machine_information):
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Machine
+
+        machine = Machine.objects.get_or_create(name=machine_information['name'],
+                                                description=machine_information['description'])
+
+        return machine[0]
+
+    def get_build_information(self, server, machine_obj):
+        build_info = {}
+
+        # Generate an identifier for each new build
+        self.uuid = str(uuid.uuid4())
+
+        build_info['uuid'] = self.uuid
+        build_info['target'] = 'Not Available'
+        build_info['machine'] = machine_obj
+        build_info['distro'] = server.runCommand(["getVariable", "DISTRO"])[0]
+        build_info['distro_version'] = server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
+        build_info['started_on'] = datetime.datetime.now()
+        build_info['completed_on'] = datetime.datetime.now()
+        build_info['outcome'] = 0
+        build_info['number_of_errors'] = 0
+        build_info['number_of_warnings'] = 0
+        build_info['cpu_usage'] = 0
+        build_info['disk_io'] = 0
+        build_info['cooker_log_path'] = server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
+        build_info['build_name'] = server.runCommand(["getVariable", "BUILDNAME"])[0]
+        build_info['bitbake_version'] = server.runCommand(["getVariable", "BB_VERSION"])[0]
+
+        return build_info
+
+    def create_build_object(self, build_info):
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Build
+
+        build = Build.objects.create(uuid=build_info['uuid'],
+                                    target=build_info['target'],
+                                    machine=build_info['machine'],
+                                    distro=build_info['distro'],
+                                    distro_version=build_info['distro_version'],
+                                    started_on=build_info['started_on'],
+                                    completed_on=build_info['completed_on'],
+                                    outcome=build_info['outcome'],
+                                    errors_no=build_info['number_of_errors'],
+                                    warnings_no=build_info['number_of_warnings'],
+                                    cpu_usage=build_info['cpu_usage'],
+                                    disk_io=build_info['disk_io'],
+                                    cooker_log_path=build_info['cooker_log_path'],
+                                    build_name=build_info['build_name'],
+                                    bitbake_version=build_info['bitbake_version'])
+
+        return build
+
+    def update_build_object(self, build_obj, errors, warnings):
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Build
+
+        outcome = 0
+        if errors or warnings:
+            outcome = 1
+
+        build = Build.objects.get(uuid=build_obj.uuid)
+        build.completed_on = datetime.datetime.now()
+        build.errors_no = errors
+        build.warnings_no = warnings
+        build.outcome = outcome
+        build.save()
+
+        return build_obj
+
+
+class BuildInfoHelper(object):
+    """ This class gathers the build information from the server and sends it
+        towards the ORM wrapper for storing in the database
+    """
+
+    def __init__(self):
+        self.configure_django()
+        self.task_order = 0
+        self.tasks_information = {}
+        self.uuid = None
+        self.transport_utils = {}
+        self.orm_wrapper = ORMWrapper()
+
+    def configure_django(self):
+        import webhob.whbmain.settings as whb_django_settings
+        from django.core.management import setup_environ
+        setup_environ(whb_django_settings)
+        # Add webhob to sys path for importing modules
+        sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'webhob'))
+
+    def store_started_build(self, event, server):
+
+        machine_information = self.orm_wrapper.get_machine_information(server)
+        machine_obj = self.orm_wrapper.create_machine_object(machine_information)
+
+        build_information = self.orm_wrapper.get_build_information(server, machine_obj)
+        build_obj = self.orm_wrapper.create_build_object(build_information)
+        self.transport_utils['build'] = build_obj
+
+    def update_build_information(self, event, errors, warnings):
+        build_obj = self.orm_wrapper.update_build_object(self.transport_utils['build'], errors, warnings)
+        del self.transport_utils['build']
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index f832806..d7b038c 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -17,157 +17,25 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 from __future__ import division
+from bb.ui import uihelper
+from bb.ui.buildinfohelper import BuildInfoHelper
 
-import os
-import sys
-import xmlrpclib
+import bb.msg
+import copy
+import fcntl
 import logging
+import os
 import progressbar
 import signal
-import bb.msg
-import time
-import fcntl
 import struct
-import copy
-import datetime
+import sys
+import time
+import xmlrpclib
 
-from bb.ui import uihelper
 
 logger = logging.getLogger("BitBake")
 interactive = sys.stdout.isatty()
 
-class WebHOBHelper(object):
-    
-    def __init__(self):
-        self.configure_django()
-        self.task_order = 0
-        self.tasks_information = {}
-
-    def configure_django(self):
-        import webhob.whbmain.settings as whb_django_settings
-        from django.core.management import setup_environ
-        setup_environ(whb_django_settings)
-        # Add webhob to sys path for importing modules
-        sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'webhob'))
-    
-    def store_build(self, uuid):
-        # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Builds, Machines, Layers
-        machine_obj = Machines.objects.create(
-                                name='Test Machine',
-                                description='This is our great test machine'
-                                )
-        machine_obj.save()
-
-        layer_obj = Layers.objects.create(
-                                name='Test Layer',
-                                branch='master_test',
-                                commit='2148u323nsau83smdmbnca3312f',
-                                priority=2,
-                                link_to_oe_core='http://test.oecore.com'
-                                )
-        layer_obj.save()
-
-        build_obj = Builds.objects.create(
-                                uuid=uuid,
-                                target='test target',
-                                host_machine='Host machine',
-                                distro='1.234',
-                                distro_version='v2',
-                                host_system='Test hosting system',
-                                host_distribution='Test host distrib',
-                                target_system=machine_obj,
-                                started_on=datetime.datetime.now(),
-                                completed_on=datetime.datetime.now(),
-                                outcome=3,
-                                errors_no=0,
-                                warnings_no=0,
-                                cpu_usage=54,
-                                disk_io=23.3232,
-                                cooker_log_path='/home/test/cooker_test',
-                                build_name='tester_build',
-                                bitbake_version='1.0.5')
-        build_obj.layers.add(layer_obj)
-        build_obj.save()
-
-        return build_obj, layer_obj
-
-    def store_started_task(self, event, build_object):
-        self.task_order += 1
-        identifier = event._package + event._task
-        self.tasks_information[identifier] = {
-                'uuid': build_object,
-                'task_executed': True,
-                'order': self.task_order,
-                'recipe': event._package,
-                'task_name': event._task,
-                'start_time': time.time(),
-                }
-
-    def write_stored_tasks(self, event, build_object, layer_object):
-        identifier = event._package + event._task
-
-        # fix this
-        if event.getDisplayName().lower() == 'succeded':
-            outcome = 0
-        else:
-            outcome = 1
-
-        self.tasks_information[identifier].update({
-                'outcome': outcome,
-                'end_time': time.time(),
-                })
-
-        task = self.tasks_information[identifier]
-
-        # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Tasks, Recipes
-
-        recipe_obj = Recipes.objects.create(
-                                    name=task['recipe'],
-                                    version='1.0',
-                                    layer=layer_object,
-                                    summary='Short summary',
-                                    description='Longer description',
-                                    section='24',
-                                    license='license title',
-                                    licensing_info='longer license information',
-                                    homepage='http://homepage.com',
-                                    bugtracker='2456',
-                                    author='Mr.Test',
-                                    file_path='home/test/testproject/',
-                                    )
-
-        # Store finished task
-        task_obj = Tasks.objects.create(uuid=task['uuid'],
-                                        order=task['order'],
-                                        outcome=task['outcome'],
-                                        recipe=recipe_obj,
-                                        task_name=task['task_name'],
-                                        elapsed_time=task['end_time'] - task['start_time'],
-                                        # The next lines are just for testing reasons
-                                        sstate_checksum = '2134123sdscsdf232342tgfdv',
-                                        task_executed=True,
-                                        path_to_sstate_obj='/home/calin',
-                                        source_url='/',
-                                        log_file='/',
-                                        work_directory='/',
-                                        script_type=0,
-                                        file_path='/',
-                                        line_number=55,
-                                        py_stack_trace='Testing traceback',
-                                        disk_io = 23.55633,
-                                        cpu_usage=43,
-                                        errors_no=0,
-                                        warnings_no=0,
-                                        error='None',
-                                        warning='None',
-                                        sstate_result=0,
-                                        diffsigs="Testing diffsigs")
-        task_obj.save()
-        # Clean memory
-        del self.tasks_information[identifier]
-
 
 class BBProgress(progressbar.ProgressBar):
     def __init__(self, msg, maxval):
@@ -350,12 +218,7 @@ class TerminalFilter(object):
             self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
 
 def main(server, eventHandler, params, tf = TerminalFilter):
-    print "This is running in DSI mode"
-
-    # Generate an unique ID for this build
-    # TODO: for multiple build commands this might not work as expected
-    import uuid
-    uuid = sessionid = str(uuid.uuid4())
+    print "DSI - Data Store Interface"
 
     # Get values of variables which control our output
     includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
@@ -422,8 +285,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
     errors = 0
     warnings = 0
     taskfailures = []
-    wbhbhelper = WebHOBHelper()
-    build_object, layer_object = wbhbhelper.store_build(uuid)
+    buildinfohelper = BuildInfoHelper()
 
     termfilter = tf(main, helper, console, format)
 
@@ -443,11 +305,8 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 if not main.shutdown:
                     main.shutdown = 1
 
-            if isinstance(event, bb.build.TaskStarted):
-                wbhbhelper.store_started_task(event, build_object)
-
-            if isinstance(event, (bb.build.TaskSucceeded, bb.build.TaskFailedSilent, bb.build.TaskFailed)):
-                wbhbhelper.write_stored_tasks(event, build_object, layer_object)
+            if isinstance(event, bb.event.BuildStarted):
+                buildinfohelper.store_started_build(event, server)
 
             if isinstance(event, bb.event.LogExecTTY):
                 if log_exec_tty:
@@ -470,7 +329,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 elif event.levelno == format.WARNING:
                     warnings = warnings + 1
                 # For "normal" logging conditions, don't show note logs from tasks
-                # but do show them if the user has changed the default log level to 
+                # but do show them if the user has changed the default log level to
                 # include verbose/debug messages
                 if event.taskpid != 0 and event.levelno <= format.NOTE:
                     continue
@@ -577,20 +436,24 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 logger.error("Task %s (%s) failed with exit code '%s'",
                              event.taskid, event.taskstring, event.exitcode)
                 continue
- 
+
             if isinstance(event, bb.runqueue.sceneQueueTaskFailed):
                 logger.warn("Setscene task %s (%s) failed with exit code '%s' - real task will be run instead",
                              event.taskid, event.taskstring, event.exitcode)
                 continue
 
+            if isinstance(event, (bb.command.CommandFailed, bb.command.CommandExit, bb.command.CommandCompleted, bb.cooker.CookerExit)):
+                buildinfohelper.update_build_information(event, errors, warnings)
+                main.shutdown = 2
+
             if isinstance(event, bb.event.ConfigParsed):
                 # timestamp should be added for this
                 continue
-            
+
             if isinstance(event, bb.event.RecipeParsed):
                 # timestamp should be added for this
                 continue
-                
+
             if isinstance(event, bb.event.OperationStarted):
                 # timestamp should be added for this
                 continue
@@ -664,4 +527,3 @@ def main(server, eventHandler, params, tf = TerminalFilter):
     termfilter.finish()
 
     return return_value
-
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 07/94] bitbake: webhob: make DSI store task information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (5 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 06/94] bitbake: webhob: make DSI store build information Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 08/94] bitbake: webhob: view a table with all builds Alex DAMIAN
                   ` (87 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch will enable DSI to store task related information.
Together with the task object, other objects need to be obtained
or created in order to respect the Django models defined.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 235 +++++++++++++++++++++++++++++++++--
 bitbake/lib/bb/ui/dsi.py             |   8 +-
 2 files changed, 231 insertions(+), 12 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index c77248c..57e404b 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -11,6 +11,8 @@ class ORMWrapper(object):
 
     def __init__(self):
         self.uuid = None
+        self.task_order = 0
+        self.transport_utils = {}
 
     def get_machine_information(self, server):
         machine_info = {}
@@ -75,12 +77,12 @@ class ORMWrapper(object):
 
         return build
 
-    def update_build_object(self, build_obj, errors, warnings):
+    def update_build_object(self, build_obj, errors, warnings, taskfailures):
         # This needs to be imported after we have configured the Django settings file
         from webhob.orm.models import Build
 
         outcome = 0
-        if errors or warnings:
+        if errors or taskfailures:
             outcome = 1
 
         build = Build.objects.get(uuid=build_obj.uuid)
@@ -90,7 +92,212 @@ class ORMWrapper(object):
         build.outcome = outcome
         build.save()
 
-        return build_obj
+    def get_task_information(self, event):
+
+        self.task_order += 1
+
+        recipe = self.get_recipe_object(event)
+
+        task_information = {}
+        task_information['build'] = self.transport_utils['build']
+        task_information['order'] = self.task_order
+        task_information['task_executed'] = True
+        task_information['outcome'] = 0
+        task_information['sstate_checksum'] = 'Not Available'
+        task_information['path_to_sstate_obj'] = 'Not Available'
+        task_information['recipe'] = recipe
+        task_information['task_name'] = event._task
+        task_information['source_url'] = 'Not Available'
+        task_information['log_file'] = 'Not Available'
+        task_information['work_directory'] = 'Not Available'
+        task_information['script_type'] = 0
+        task_information['file_path'] = 'Not Available'
+        task_information['line_number'] = 0
+        task_information['py_stack_trace'] = 'Not Available'
+        task_information['disk_io'] = 0
+        task_information['cpu_usage'] = 0
+        task_information['elapsed_time'] = 'Not Available'
+        task_information['errors_no'] = 0
+        task_information['warnings_no'] = 0
+        task_information['error'] = 'Not Available'
+        task_information['warning'] = 'Not Available'
+        task_information['sstate_result'] = 0
+
+        return task_information
+
+    def create_task_object(self, task_information):
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Task
+
+        identifier = task_information['recipe'].name + task_information['task_name']
+
+        task_object = Task.objects.get_or_create(
+                                build=task_information['build'],
+                                order=task_information['order'],
+                                task_executed=task_information['task_executed'],
+                                outcome=task_information['outcome'],
+                                sstate_checksum=task_information['sstate_checksum'],
+                                path_to_sstate_obj=task_information['path_to_sstate_obj'],
+                                recipe=task_information['recipe'],
+                                task_name=task_information['task_name'],
+                                source_url=task_information['source_url'],
+                                log_file=task_information['log_file'],
+                                work_directory=task_information['work_directory'],
+                                script_type=task_information['script_type'],
+                                file_path=task_information['file_path'],
+                                line_number=task_information['line_number'],
+                                py_stack_trace=task_information['py_stack_trace'],
+                                disk_io=task_information['disk_io'],
+                                cpu_usage=task_information['cpu_usage'],
+                                elapsed_time=task_information['elapsed_time'],
+                                errors_no=task_information['errors_no'],
+                                warnings_no=task_information['warnings_no'],
+                                error=task_information['error'],
+                                warning=task_information['warning'],
+                                sstate_result=task_information['sstate_result'])
+        task_object[0].save()
+
+        self.transport_utils[identifier] = {'object': task_object[0], 'start_time': datetime.datetime.now()}
+
+        return task_object[0]
+
+    def update_task_object(self, task_dictionary, event):
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Task
+
+        task_object = task_dictionary['object']
+
+        task = Task.objects.get(build=task_object.build,
+                                task_name=task_object.task_name,
+                                recipe=task_object.recipe)
+
+        duration = datetime.datetime.now() - task_dictionary['start_time']
+        task.elapsed_time = duration.total_seconds()
+
+        outcome_info = self.get_outcome_of_task(event)
+        task.outcome = outcome_info['task_outcome']
+
+        if outcome_info.get('error', ''):
+            task.error = outcome_info['error']
+
+        #TODO: get error number
+        #TODO: get warnings number
+        #TODO: get warning information
+
+        task.save()
+
+    def get_outcome_of_task(self, event):
+        from webhob.orm.models import Task
+
+        outcome_info = {}
+
+        task_result = event.getDisplayName().lower()
+        for outcome in Task.TASK_OUTCOME:
+            if task_result == outcome[1].lower():
+                task_outcome = outcome[0]
+
+        outcome_info['task_outcome'] = task_outcome
+
+        if isinstance(event, bb.build.TaskFailed):
+            if event.logfile and os.path.exists(event.logfile):
+                outcome_info['error'] = open(event.logfile, "r").read()
+
+        return outcome_info
+
+    def get_recipe_object(self, event):
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Recipe
+
+        recipe_information = self.get_recipe_information(event)
+
+        recipe_object = Recipe.objects.get_or_create(
+                                name=recipe_information['name'],
+                                version=recipe_information['version'],
+                                layer=recipe_information['layer'],
+                                summary=recipe_information['summary'],
+                                description=recipe_information['description'],
+                                section=recipe_information['section'],
+                                license=recipe_information['license'],
+                                licensing_info=recipe_information['licensing_info'],
+                                homepage=recipe_information['homepage'],
+                                bugtracker=recipe_information['bugtracker'],
+                                author=recipe_information['author'],
+                                file_path=recipe_information['file_path'])
+        recipe_object[0].save()
+
+        return recipe_object[0]
+
+    def get_recipe_information(self, event):
+
+        build_layer_obj = self.get_build_layer_object(event)
+
+        recipe_info = {}
+        recipe_info['name'] = event._package
+        recipe_info['version'] = 'Not Available'
+        recipe_info['layer'] = build_layer_obj
+        recipe_info['summary'] = 'Not Available'
+        recipe_info['description'] = 'Not Available'
+        recipe_info['section'] = 'Not Available'
+        recipe_info['license'] = 'Not Available'
+        recipe_info['licensing_info'] = 'Not Available'
+        recipe_info['homepage'] = 'Not Available'
+        recipe_info['bugtracker'] = 'Not Available'
+        recipe_info['author'] = 'Not Available'
+        recipe_info['file_path'] = 'Not Available'
+
+        return recipe_info
+
+    def get_build_layer_object(self, event):
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Build_Layer
+
+        build_layer_information = self.get_build_layer_information(event)
+
+        build_layer_object = Build_Layer.objects.get_or_create(
+                                    build = build_layer_information['build'],
+                                    layer = build_layer_information['layer'],
+                                    branch = build_layer_information['branch'],
+                                    commit = build_layer_information['commit'],
+                                    priority = build_layer_information['priority'])
+        build_layer_object[0].save()
+
+        return build_layer_object[0]
+
+    def get_build_layer_information(self, event):
+
+        layer_object = self.get_layer_object(event)
+
+        build_layer_info = {}
+        build_layer_info['build'] = self.transport_utils['build']
+        build_layer_info['layer'] = layer_object
+        build_layer_info['branch'] = 'Not Available'
+        build_layer_info['commit'] = 'Not Available'
+        build_layer_info['priority'] = 0
+
+        return build_layer_info
+
+    def get_layer_object(self, event):
+        # This needs to be imported after we have configured the Django settings file
+        from webhob.orm.models import Layer
+
+        layer_information = self.get_layer_information(event)
+
+        layer_object = Layer.objects.get_or_create(
+                                name=layer_information['name'],
+                                local_path=layer_information['local_path'],
+                                layer_index_url=layer_information['layer_index_url'])
+        layer_object[0].save()
+
+        return layer_object[0]
+
+    def get_layer_information(self, event):
+
+        layer_info = {}
+        layer_info['name'] = 'Layer Test Name'
+        layer_info['local_path'] = 'Not Available'
+        layer_info['layer_index_url'] = 'Not Available'
+
+        return layer_info
 
 
 class BuildInfoHelper(object):
@@ -100,10 +307,6 @@ class BuildInfoHelper(object):
 
     def __init__(self):
         self.configure_django()
-        self.task_order = 0
-        self.tasks_information = {}
-        self.uuid = None
-        self.transport_utils = {}
         self.orm_wrapper = ORMWrapper()
 
     def configure_django(self):
@@ -120,8 +323,18 @@ class BuildInfoHelper(object):
 
         build_information = self.orm_wrapper.get_build_information(server, machine_obj)
         build_obj = self.orm_wrapper.create_build_object(build_information)
-        self.transport_utils['build'] = build_obj
+        self.orm_wrapper.transport_utils['build'] = build_obj
+
+    def update_build_information(self, event, errors, warnings, taskfailures):
+        self.orm_wrapper.update_build_object(self.orm_wrapper.transport_utils['build'], errors, warnings, taskfailures)
+        del self.orm_wrapper.transport_utils['build']
+
+    def store_started_task(self, event):
+        task_information = self.orm_wrapper.get_task_information(event)
+        task_obj = self.orm_wrapper.create_task_object(task_information)
 
-    def update_build_information(self, event, errors, warnings):
-        build_obj = self.orm_wrapper.update_build_object(self.transport_utils['build'], errors, warnings)
-        del self.transport_utils['build']
+    def update_and_store_task(self, event):
+        identifier = event._package + event._task
+        task_dictionary = self.orm_wrapper.transport_utils[identifier]
+        self.orm_wrapper.update_task_object(task_dictionary, event)
+        del self.orm_wrapper.transport_utils[identifier]
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index d7b038c..54364ce 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -308,6 +308,12 @@ def main(server, eventHandler, params, tf = TerminalFilter):
             if isinstance(event, bb.event.BuildStarted):
                 buildinfohelper.store_started_build(event, server)
 
+            if isinstance(event, bb.build.TaskStarted):
+                buildinfohelper.store_started_task(event)
+
+            if isinstance(event, (bb.build.TaskSucceeded, bb.build.TaskFailedSilent, bb.build.TaskFailed)):
+                buildinfohelper.update_and_store_task(event)
+
             if isinstance(event, bb.event.LogExecTTY):
                 if log_exec_tty:
                     tries = event.retries
@@ -443,7 +449,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 continue
 
             if isinstance(event, (bb.command.CommandFailed, bb.command.CommandExit, bb.command.CommandCompleted, bb.cooker.CookerExit)):
-                buildinfohelper.update_build_information(event, errors, warnings)
+                buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
                 main.shutdown = 2
 
             if isinstance(event, bb.event.ConfigParsed):
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 08/94] bitbake: webhob: view a table with all builds
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (6 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 07/94] bitbake: webhob: make DSI store task information Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 09/94] bitbake: webhob: view a table with all the tasks of a build Alex DAMIAN
                   ` (86 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch creates a page in Webhob in which you can view detailed
information about each executed build. There is data about the
outcome of each build, execution times, hardware usage, logs,
errors, warnings and others.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/__init__.py           |  0
 bitbake/lib/webhob/bldviewer/templates/build.html  | 54 ++++++++++++++++++++++
 .../lib/webhob/bldviewer/templatetags/__init__.py  |  0
 .../webhob/bldviewer/templatetags/projecttags.py   |  8 ++++
 bitbake/lib/webhob/bldviewer/urls.py               |  6 +++
 bitbake/lib/webhob/bldviewer/views.py              | 11 +++++
 bitbake/lib/webhob/whbmain/urls.py                 |  2 +-
 7 files changed, 80 insertions(+), 1 deletion(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/__init__.py
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/build.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templatetags/__init__.py
 create mode 100644 bitbake/lib/webhob/bldviewer/templatetags/projecttags.py
 create mode 100644 bitbake/lib/webhob/bldviewer/urls.py
 create mode 100644 bitbake/lib/webhob/bldviewer/views.py

diff --git a/bitbake/lib/webhob/bldviewer/__init__.py b/bitbake/lib/webhob/bldviewer/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
new file mode 100644
index 0000000..9a2d412
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -0,0 +1,54 @@
+<html>
+
+	<head>
+		<title>WebHob Build page</title>
+	</head>
+
+<body>
+	<h1>WebHob Builds</h1>
+
+	<table border="1">
+
+	{% load projecttags %}
+
+		<tr>
+			<th>Outcome</th>
+			<th>Started On</th>
+			<th>Completed On</th>
+			<th>Target</th>
+			<th>Machine</th>
+			<th>Time</th>
+			<th>Errors</th>
+			<th>Warnings</th>
+			<th>CPU usage</th>
+			<th>Disk I/O</th>
+			<th>Log</th>
+			<th>Bitbake Version</th>
+			<th>Build Name</th>
+		</tr>
+
+		{% for build in builds %}
+
+		<tr>
+			<td><a href="/build/{{build.id}}">{{build.get_outcome_display}}</a></td>
+			<td>{{build.started_on}}</td>
+			<td>{{build.completed_on}}</td>
+			<td>{{build.target}}</td>
+			<td>{{build.machine.name}}</td>
+			<td>{% time_difference build.started_on build.completed_on %}</td>
+			<td>{{build.errors_no}}</td>
+			<td>{{build.warnings_no}}</td>
+			<td>{{build.cpu_usage}}</td>
+			<td>{{build.disk_io}}</td>
+			<td>{{build.cooker_log_path}}</td>
+			<td>{{build.bitbake_version}}</td>
+			<td>{{build.build_name}}</td>
+		</tr>
+
+		{% endfor %}
+
+	</table>
+
+</body>
+
+</html>
diff --git a/bitbake/lib/webhob/bldviewer/templatetags/__init__.py b/bitbake/lib/webhob/bldviewer/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bitbake/lib/webhob/bldviewer/templatetags/projecttags.py b/bitbake/lib/webhob/bldviewer/templatetags/projecttags.py
new file mode 100644
index 0000000..9ae6214
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templatetags/projecttags.py
@@ -0,0 +1,8 @@
+from datetime import datetime
+from django import template
+
+register = template.Library()
+
+@register.simple_tag
+def time_difference(start_time, end_time):
+    return end_time - start_time
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
new file mode 100644
index 0000000..3ec2a30
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls import patterns, include, url
+
+
+urlpatterns = patterns('bldviewer.views',
+        url(r'^$', 'build', name='build'),
+)
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
new file mode 100644
index 0000000..15a2d1d
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -0,0 +1,11 @@
+from django.shortcuts import render
+from orm.models import Build
+
+
+def build(request):
+    template = 'build.html'
+    build_info = Build.objects.all()
+
+    context = {'builds': build_info}
+
+    return render(request, template, context)
diff --git a/bitbake/lib/webhob/whbmain/urls.py b/bitbake/lib/webhob/whbmain/urls.py
index 04e0352..a15fc1b 100644
--- a/bitbake/lib/webhob/whbmain/urls.py
+++ b/bitbake/lib/webhob/whbmain/urls.py
@@ -5,8 +5,8 @@ from django.conf.urls import patterns, include, url
 # admin.autodiscover()
 
 urlpatterns = patterns('',
+    url(r'^build/$', include('bldviewer.urls')),
     # Examples:
-    # url(r'^$', 'webhob.views.home', name='home'),
     # url(r'^webhob/', include('webhob.foo.urls')),
 
     # Uncomment the admin/doc line below to enable admin documentation:
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 09/94] bitbake: webhob: view a table with all the tasks of a build
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (7 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 08/94] bitbake: webhob: view a table with all builds Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 10/94] bitbake: webhob: use buildcompleted event as the end of a build operation Alex DAMIAN
                   ` (85 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch will display all tasks executed for a build.
There is detailed information about each task executed, if any.
In case there were no tasks executed for a build, a warning message
is displayed.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/task.html | 81 ++++++++++++++++++++++++
 bitbake/lib/webhob/bldviewer/urls.py             |  1 +
 bitbake/lib/webhob/bldviewer/views.py            | 11 +++-
 bitbake/lib/webhob/whbmain/urls.py               |  2 +-
 4 files changed, 93 insertions(+), 2 deletions(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/task.html

diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
new file mode 100644
index 0000000..26a09e9
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -0,0 +1,81 @@
+<html>
+
+	<head>
+		<title>WebHob Task page</title>
+	</head>
+
+<body>
+
+	<h1>WebHob Tasks</h1>
+
+	{% if not tasks %}
+		<p>No tasks were executed in this build!</p>
+	{% else %}
+
+		<table border="1">
+
+			<tr>
+			<th>Order</th>
+			<th>Task</th>
+			<th>Recipe</th>
+			<th>Recipe Version</th>
+			<th>Task Type</th>
+			<th>Outcome</th>
+			<th>Errors</th>
+			<th>Warnings</th>
+			<th>Time</th>
+			<th>Log</th>
+			<th>Work directory</th>
+			<th>CPU usage</th>
+			<th>Disk I/O</th>
+			<th>Sstate Checksum</th>
+			<th>Path to sstate object</th>
+			<th>Source URL</th>
+			<th>Script type</th>
+			<th>File path</th>
+			<th>Line number</th>
+			<th>Python Stack Trace</th>
+			<th>Sstate Result</th>
+			</tr>
+
+			{% for task in tasks %}
+
+				<tr>
+				<td>{{task.order}}</td>
+				<td>{{task.task_name}}</td>
+				<td>{{task.recipe.name}}</td>
+				<td>{{task.recipe.version}}</td>
+
+				{% if task.task_executed %}
+					<td>Executed</td>
+				{% else %}
+					<td>Prebuilt</td>
+				{% endif %}
+
+				<td>{{task.get_outcome_display}}</td>
+				<td>{{task.errors_no}}</td>
+				<td>{{task.warnings_no}}</td>
+				<td>{{task.elapsed_time}}</td>
+				<td>{{task.log_file}}</td>
+				<td>{{task.work_directory}}</td>
+				<td>{{task.cpu_usage}}</td>
+				<td>{{task.disk_io}}</td>
+				<td>{{task.sstate_checksum}}</td>
+				<td>{{task.path_to_sstate_obj}}</td>
+				<td>{{task.source_url}}</td>
+				<td>{{task.get_script_type_display}}</td>
+				<td>{{task.file_path}}</td>
+				<td>{{task.line_number}}</td>
+				<td>{{task.py_stack_trace}}</td>
+				<td>{{task.get_sstate_result_display}}</td>
+				</tr>
+
+			{% endfor %}
+
+		</table>
+
+	{% endif %}
+
+</body>
+
+</html>
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index 3ec2a30..335885c 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -3,4 +3,5 @@ from django.conf.urls import patterns, include, url
 
 urlpatterns = patterns('bldviewer.views',
         url(r'^$', 'build', name='build'),
+        url(r'^(?P<build_id>\d+)/$', 'task', name='task'),
 )
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 15a2d1d..292a638 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -1,5 +1,5 @@
 from django.shortcuts import render
-from orm.models import Build
+from orm.models import Build, Task
 
 
 def build(request):
@@ -9,3 +9,12 @@ def build(request):
     context = {'builds': build_info}
 
     return render(request, template, context)
+
+def task(request, build_id):
+    template = 'task.html'
+
+    tasks = Task.objects.filter(build=build_id)
+
+    context = {'tasks': tasks}
+
+    return render(request, template, context)
diff --git a/bitbake/lib/webhob/whbmain/urls.py b/bitbake/lib/webhob/whbmain/urls.py
index a15fc1b..c40e7c7 100644
--- a/bitbake/lib/webhob/whbmain/urls.py
+++ b/bitbake/lib/webhob/whbmain/urls.py
@@ -5,7 +5,7 @@ from django.conf.urls import patterns, include, url
 # admin.autodiscover()
 
 urlpatterns = patterns('',
-    url(r'^build/$', include('bldviewer.urls')),
+    url(r'^build/', include('bldviewer.urls')),
     # Examples:
     # url(r'^webhob/', include('webhob.foo.urls')),
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 10/94] bitbake: webhob: use buildcompleted event as the end of a build operation
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (8 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 09/94] bitbake: webhob: view a table with all the tasks of a build Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 11/94] bitbake: webhob: changes to build information table Alex DAMIAN
                   ` (84 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

The BuildCompleted event marks the end of a build, so the code will
use this timestamp for computing the duration of a build operation.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/dsi.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 54364ce..d5fe733 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -448,7 +448,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                              event.taskid, event.taskstring, event.exitcode)
                 continue
 
-            if isinstance(event, (bb.command.CommandFailed, bb.command.CommandExit, bb.command.CommandCompleted, bb.cooker.CookerExit)):
+            if isinstance(event, bb.event.BuildCompleted):
                 buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
                 main.shutdown = 2
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 11/94] bitbake: webhob: changes to build information table
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (9 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 10/94] bitbake: webhob: use buildcompleted event as the end of a build operation Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 12/94] bitbake: webhob: gather buildstats for each executed task Alex DAMIAN
                   ` (83 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

It was decided that disk_io and cpu usage information are not very
relevant for a full build, so these fields have been droped from
our models and replaced with output information (image_fstypes field)

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py              | 6 ++----
 bitbake/lib/webhob/bldviewer/templates/build.html | 6 ++----
 bitbake/lib/webhob/orm/models.py                  | 3 +--
 3 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 57e404b..e1ebd41 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -47,8 +47,7 @@ class ORMWrapper(object):
         build_info['outcome'] = 0
         build_info['number_of_errors'] = 0
         build_info['number_of_warnings'] = 0
-        build_info['cpu_usage'] = 0
-        build_info['disk_io'] = 0
+        build_info['image_fstypes'] = server.runCommand(["getVariable", "IMAGE_FSTYPES"])[0]
         build_info['cooker_log_path'] = server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
         build_info['build_name'] = server.runCommand(["getVariable", "BUILDNAME"])[0]
         build_info['bitbake_version'] = server.runCommand(["getVariable", "BB_VERSION"])[0]
@@ -69,8 +68,7 @@ class ORMWrapper(object):
                                     outcome=build_info['outcome'],
                                     errors_no=build_info['number_of_errors'],
                                     warnings_no=build_info['number_of_warnings'],
-                                    cpu_usage=build_info['cpu_usage'],
-                                    disk_io=build_info['disk_io'],
+                                    image_fstypes=build_info['image_fstypes'],
                                     cooker_log_path=build_info['cooker_log_path'],
                                     build_name=build_info['build_name'],
                                     bitbake_version=build_info['bitbake_version'])
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index 9a2d412..1b694d6 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -20,8 +20,7 @@
 			<th>Time</th>
 			<th>Errors</th>
 			<th>Warnings</th>
-			<th>CPU usage</th>
-			<th>Disk I/O</th>
+			<th>Output</th>
 			<th>Log</th>
 			<th>Bitbake Version</th>
 			<th>Build Name</th>
@@ -38,8 +37,7 @@
 			<td>{% time_difference build.started_on build.completed_on %}</td>
 			<td>{{build.errors_no}}</td>
 			<td>{{build.warnings_no}}</td>
-			<td>{{build.cpu_usage}}</td>
-			<td>{{build.disk_io}}</td>
+			<td>{{build.image_fstypes}}</td>
 			<td>{{build.cooker_log_path}}</td>
 			<td>{{build.bitbake_version}}</td>
 			<td>{{build.build_name}}</td>
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 6b8e2fa..f0492db 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -18,8 +18,7 @@ class Build(models.Model):
     outcome = models.IntegerField(choices=BUILD_OUTCOME)
     errors_no = models.IntegerField()
     warnings_no = models.IntegerField()
-    cpu_usage = models.IntegerField()
-    disk_io = models.DecimalField(max_digits=20, decimal_places=10)
+    image_fstypes = models.CharField(max_length=100)
     cooker_log_path = models.CharField(max_length=500)
     build_name = models.CharField(max_length=100)
     bitbake_version = models.CharField(max_length=50)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 12/94] bitbake: webhob: gather buildstats for each executed task
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (10 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 11/94] bitbake: webhob: changes to build information table Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 13/94] bitbake: webhob: set sane default settings Alex DAMIAN
                   ` (82 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch reads the buildstats file created for each
executed task and retrieves cpu usage and disk IO information
which it will store in the database.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 89 ++++++++++++++++++++++++++++++------
 bitbake/lib/bb/ui/dsi.py             |  4 +-
 bitbake/lib/webhob/orm/models.py     |  4 +-
 3 files changed, 78 insertions(+), 19 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index e1ebd41..6f3b5c2 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -9,17 +9,20 @@ class ORMWrapper(object):
         information in the database.
     """
 
-    def __init__(self):
+    def __init__(self, server):
         self.uuid = None
         self.task_order = 0
         self.transport_utils = {}
+        self.server = server
 
-    def get_machine_information(self, server):
+    def get_machine_information(self):
         machine_info = {}
 
-        machine_info['name'] = server.runCommand(["getVariable", "MACHINE"])[0]
+        machine_info['name'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
         machine_info['description'] = 'Not Available'
 
+        self.transport_utils['machine'] = machine_info['name']
+
         return machine_info
 
     def create_machine_object(self, machine_information):
@@ -31,7 +34,7 @@ class ORMWrapper(object):
 
         return machine[0]
 
-    def get_build_information(self, server, machine_obj):
+    def get_build_information(self, machine_obj):
         build_info = {}
 
         # Generate an identifier for each new build
@@ -40,17 +43,17 @@ class ORMWrapper(object):
         build_info['uuid'] = self.uuid
         build_info['target'] = 'Not Available'
         build_info['machine'] = machine_obj
-        build_info['distro'] = server.runCommand(["getVariable", "DISTRO"])[0]
-        build_info['distro_version'] = server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
+        build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
+        build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
         build_info['started_on'] = datetime.datetime.now()
         build_info['completed_on'] = datetime.datetime.now()
         build_info['outcome'] = 0
         build_info['number_of_errors'] = 0
         build_info['number_of_warnings'] = 0
-        build_info['image_fstypes'] = server.runCommand(["getVariable", "IMAGE_FSTYPES"])[0]
-        build_info['cooker_log_path'] = server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
-        build_info['build_name'] = server.runCommand(["getVariable", "BUILDNAME"])[0]
-        build_info['bitbake_version'] = server.runCommand(["getVariable", "BB_VERSION"])[0]
+        build_info['image_fstypes'] = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"])[0]
+        build_info['cooker_log_path'] = self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
+        build_info['build_name'] = self.server.runCommand(["getVariable", "BUILDNAME"])[0]
+        build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0]
 
         return build_info
 
@@ -175,6 +178,10 @@ class ORMWrapper(object):
         outcome_info = self.get_outcome_of_task(event)
         task.outcome = outcome_info['task_outcome']
 
+        task_build_stats = self.get_task_build_stats(task_object)
+        task.cpu_usage = task_build_stats['cpu_usage']
+        task.disk_io = task_build_stats['disk_io']
+
         if outcome_info.get('error', ''):
             task.error = outcome_info['error']
 
@@ -202,6 +209,57 @@ class ORMWrapper(object):
 
         return outcome_info
 
+    def get_task_build_stats(self, task_object):
+        bs_path = self.get_path_information(task_object)
+        task_build_stats = self.get_build_stats_from_file(bs_path, task_object.task_name)
+
+        return task_build_stats
+
+    def get_path_information(self, task_object):
+        build_stats_format = "{tmpdir}/buildstats/{target}-{machine}/{buildname}/{package}/"
+
+        tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
+        target = self.transport_utils['target']
+        machine = self.transport_utils['build'].machine.name
+        buildname = self.transport_utils['build'].build_name
+        package = task_object.recipe.name
+
+        build_stats_path = build_stats_format.format(tmpdir=tmp_dir, target=target,
+                                                     machine=machine, buildname=buildname,
+                                                     package=package)
+
+        return build_stats_path
+
+    def get_build_stats_from_file(self, bs_path, task_name):
+
+        task_bs_filename = str(bs_path) + str(task_name)
+        task_bs = open(task_bs_filename, 'r')
+
+        cpu_usage = 0
+        disk_io = 0
+        startio = ''
+        endio = ''
+
+        for line in task_bs.readlines():
+            if line.startswith('CPU usage: '):
+                cpu_usage = line[11:]
+            elif line.startswith('EndTimeIO: '):
+                endio = line[11:]
+            elif line.startswith('StartTimeIO: '):
+                startio = line[13:]
+
+        task_bs.close()
+
+        if startio and endio:
+            disk_io = int(endio.strip('\n ')) - int(startio.strip('\n '))
+
+        if cpu_usage:
+            cpu_usage = float(cpu_usage.strip('% \n'))
+
+        task_build_stats = {'cpu_usage': cpu_usage, 'disk_io': disk_io}
+
+        return task_build_stats
+
     def get_recipe_object(self, event):
         # This needs to be imported after we have configured the Django settings file
         from webhob.orm.models import Recipe
@@ -303,9 +361,9 @@ class BuildInfoHelper(object):
         towards the ORM wrapper for storing in the database
     """
 
-    def __init__(self):
+    def __init__(self, server):
         self.configure_django()
-        self.orm_wrapper = ORMWrapper()
+        self.orm_wrapper = ORMWrapper(server)
 
     def configure_django(self):
         import webhob.whbmain.settings as whb_django_settings
@@ -314,14 +372,15 @@ class BuildInfoHelper(object):
         # Add webhob to sys path for importing modules
         sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'webhob'))
 
-    def store_started_build(self, event, server):
+    def store_started_build(self, event):
 
-        machine_information = self.orm_wrapper.get_machine_information(server)
+        machine_information = self.orm_wrapper.get_machine_information()
         machine_obj = self.orm_wrapper.create_machine_object(machine_information)
 
-        build_information = self.orm_wrapper.get_build_information(server, machine_obj)
+        build_information = self.orm_wrapper.get_build_information(machine_obj)
         build_obj = self.orm_wrapper.create_build_object(build_information)
         self.orm_wrapper.transport_utils['build'] = build_obj
+        self.orm_wrapper.transport_utils['target'] = event.getPkgs()[0]
 
     def update_build_information(self, event, errors, warnings, taskfailures):
         self.orm_wrapper.update_build_object(self.orm_wrapper.transport_utils['build'], errors, warnings, taskfailures)
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index d5fe733..fb3595b 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -285,7 +285,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
     errors = 0
     warnings = 0
     taskfailures = []
-    buildinfohelper = BuildInfoHelper()
+    buildinfohelper = BuildInfoHelper(server)
 
     termfilter = tf(main, helper, console, format)
 
@@ -306,7 +306,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                     main.shutdown = 1
 
             if isinstance(event, bb.event.BuildStarted):
-                buildinfohelper.store_started_build(event, server)
+                buildinfohelper.store_started_build(event)
 
             if isinstance(event, bb.build.TaskStarted):
                 buildinfohelper.store_started_task(event)
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index f0492db..76b7c1d 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -61,8 +61,8 @@ class Task(models.Model):
     file_path = models.FilePathField(max_length=255)
     line_number = models.IntegerField()
     py_stack_trace = models.TextField()
-    disk_io = models.DecimalField(max_digits=20, decimal_places=10)
-    cpu_usage = models.IntegerField()
+    disk_io = models.IntegerField()
+    cpu_usage = models.DecimalField(max_digits=6, decimal_places=2)
     elapsed_time = models.CharField(max_length=50)
     errors_no = models.IntegerField()
     warnings_no = models.IntegerField()
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 13/94] bitbake: webhob: set sane default settings
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (11 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 12/94] bitbake: webhob: gather buildstats for each executed task Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 14/94] bitbake: webhob: adds dsi support for observer mode Alex DAMIAN
                   ` (81 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Changes the default Django settings as to point
to sane default of using sqlite by default. This
is the preffered running mode for webhob.

Changes the list of installed applications.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/whbmain/settings.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/bitbake/lib/webhob/whbmain/settings.py b/bitbake/lib/webhob/whbmain/settings.py
index 71bba4f..cd9dbe8 100644
--- a/bitbake/lib/webhob/whbmain/settings.py
+++ b/bitbake/lib/webhob/whbmain/settings.py
@@ -11,8 +11,8 @@ MANAGERS = ADMINS
 
 DATABASES = {
     'default': {
-        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
-        'NAME': '',                      # Or path to database file if using sqlite3.
+        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': 'webhob.sqlite',                      # Or path to database file if using sqlite3.
         'USER': '',
         'PASSWORD': '',
         'HOST': '127.0.0.1',                      # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
@@ -124,6 +124,8 @@ INSTALLED_APPS = (
     # Uncomment the next line to enable admin documentation:
     # 'django.contrib.admindocs',
     'orm',
+    'whbmain',
+    'bldviewer',
 )
 
 # A sample logging configuration. The only tangible logging
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 14/94] bitbake: webhob: adds dsi support for observer mode
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (12 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 13/94] bitbake: webhob: set sane default settings Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 15/94] bitbake: dsi: Translate runQueue events into Task data Alex DAMIAN
                   ` (80 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding support in the DSI user interface to remain resident
and run in observer-only mode.

Modifies the webhob launch script to automatically lunch
the DSI as to log all builds run under webhob setup.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/bin/webhob       |  5 ++++-
 bitbake/lib/bb/ui/dsi.py | 47 ++++++++++++++---------------------------------
 2 files changed, 18 insertions(+), 34 deletions(-)

diff --git a/bitbake/bin/webhob b/bitbake/bin/webhob
index ccaf91f..8513a3a 100755
--- a/bitbake/bin/webhob
+++ b/bitbake/bin/webhob
@@ -28,7 +28,7 @@ function webserverKillAllComponents()
 	for pidfile in ${BUILDDIR}/whbmain.pid; do
 		if [ -f ${pidfile} ]; then
 		while kill -0 $(< ${pidfile}) 2>/dev/null; do
-			kill -SIGTERM -`ps -p $(< ${pidfile}) -o "%r" --no-headers`	
+			kill -SIGTERM -$(< ${pidfile})
 			sleep 1;
 		done;
 		rm  ${pidfile}
@@ -84,9 +84,12 @@ case $CMD in
         python $BBBASEDIR/lib/webhob/manage.py syncdb || (echo "Failed db sync, stopping system start" 1>&2 && return 0)
         bitbake --server-only -t xmlrpc -B localhost:8200 
         export BBSERVER=localhost:8200
+        bitbake --observe-only -u dsi >/dev/null 2>&1 & echo $! >${BUILDDIR}/dsi.pid
         python $BBBASEDIR/lib/webhob/manage.py runserver </dev/null >${BUILDDIR}/whbmain.log 2>&1 & echo $! >${BUILDDIR}/whbmain.pid
     ;;
     stop )
+        kill $(< ${BUILDDIR}/dsi.pid )
+        rm ${BUILDDIR}/dsi.pid
         bitbake -m
         unset BBSERVER
         webserverKillAllComponents
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index fb3595b..5ff5696 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -217,22 +217,25 @@ class TerminalFilter(object):
             fd = sys.stdin.fileno()
             self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
 
-def main(server, eventHandler, params, tf = TerminalFilter):
-    print "DSI - Data Store Interface"
-
+def _log_settings_from_server(server):
     # Get values of variables which control our output
     includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
     if error:
         logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
-        return 1
+        raise BaseException(error)
     loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
     if error:
         logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
-        return 1
+        raise BaseException(error)
     consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
     if error:
         logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
-        return 1
+        raise BaseException(error)
+    return includelogs, loglines, consolelogfile
+
+def main(server, eventHandler, params, tf = TerminalFilter):
+
+    includelogs, loglines, consolelogfile = _log_settings_from_server(server)
 
     if sys.stdin.isatty() and sys.stdout.isatty():
         log_exec_tty = True
@@ -256,26 +259,6 @@ def main(server, eventHandler, params, tf = TerminalFilter):
         consolelog.setFormatter(conlogformat)
         logger.addHandler(consolelog)
 
-    try:
-        params.updateFromServer(server)
-        cmdline = params.parseActions()
-        if not cmdline:
-            print("Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
-            return 1
-        if 'msg' in cmdline and cmdline['msg']:
-            logger.error(cmdline['msg'])
-            return 1
-
-        ret, error = server.runCommand(cmdline['action'])
-        if error:
-            logger.error("Command '%s' failed: %s" % (cmdline, error))
-            return 1
-        elif ret != True:
-            logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
-            return 1
-    except xmlrpclib.Fault as x:
-        logger.error("XMLRPC Fault getting commandline:\n %s" % x)
-        return 1
 
     parseprogress = None
     cacheprogress = None
@@ -301,10 +284,6 @@ def main(server, eventHandler, params, tf = TerminalFilter):
 
             helper.eventHandler(event)
 
-            if isinstance(event, bb.runqueue.runQueueExitWait):
-                if not main.shutdown:
-                    main.shutdown = 1
-
             if isinstance(event, bb.event.BuildStarted):
                 buildinfohelper.store_started_build(event)
 
@@ -450,7 +429,6 @@ def main(server, eventHandler, params, tf = TerminalFilter):
 
             if isinstance(event, bb.event.BuildCompleted):
                 buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
-                main.shutdown = 2
 
             if isinstance(event, bb.event.ConfigParsed):
                 # timestamp should be added for this
@@ -496,12 +474,15 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 pass
         except KeyboardInterrupt:
             termfilter.clearFooter()
-            if main.shutdown == 1:
+            if params.observe_only:
+                print("\nKeyboard Interrupt, exiting observer...")
+                main.shutdown = 2
+            if not params.observe_only and main.shutdown == 1:
                 print("\nSecond Keyboard Interrupt, stopping...\n")
                 _, error = server.runCommand(["stateStop"])
                 if error:
                     logger.error("Unable to cleanly stop: %s" % error)
-            if main.shutdown == 0:
+            if not params.observe_only and main.shutdown == 0:
                 print("\nKeyboard Interrupt, closing down...\n")
                 interrupted = True
                 _, error = server.runCommand(["stateShutdown"])
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 15/94] bitbake: dsi: Translate runQueue events into Task data
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (13 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 14/94] bitbake: webhob: adds dsi support for observer mode Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 16/94] bitbake: webhob: disable synchronous sqlite connection Alex DAMIAN
                   ` (79 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We're moving the capture of task running information from
bb.build.Task* events to runQueue* events.

We handle runQueueTaskSkipped events to record all skipped
tasks.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/dsi.py | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 5ff5696..e527e56 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -287,11 +287,8 @@ def main(server, eventHandler, params, tf = TerminalFilter):
             if isinstance(event, bb.event.BuildStarted):
                 buildinfohelper.store_started_build(event)
 
-            if isinstance(event, bb.build.TaskStarted):
-                buildinfohelper.store_started_task(event)
-
             if isinstance(event, (bb.build.TaskSucceeded, bb.build.TaskFailedSilent, bb.build.TaskFailed)):
-                buildinfohelper.update_and_store_task(event)
+                continue
 
             if isinstance(event, bb.event.LogExecTTY):
                 if log_exec_tty:
@@ -416,13 +413,19 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                             event.stats.total, event.taskid, event.taskstring)
                 continue
 
+            if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.runQueueTaskSkipped)):
+                buildinfohelper.store_started_task(event)
+                continue
+
             if isinstance(event, bb.runqueue.runQueueTaskFailed):
+                buildinfohelper.update_and_store_task(event)
                 taskfailures.append(event.taskstring)
                 logger.error("Task %s (%s) failed with exit code '%s'",
                              event.taskid, event.taskstring, event.exitcode)
                 continue
 
             if isinstance(event, bb.runqueue.sceneQueueTaskFailed):
+                buildinfohelper.update_and_store_task(event)
                 logger.warn("Setscene task %s (%s) failed with exit code '%s' - real task will be run instead",
                              event.taskid, event.taskstring, event.exitcode)
                 continue
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 16/94] bitbake: webhob: disable synchronous sqlite connection
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (14 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 15/94] bitbake: dsi: Translate runQueue events into Task data Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 17/94] bitbake: buildinfohelper: clean-up the imports Alex DAMIAN
                   ` (78 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

SQLite has very bad INSERT performance in synchronous mode.
We disable the synchronous mode to improve performance only
on sqlite connections.

This requires also to change the way the build info
wrapper loads the Django settings.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py   |  3 +--
 bitbake/lib/webhob/whbmain/settings.py | 10 ++++++++++
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 6f3b5c2..e12750e 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -366,9 +366,8 @@ class BuildInfoHelper(object):
         self.orm_wrapper = ORMWrapper(server)
 
     def configure_django(self):
+        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
         import webhob.whbmain.settings as whb_django_settings
-        from django.core.management import setup_environ
-        setup_environ(whb_django_settings)
         # Add webhob to sys path for importing modules
         sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'webhob'))
 
diff --git a/bitbake/lib/webhob/whbmain/settings.py b/bitbake/lib/webhob/whbmain/settings.py
index cd9dbe8..aa1d4fc 100644
--- a/bitbake/lib/webhob/whbmain/settings.py
+++ b/bitbake/lib/webhob/whbmain/settings.py
@@ -20,6 +20,16 @@ DATABASES = {
     }
 }
 
+# If we're using sqlite, we need to tweak the performance a bit
+from django.db.backends.signals import connection_created
+def activate_synchronous_off(sender, connection, **kwargs):
+    if connection.vendor == 'sqlite':
+        cursor = connection.cursor()
+        cursor.execute('PRAGMA synchronous = 0;')
+connection_created.connect(activate_synchronous_off)
+#
+
+
 # Hosts/domain names that are valid for this site; required if DEBUG is False
 # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
 ALLOWED_HOSTS = []
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 17/94] bitbake: buildinfohelper: clean-up the imports
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (15 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 16/94] bitbake: webhob: disable synchronous sqlite connection Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 18/94] bitbake: buildinfohelper: clearing up task order Alex DAMIAN
                   ` (77 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

As per convention, we do all imports at the beginning of file.

Also, globally set Django settings in the Django interface file.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 15 +++++----------
 1 file changed, 5 insertions(+), 10 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index e12750e..f7f98d8 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -3,6 +3,11 @@ import sys
 import uuid
 
 
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
+
+import webhob.whbmain.settings as whb_django_settings
+from webhob.orm.models import Machine, Build, Task, Recipe, Build_Layer, Layer
+
 class ORMWrapper(object):
     """ This class creates the dictionaries needed to store information in the database
         following the format defined by the Django models. It is also used to save this
@@ -27,7 +32,6 @@ class ORMWrapper(object):
 
     def create_machine_object(self, machine_information):
         # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Machine
 
         machine = Machine.objects.get_or_create(name=machine_information['name'],
                                                 description=machine_information['description'])
@@ -59,7 +63,6 @@ class ORMWrapper(object):
 
     def create_build_object(self, build_info):
         # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Build
 
         build = Build.objects.create(uuid=build_info['uuid'],
                                     target=build_info['target'],
@@ -80,7 +83,6 @@ class ORMWrapper(object):
 
     def update_build_object(self, build_obj, errors, warnings, taskfailures):
         # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Build
 
         outcome = 0
         if errors or taskfailures:
@@ -128,8 +130,6 @@ class ORMWrapper(object):
 
     def create_task_object(self, task_information):
         # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Task
-
         identifier = task_information['recipe'].name + task_information['task_name']
 
         task_object = Task.objects.get_or_create(
@@ -262,7 +262,6 @@ class ORMWrapper(object):
 
     def get_recipe_object(self, event):
         # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Recipe
 
         recipe_information = self.get_recipe_information(event)
 
@@ -305,7 +304,6 @@ class ORMWrapper(object):
 
     def get_build_layer_object(self, event):
         # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Build_Layer
 
         build_layer_information = self.get_build_layer_information(event)
 
@@ -334,7 +332,6 @@ class ORMWrapper(object):
 
     def get_layer_object(self, event):
         # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Layer
 
         layer_information = self.get_layer_information(event)
 
@@ -366,8 +363,6 @@ class BuildInfoHelper(object):
         self.orm_wrapper = ORMWrapper(server)
 
     def configure_django(self):
-        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
-        import webhob.whbmain.settings as whb_django_settings
         # Add webhob to sys path for importing modules
         sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'webhob'))
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 18/94] bitbake: buildinfohelper: clearing up task order
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (16 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 17/94] bitbake: buildinfohelper: clean-up the imports Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 19/94] bitbake: dsi: saving prebuild task detailed information Alex DAMIAN
                   ` (76 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Moved code around so that the task order is issued only
on creating new task method, and not on get information.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index f7f98d8..c743751 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -130,11 +130,12 @@ class ORMWrapper(object):
 
     def create_task_object(self, task_information):
         # This needs to be imported after we have configured the Django settings file
+        self.task_order += 1
         identifier = task_information['recipe'].name + task_information['task_name']
 
         task_object = Task.objects.get_or_create(
                                 build=task_information['build'],
-                                order=task_information['order'],
+                                order=self.task_order,
                                 task_executed=task_information['task_executed'],
                                 outcome=task_information['outcome'],
                                 sstate_checksum=task_information['sstate_checksum'],
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 19/94] bitbake: dsi: saving prebuild task detailed information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (17 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 18/94] bitbake: buildinfohelper: clearing up task order Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 20/94] bitbake: dsi: clear up the state on build completion Alex DAMIAN
                   ` (75 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Changes to the internal structure of the database interface layer.

Clearing up the code to save information about prebuild tasks.

Clear up the event-processing fall-through code in DSI.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 54 +++++++++++++-----------------------
 bitbake/lib/bb/ui/dsi.py             |  9 ++++--
 2 files changed, 25 insertions(+), 38 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index c743751..a0da2e1 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -1,6 +1,7 @@
 import datetime
 import sys
 import uuid
+import bb
 
 
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
@@ -97,19 +98,24 @@ class ORMWrapper(object):
 
     def get_task_information(self, event):
 
-        self.task_order += 1
 
         recipe = self.get_recipe_object(event)
 
         task_information = {}
         task_information['build'] = self.transport_utils['build']
-        task_information['order'] = self.task_order
-        task_information['task_executed'] = True
-        task_information['outcome'] = 0
+        task_information['outcome'] = 4
+        if isinstance(event, bb.runqueue.runQueueTaskSkipped):
+            task_information['task_executed'] = False
+            if event._skip == "covered":
+                task_information['outcome'] = 2
+            if event._skip == "existing":
+                task_information['outcome'] = 0
+        else:
+            task_information['task_executed'] = True
         task_information['sstate_checksum'] = 'Not Available'
         task_information['path_to_sstate_obj'] = 'Not Available'
         task_information['recipe'] = recipe
-        task_information['task_name'] = event._task
+        task_information['task_name'] = event.taskname
         task_information['source_url'] = 'Not Available'
         task_information['log_file'] = 'Not Available'
         task_information['work_directory'] = 'Not Available'
@@ -165,21 +171,16 @@ class ORMWrapper(object):
 
     def update_task_object(self, task_dictionary, event):
         # This needs to be imported after we have configured the Django settings file
-        from webhob.orm.models import Task
-
-        task_object = task_dictionary['object']
 
-        task = Task.objects.get(build=task_object.build,
-                                task_name=task_object.task_name,
-                                recipe=task_object.recipe)
+        task = task_dictionary['object']
 
         duration = datetime.datetime.now() - task_dictionary['start_time']
         task.elapsed_time = duration.total_seconds()
 
-        outcome_info = self.get_outcome_of_task(event)
-        task.outcome = outcome_info['task_outcome']
+        if isinstance(event, bb.runqueue.runQueueTaskCompleted):
+            task.outcome = 3
 
-        task_build_stats = self.get_task_build_stats(task_object)
+        task_build_stats = self.get_task_build_stats(task)
         task.cpu_usage = task_build_stats['cpu_usage']
         task.disk_io = task_build_stats['disk_io']
 
@@ -192,24 +193,6 @@ class ORMWrapper(object):
 
         task.save()
 
-    def get_outcome_of_task(self, event):
-        from webhob.orm.models import Task
-
-        outcome_info = {}
-
-        task_result = event.getDisplayName().lower()
-        for outcome in Task.TASK_OUTCOME:
-            if task_result == outcome[1].lower():
-                task_outcome = outcome[0]
-
-        outcome_info['task_outcome'] = task_outcome
-
-        if isinstance(event, bb.build.TaskFailed):
-            if event.logfile and os.path.exists(event.logfile):
-                outcome_info['error'] = open(event.logfile, "r").read()
-
-        return outcome_info
-
     def get_task_build_stats(self, task_object):
         bs_path = self.get_path_information(task_object)
         task_build_stats = self.get_build_stats_from_file(bs_path, task_object.task_name)
@@ -288,7 +271,7 @@ class ORMWrapper(object):
         build_layer_obj = self.get_build_layer_object(event)
 
         recipe_info = {}
-        recipe_info['name'] = event._package
+        recipe_info['name'] = event.taskpackage
         recipe_info['version'] = 'Not Available'
         recipe_info['layer'] = build_layer_obj
         recipe_info['summary'] = 'Not Available'
@@ -373,9 +356,10 @@ class BuildInfoHelper(object):
         machine_obj = self.orm_wrapper.create_machine_object(machine_information)
 
         build_information = self.orm_wrapper.get_build_information(machine_obj)
+        build_information['target'] = ' '.join(event.getPkgs())
         build_obj = self.orm_wrapper.create_build_object(build_information)
         self.orm_wrapper.transport_utils['build'] = build_obj
-        self.orm_wrapper.transport_utils['target'] = event.getPkgs()[0]
+        self.orm_wrapper.transport_utils['target'] = build_information['target']
 
     def update_build_information(self, event, errors, warnings, taskfailures):
         self.orm_wrapper.update_build_object(self.orm_wrapper.transport_utils['build'], errors, warnings, taskfailures)
@@ -386,7 +370,7 @@ class BuildInfoHelper(object):
         task_obj = self.orm_wrapper.create_task_object(task_information)
 
     def update_and_store_task(self, event):
-        identifier = event._package + event._task
+        identifier = event.taskpackage + event.taskname
         task_dictionary = self.orm_wrapper.transport_utils[identifier]
         self.orm_wrapper.update_task_object(task_dictionary, event)
         del self.orm_wrapper.transport_utils[identifier]
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index e527e56..6264ecc 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -411,12 +411,17 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                             event.stats.completed + event.stats.active +
                                 event.stats.failed + 1,
                             event.stats.total, event.taskid, event.taskstring)
+                buildinfohelper.store_started_task(event)
                 continue
 
-            if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.runQueueTaskSkipped)):
+            if isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped)):
                 buildinfohelper.store_started_task(event)
                 continue
 
+            if isinstance(event, bb.runqueue.runQueueTaskCompleted):
+                buildinfohelper.update_and_store_task(event)
+                continue
+
             if isinstance(event, bb.runqueue.runQueueTaskFailed):
                 buildinfohelper.update_and_store_task(event)
                 taskfailures.append(event.taskstring)
@@ -430,8 +435,6 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                              event.taskid, event.taskstring, event.exitcode)
                 continue
 
-            if isinstance(event, bb.event.BuildCompleted):
-                buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
 
             if isinstance(event, bb.event.ConfigParsed):
                 # timestamp should be added for this
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 20/94] bitbake: dsi: clear up the state on build completion
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (18 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 19/94] bitbake: dsi: saving prebuild task detailed information Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 21/94] bitbake: webhob: add layer information to the database Alex DAMIAN
                   ` (74 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

In order to prevent cross-data between different builds,
we drop the build helper object which contains the state
of the current build when the build is completed.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/dsi.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 6264ecc..90dd732 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -458,6 +458,15 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 # trigger an error
                 continue
 
+            if isinstance(event, (bb.event.BuildCompleted,
+                                  bb.command.CommandCompleted,
+                                  bb.command.CommandFailed,
+                                  bb.command.CommandExit)):
+                buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
+                # we start a new build info
+                buildinfohelper = BuildInfoHelper(server)
+                continue
+
             # ignore
             if isinstance(event, (bb.event.BuildBase,
                                   bb.event.StampUpdate,
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 21/94] bitbake: webhob: add layer information to the database
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (19 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 20/94] bitbake: dsi: clear up the state on build completion Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 22/94] bitbake: webhob: mark private methods inside DSI Alex DAMIAN
                   ` (73 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch adds the layer information to the database. Using the
values stored in BBLAYERS it retrieves all the information needed
for the database fields.
It also includes a hardcoded mapping to deal with the URLs that
are different from the standard naming convention.
---
 bitbake/lib/bb/ui/buildinfohelper.py              | 45 +++++++++++++++++++++++
 bitbake/lib/webhob/bldviewer/templates/layer.html | 34 +++++++++++++++++
 bitbake/lib/webhob/bldviewer/urls.py              |  1 +
 bitbake/lib/webhob/bldviewer/views.py             | 10 ++++-
 4 files changed, 89 insertions(+), 1 deletion(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/layer.html

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index a0da2e1..2da38e2 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -336,6 +336,49 @@ class ORMWrapper(object):
 
         return layer_info
 
+    def _get_layer_dict(self, layer_path):
+
+        layer_info = {}
+        layer_name = layer_path.split('/')[-1]
+        layer_url = 'http://layers.openembedded.org/layerindex/layer/{layer}/'
+        layer_url_name = self._get_url_map_name(layer_name)
+
+        layer_info['name'] = layer_name
+        layer_info['local_path'] = layer_path
+        layer_info['layer_index_url'] = layer_url.format(layer=layer_url_name)
+
+        return layer_info
+
+    def _get_url_map_name(self, layer_name):
+        """ Some layers have a different name on openembedded.org site,
+            this method returns the correct name to use in the URL
+        """
+
+        url_name = layer_name
+        url_mapping = {'meta': 'openembedded-core'}
+
+        for key in url_mapping.keys():
+            if key == layer_name:
+                url_name = url_mapping[key]
+
+        return url_name
+
+    def _save_layer_object(self, layer_path):
+
+        layer_information = self._get_layer_dict(layer_path)
+
+        layer_object = Layer.objects.get_or_create(
+                            name=layer_information['name'],
+                            local_path=layer_information['local_path'],
+                            layer_index_url=layer_information['layer_index_url'])
+
+        layer_object[0].save()
+
+    def store_layer_info(self):
+        layers = self.server.runCommand(["getVariable", "BBLAYERS"])[0].strip().split(" ")
+        for layer_path in layers:
+            self._save_layer_object(layer_path)
+
 
 class BuildInfoHelper(object):
     """ This class gathers the build information from the server and sends it
@@ -352,6 +395,8 @@ class BuildInfoHelper(object):
 
     def store_started_build(self, event):
 
+        self.orm_wrapper.store_layer_info()
+
         machine_information = self.orm_wrapper.get_machine_information()
         machine_obj = self.orm_wrapper.create_machine_object(machine_information)
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/layer.html b/bitbake/lib/webhob/bldviewer/templates/layer.html
new file mode 100644
index 0000000..905a781
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/layer.html
@@ -0,0 +1,34 @@
+<html>
+
+	<head>
+		<title>WebHob Layer page</title>
+	</head>
+
+<body>
+	<h1>WebHob Layers</h1>
+
+	<table border="1">
+
+	{% load projecttags %}
+
+		<tr>
+			<th>Name</th>
+			<th>Local Path</th>
+			<th>Layer Index URL</th>
+		</tr>
+
+		{% for layer in layers %}
+
+		<tr>
+			<td>{{layer.name}}</td>
+			<td>{{layer.local_path}}</td>
+			<td><a href='{{layer.layer_index_url}}'>{{layer.layer_index_url}}</a></td>
+		</tr>
+
+		{% endfor %}
+
+	</table>
+
+</body>
+
+</html>
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index 335885c..8d5dc6e 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -3,5 +3,6 @@ from django.conf.urls import patterns, include, url
 
 urlpatterns = patterns('bldviewer.views',
         url(r'^$', 'build', name='build'),
+        url(r'^layers/$', 'layer', name='layer'),
         url(r'^(?P<build_id>\d+)/$', 'task', name='task'),
 )
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 292a638..f40d396 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -1,5 +1,5 @@
 from django.shortcuts import render
-from orm.models import Build, Task
+from orm.models import Build, Task, Layer
 
 
 def build(request):
@@ -18,3 +18,11 @@ def task(request, build_id):
     context = {'tasks': tasks}
 
     return render(request, template, context)
+
+def layer(request):
+    template = 'layer.html'
+    layer_info = Layer.objects.all()
+
+    context = {'layers': layer_info}
+
+    return render(request, template, context)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 22/94] bitbake: webhob: mark private methods inside DSI
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (20 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 21/94] bitbake: webhob: add layer information to the database Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 23/94] bitbake: webhob: create basic structure for static files Alex DAMIAN
                   ` (72 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch marks private methods used in the DSI.
It also cleans some old meaningless comments.
---
 bitbake/lib/bb/ui/buildinfohelper.py | 49 +++++++++++++++---------------------
 1 file changed, 20 insertions(+), 29 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 2da38e2..820e0f8 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -32,7 +32,6 @@ class ORMWrapper(object):
         return machine_info
 
     def create_machine_object(self, machine_information):
-        # This needs to be imported after we have configured the Django settings file
 
         machine = Machine.objects.get_or_create(name=machine_information['name'],
                                                 description=machine_information['description'])
@@ -63,7 +62,6 @@ class ORMWrapper(object):
         return build_info
 
     def create_build_object(self, build_info):
-        # This needs to be imported after we have configured the Django settings file
 
         build = Build.objects.create(uuid=build_info['uuid'],
                                     target=build_info['target'],
@@ -83,7 +81,6 @@ class ORMWrapper(object):
         return build
 
     def update_build_object(self, build_obj, errors, warnings, taskfailures):
-        # This needs to be imported after we have configured the Django settings file
 
         outcome = 0
         if errors or taskfailures:
@@ -98,8 +95,7 @@ class ORMWrapper(object):
 
     def get_task_information(self, event):
 
-
-        recipe = self.get_recipe_object(event)
+        recipe = self._get_recipe_object(event)
 
         task_information = {}
         task_information['build'] = self.transport_utils['build']
@@ -135,7 +131,6 @@ class ORMWrapper(object):
         return task_information
 
     def create_task_object(self, task_information):
-        # This needs to be imported after we have configured the Django settings file
         self.task_order += 1
         identifier = task_information['recipe'].name + task_information['task_name']
 
@@ -170,7 +165,6 @@ class ORMWrapper(object):
         return task_object[0]
 
     def update_task_object(self, task_dictionary, event):
-        # This needs to be imported after we have configured the Django settings file
 
         task = task_dictionary['object']
 
@@ -180,7 +174,7 @@ class ORMWrapper(object):
         if isinstance(event, bb.runqueue.runQueueTaskCompleted):
             task.outcome = 3
 
-        task_build_stats = self.get_task_build_stats(task)
+        task_build_stats = self._get_task_build_stats(task)
         task.cpu_usage = task_build_stats['cpu_usage']
         task.disk_io = task_build_stats['disk_io']
 
@@ -193,13 +187,13 @@ class ORMWrapper(object):
 
         task.save()
 
-    def get_task_build_stats(self, task_object):
-        bs_path = self.get_path_information(task_object)
-        task_build_stats = self.get_build_stats_from_file(bs_path, task_object.task_name)
+    def _get_task_build_stats(self, task_object):
+        bs_path = self._get_path_information(task_object)
+        task_build_stats = self._get_build_stats_from_file(bs_path, task_object.task_name)
 
         return task_build_stats
 
-    def get_path_information(self, task_object):
+    def _get_path_information(self, task_object):
         build_stats_format = "{tmpdir}/buildstats/{target}-{machine}/{buildname}/{package}/"
 
         tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
@@ -214,7 +208,7 @@ class ORMWrapper(object):
 
         return build_stats_path
 
-    def get_build_stats_from_file(self, bs_path, task_name):
+    def _get_build_stats_from_file(self, bs_path, task_name):
 
         task_bs_filename = str(bs_path) + str(task_name)
         task_bs = open(task_bs_filename, 'r')
@@ -244,10 +238,9 @@ class ORMWrapper(object):
 
         return task_build_stats
 
-    def get_recipe_object(self, event):
-        # This needs to be imported after we have configured the Django settings file
+    def _get_recipe_object(self, event):
 
-        recipe_information = self.get_recipe_information(event)
+        recipe_information = self._get_recipe_information(event)
 
         recipe_object = Recipe.objects.get_or_create(
                                 name=recipe_information['name'],
@@ -266,9 +259,9 @@ class ORMWrapper(object):
 
         return recipe_object[0]
 
-    def get_recipe_information(self, event):
+    def _get_recipe_information(self, event):
 
-        build_layer_obj = self.get_build_layer_object(event)
+        build_layer_obj = self._get_build_layer_object(event)
 
         recipe_info = {}
         recipe_info['name'] = event.taskpackage
@@ -286,10 +279,9 @@ class ORMWrapper(object):
 
         return recipe_info
 
-    def get_build_layer_object(self, event):
-        # This needs to be imported after we have configured the Django settings file
+    def _get_build_layer_object(self, event):
 
-        build_layer_information = self.get_build_layer_information(event)
+        build_layer_information = self._get_build_layer_information(event)
 
         build_layer_object = Build_Layer.objects.get_or_create(
                                     build = build_layer_information['build'],
@@ -301,9 +293,9 @@ class ORMWrapper(object):
 
         return build_layer_object[0]
 
-    def get_build_layer_information(self, event):
+    def _get_build_layer_information(self, event):
 
-        layer_object = self.get_layer_object(event)
+        layer_object = self._get_layer_object(event)
 
         build_layer_info = {}
         build_layer_info['build'] = self.transport_utils['build']
@@ -314,10 +306,9 @@ class ORMWrapper(object):
 
         return build_layer_info
 
-    def get_layer_object(self, event):
-        # This needs to be imported after we have configured the Django settings file
+    def _get_layer_object(self, event):
 
-        layer_information = self.get_layer_information(event)
+        layer_information = self._get_layer_information(event)
 
         layer_object = Layer.objects.get_or_create(
                                 name=layer_information['name'],
@@ -327,7 +318,7 @@ class ORMWrapper(object):
 
         return layer_object[0]
 
-    def get_layer_information(self, event):
+    def _get_layer_information(self, event):
 
         layer_info = {}
         layer_info['name'] = 'Layer Test Name'
@@ -386,10 +377,10 @@ class BuildInfoHelper(object):
     """
 
     def __init__(self, server):
-        self.configure_django()
+        self._configure_django()
         self.orm_wrapper = ORMWrapper(server)
 
-    def configure_django(self):
+    def _configure_django(self):
         # Add webhob to sys path for importing modules
         sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'webhob'))
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 23/94] bitbake: webhob: create basic structure for static files
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (21 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 22/94] bitbake: webhob: mark private methods inside DSI Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 24/94] bitbake: dsi: event data change Alex DAMIAN
                   ` (71 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch creates the basic structure for the front end
files needed by Webhob. It sets up a new application called
whbgui which will host all the static files we need.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/whbgui/__init__.py             |   0
 bitbake/lib/webhob/whbgui/static/images/yocto.jpg | Bin 0 -> 6582 bytes
 bitbake/lib/webhob/whbgui/templates/index.html    |  13 +++++++++++++
 bitbake/lib/webhob/whbgui/tests.py                |  16 ++++++++++++++++
 bitbake/lib/webhob/whbgui/urls.py                 |   9 +++++++++
 bitbake/lib/webhob/whbgui/views.py                |   8 ++++++++
 bitbake/lib/webhob/whbmain/settings.py            |   3 ++-
 bitbake/lib/webhob/whbmain/urls.py                |   1 +
 8 files changed, 49 insertions(+), 1 deletion(-)
 create mode 100644 bitbake/lib/webhob/whbgui/__init__.py
 create mode 100644 bitbake/lib/webhob/whbgui/static/images/yocto.jpg
 create mode 100644 bitbake/lib/webhob/whbgui/templates/index.html
 create mode 100644 bitbake/lib/webhob/whbgui/tests.py
 create mode 100644 bitbake/lib/webhob/whbgui/urls.py
 create mode 100644 bitbake/lib/webhob/whbgui/views.py

diff --git a/bitbake/lib/webhob/whbgui/__init__.py b/bitbake/lib/webhob/whbgui/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bitbake/lib/webhob/whbgui/static/images/yocto.jpg b/bitbake/lib/webhob/whbgui/static/images/yocto.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5de4b7693aa330bb8220baa81567bf82c307a8ab
GIT binary patch
literal 6582
zcmbW5cQBk^-^O?K5M`rxmW0?vlwCv#5nYHDYzVOl7OQPU4M8HJ_eAdz8{JCuC{e>=
zg{<C*9!s<+uix*Pr_H?YKkxUR`Oca9l<V9x=bY<vpL62*EPzQ@Q%4g31OfoSiwAH%
z0eA_xL`BWQ3}R(rVqs%tWoKvS=49t!=j0M(=jUPPU}NXt;^E^J1#@y;<>O@&loAxV
z4w1Ma!66_cFAJ3ym6U)0X=!O0=owg;m{_1ZEIbhCe}|l$xFi$`fyhCj@_*M{6d_Pq
z38<Vr<c2&10+Bv%0f1-#5kNFK5DXv#0m(tY^RED&iwTkg{|dlAgNmAll7gJ<uUbte
z0Pv!8iGqTb`qJMmfB-Ub3XmWr74;P<HKRu!%;4a7>D!*UEJ8426FW>uWtFgcLSl6*
zfn7#6G_S4w>k#r{9QOZK_)7^$M){XK=r3h5@_(xXfFN=~3MnJ9^KroCi}z$8auDDa
zpwS2f1TlgB??jy_wj^3S|8%%f8oudKH)9Kx5>BTR3ssb@hQ5snet*qn)7tL9UkItZ
zl9#dSwpe`Jb#foNSE4(J0H+T0lwu~W7N7-nUlR=Kz9!b(W#Ly3x>~(?^<>F_#pr(#
zrcS)RB#&B<Hq|B)<!UEdJe0!3O7XlmvIgN{kP3Nc6}89~`LIu?6B~71aVF0N?Pp8#
z{JDV1nTN5X<U@bjHg_x=BW>Nq3Z42=ANI_}I7W{Nsi*mZ{P~cNc-|J9rh)Lc3?kG!
zwaeS-pE34P%R$f@AQ1K2>p!Gv9;cbuYZ6xYRBagiOR$rZ!@wWnWifr2n3DJ?3xYc%
z`p@W5JD<jL+)M@>DY_tEPtbEqll(CJec?ch)iAi7)p)d|r%v<w!WA{4$B&51aa!sv
zB*^!Czgzt~8X7#05nA3$`;(Miiga>YWnmsmKOJ*qOmFsc0Hh=p(}o()xJ}LV)6qr&
z<pt1e1MR2tT9@hU4%-7MqJV2=GMRO|CRX!23p|Q>j|4!!2g>~BdNfA9j*MMPEq0qK
zDpu+a59&6@M(TZ+g!?;0rV|Ob23Pn!^N56JWPWtzy>`wfiy5B{jVFfh^5mdL+=czs
zode9(9<L$Ze*JZr^>Z4o5F;-16%wR%Y_MrH?nffWUrq<`c*FjrE!0Laif>pWw8Qhl
zzesCy(PgLttP)%R+&YRO#WL-BA{HHqFDhzfY9F2h?5FuHrymSHV1+@W>j}ywj^kFd
zgG#!w-&eK_?8j>}H+X~mUD>>QIW;Y(N{t{(-t4svy?>P0laf7PxiN_(x8dxmo>R!t
zY0=_Lk@r@?S#mbHAz9k%V3Iy8=Ny1LN#EN#{hm$89uK^G>>apfxN5n3@0W_s2Ooan
zc5|w@>a_@Lh=pxZy%w8=lah<Sl?QkNXi7Ru$)XGbu`NeePsbz#SA5rfAQ12}S}TLD
zOp2`6=zkOYfC-WpC_!fHllE)I3sUN8iM+F}f?llXoXTUK8Mm`e$W_f(%jLZw)wZL>
z$NGHLlUz#p-%JFCIL_OOqN)i6(XWT1TqQr9(VwvLCX*URq&dYNKaDx=q9QpGhq}em
z8b~S0Kf&-vDadm`|2yAC96x10mwjQg>~FSnfY_OJ(XMGOWY0pij%w27UD}|fq|T9w
zGdUzz<Z1uNXLGU#+s20?&f~n?Y0DYvX=>E(gvhGwHte@BrbD+7X}75k`@ivyeCe~;
z)wf3N8I|-5a8$j#^SKTJ*aV9_*;gnUMJ+5BU`4~jX~JKGhts|y?4wV(%0EXT#3D14
zEtNeMR(%>mn%^=ik!VWu6PVF<uzZmbpFP97UGbv#U3-$ZLmZdG$98JvF_lDLkh?Ey
zQ5Bo%6Eh`D*U#=jbjvnsnZ7IJn{g*%1=fZ~iM!7twB40jDIm9Q`*sxzeJD^KlQ=A|
zYw@0#r#!~(+^=RA+kL_{s?NNWG!~D$*xT$6lg(Be<h_tjcpTWrNF+z)&cj-ptzDx+
z^S7S)YgpkdLtQOqw=_5`KaP!$)+o<v>d?$FetySfZ21q}_6~Ztq50>0u7J2;e&fs`
z>tz>rT<l@b^CIBXpB$<~u|6HXUqA^sz$&qpzR>>U6#v?ML}50o<E@MM?9|q0yjMA)
z-ShKaO|m{GT(#Xn_W{Sx4V$yLx%ujOjq)aX?&bK(+k}J0u~O~<(pp&`Vd#kxJ!1)(
z@3WNbt%+{AA8P?2D~?)nEpL98vtxQ52c!)<#P>BYHuNLHwGUf$xGCRt;@q88`Ih>>
zDU|D&P8gE&wzfFcK6S68tB8Q8ten|4hc4J%)AdA`JQwXD@}qOM+z;GdmD*Tq_aFm2
zaSE|0=iq4uN_(D#9*F{MN@nM2dI=+21|k5VW|q#b@<yg$#N?g5*K$bPit|`ynUeON
zVeoX(H~7zW2$xGfd6RkVovj3>p;_J!0c;VythYrAk|%_a8Gf=?f+*R<l%*X|Ps764
zEu$;n#aa>-D)%^;`(kKIZ4lVEx(7Of4O#Ome6Dh0sN9pV@rW&g+A|ZJ%A}PJXr?e@
ztCTjsF4a%;2hzTmvcDNjmCo(D$&2$B8t|;Da&;bRQMs0OZTCAxR+*egzt@mvwxE7%
zW%OxWHnl^)Tbzxl8tG6`#W^4(fHxb~toGc9LQ?Mr?s0LqBS%*SV}aAa$J_s5Gxp_8
z3}1&6IiH2@By5teB*XvFH_Xv(@ze8yFB!#c(OOGU<hJBFR+s<a)zr0D-K7=lm<<l%
zTnq((B1JGkQ`dCQ;)I$w^2x9p=-)GMZdGw84PwvoytNNhwAhuuzLnn;p4YM7_||{P
z^JQVl&3Nglf&we}K?V7!$6Nzyg+D@ig$iel7cAw`fs=n7Lrso!yW|(fQ2FI;dE-4g
z@G2_nl$No4Zp+H70=yaGn67nU;FgN8cL2cpK$QSR5acZ+^F#^U8OQSZSm#I%fuxN9
zTY%xHeV2bMOw)ntPD+fo`n?HxI~w9nUB6;0BKbD1G(8Xdh9Dc@0%&^e>)5HeCt&mO
zcClSlx9>w;oiU|r1CPe)+WoD&V^1e>)0(~d#UTpw$~{~>3Lez&<oM_ykVG|K(|XR;
zjGzX0e?OJumgjRpjmcob9SyDv7GTF)j?d`yyBU+6hPK`aG6GQwy^osQz;c^&9M(0I
zF9}f0Y|HxMcFdNp>gB~JXnH;DqL%$7Fe$E_QOBA5v`Xz-*JtuwD=Ftj`iMR=Tx^6^
zhd?!P{s#jEH$Np3Rwxbk?10fZU!u3q#hm53y`LdttaDImNVYLEx_T!oDW<8}kvMuN
z3!MG(^vKoT_?zyJ0MjyLhQena1#b9Rx78vHHl*SfAcj+(-%&#`lC7US2x#@5TE@{v
zIj+k!Q{L&6N0=Wh?q^VYZ$&ba6|6zc{Cwl~WlJX?`;r{Vcf>Ny?`NWP3PaS^bk+X!
z-|C~({dfh}Y}>|}h(f5mdfm}(ku7ch{LbEkB#JR<I&>7rq&(Y|q2(!)`N9ZUFqkZN
zZw%mS%)r+OO8l;{`6bRRp_$E}u;lahdN-FV7%8;F?%%W})M}A@B|oqvN{MfHarS;R
zfnv1B%V6Qs0Wk=c<q1<rcu&*$;~R4jYLiZLr9&am0PA?n{wF<X@umF}4@WL3;)tSe
zg48UR_DP3H#P}f0vB^{fwXx`SOQ)Q)<(sEbAO81TzyhyP|H3YuM9P_Nc1ci&-GJ`b
zkAMEnTK|Dv7SNFb1+S_?%X7fG+nDo%layr30bSGgO2tM&R+S66NUeF=O31hTLv>Q7
z=rjCx{xQn-X)?k$iRH3;EWdHPibIb}B^C)@RsnAyMr27LV9~sj?v`1^oVyx@2Q}Fj
zy{;6<R~Ay=W_%^SE3TGLgo4ww+H`GtzwHDaQnAM`$V#He2mkoxPg^!o&7)hj$EthR
ziu8c$4tJp2^RBuwvv&<3kmf5AHr*)Z+0^|+*O2wKFEQwjn6{FGaWi!@<5fNz!BLNK
z!?|vW6fOKE)Hq^#SErVGWWn`gQId>=>Ml{C@U^W+CC?}1h*uRHRRzzQCdePj#hn92
zrj|&J{#`Seh4`+8-GLcvn_@6ng04c$|88m>cK?3o4X;%3+T++AUV<;oO6=3{LmSXA
zJo)t(Day%r!$EYlz}ZFJf}{Zrg)hIb%S{){&!Hh$ejmNd{-^ME4?<Ckv*DN6HCP*=
zXC|?jI8zT*qAk9XbjPTOUCC3jUG#k@y#sth1e2zox=r4aoXXcmP!Dzv76=WduvJ@T
zt1;tCspjm!mlmS8qc*tRD>k{PW=fQNK3>VMhv4Tl6e7G4m%<xjx+mdEzfeR&^*WnU
zcL63>27%~x;bMCy6d5+0O7_ho{*Q54+t(DG{p=LLx<iiG<L5G}TAe1k^iAec?S1iw
z60R1*Lfznmj2kCM)}^k|u6z!y8s868sz2SNp?gE+5CV?%RfL5fh0L$C`{8ADSL*P-
z5|}p<A}Ha>#QEkiJ<)GQf+^nhS`E$_&)eO3!ss^RNJ=#rIM=gvh3UK8lM8jZruwq5
zxW195hSwb}m#8DBLbYCuDpkyvTXOKwfM-K!<{2nGtIY+p)b+c;oy4Yz=>@a{zxyJ9
zt}Ndur8-u)J^_pHCf{)t{5(Az$*72n^*dgEZ|%iu>UEhSY*IP@2dcGyy%7Vn@-yzs
zx8z96V0@`5{;|5ob;`!kPRm$sPQ9t%3J;9{F@#27n}YY%eMFJ)6K$-W%b=2S3<szo
zTnH2Pt0_|AG0*IW{J<vJ!nfm7rQW#V;Po2As=(CuWW&Q!D=Sh<*{gyx5q4`!8pi$a
z<6V~oMb>qxX9=|mhI(cMGB8M0zNQe*CjmdW9#;O+az(lt@8~=xu0If;;C)rn15S;6
z)gM+|x$2c0tf7wkOfo%1o<Z3B5Qf@G*Id$#J0ojlEEsOSymbT}Ez$k5Cr5gqLa@P`
z&N(!83>-=OTZ5wX=rT(`mSQcuQ_`8#+8O<}pWKz!pL240ruDnP!}m1)(YC15vYAST
z=Oyuo%Fv<1I`NhSi-qnha|a<mf2J!HU^i{CfsHMx9P@xtR0T$(Vk_N8F(LcMP?cjg
zODO|#95L%18uI$nJ@d{8Ol!>zc$Pz8JhO{LP^0ahm3$nOG3&SKN|}>h;tJGjiuuh(
z!BKng>`tRk*<~4p7zSjd)v)_=0*#UyM+g>{CZ;EV^7CmyTiwlIv@S9_1CN)f=rwQ3
zm`u?6mbY!3xaO_lP50knTFXyqGk*X=WrEvgrz#`Sm0x!O9#&S%glIE8efMBP&X%==
zhgx!Tj|LgAG{uhPr=UfdyQ=&GxWuf%=(z?V>aB%?WRHBSZZDTS)Wk7_LGBdAmoq<c
ztkqt4OlExR!^NM<Usy{MxuevN)XP#w4C)9I0q}%-0adS@1>cZ*H}6Xo;*|hhUrCm4
z6-X27Vg;Fp&V1{E&YJXYFAfq4x%3Ch!x}1zgiUTt|4}rjk6CQ3_4$S4qOm%FMWHYm
zs?M4O0|2FEv1=s8f6l8P(=i`15F`_8Kf_M9heB{_VHn}BD243c(uz%xumj#HKn1U4
zlXj8?7<|Fpkdj|Rk71OAp?He-i=U>tvO?MTkPNzIy<^GN28Jko5_KuZCU9k{j{F0i
z9@q(aRk?s$p2-LFWHqm+S|5GcSZ6~De-Iop5?=N$ch_G7hGB9--{n_SSjktb*ioUK
z>u;PW%8RXj|D=3XX?`V3Lh;yzt}H_0g7xz(XWp@l{P3l^-kkgb*>2syyQTEKhRI>0
zI&bYzXM;F4wwo`&&sN`gDv=LUxbj{Gb$!{*BHK;T(&?$W38zjf+|GsgMC3*E$}hTd
zwN%z!Kb9&6H?mv!+l1~kyY%ifp7>E%H7wL0yv_X0vaP-Q<Fl|N4Q?US)e=X?Q8_qS
z+lsHvQ={9g3R%a{IPKLcA4cSxYuqm^3`Fi_(D|ZyMmoc#M-1FXG+mBSt$#$aeb^w|
z>S@W<+**24%gbzxau(uB5o>nu`^34vw;J)@y^{xS5A4sJpR1_B@2P?hYK~vI<cXGF
zGwrD~z4H^xA&mTl`x8E^#e{_}53!V7-DBLe+;bBw;|XnI_nGLhrU`{EU4CT(#O;x)
zQ<0^AR3A*&%{f25_}0*C_DxUJ4%1N9eK=g;aEbC-Xa8N0#T)6!(9io&`KSH|dwfn~
zcZT_I`fzoIX%3F!EMR#p3;_DbpZ{Ec;UU%E(jG>LD8!j`AK+552tzN_D%m=^_|)##
z4P=>_bq+TVP0wHRM#$Uv#~KH)6zN$O36wIM+&QAuyjp_Hbie`J0TUJ$zelY~BJ*jF
zx+QIk+&CHFi>;VI&EBf84o*xcf-XZF0QA%$0;bL8@}aGVqO34gt`NnN31Wy84;_BD
z!FDEa)Y1MbjL|oz2UeN9UpvnMzu|vWYz+O&yukt&MDzbnWwM!BUZ!SDU3z0<8^Y@q
zwKIseQFp7Gjc4Cj3d>6hZKKkW-09Ev85sr3))+NN!;zbo`aW7g*CiSiCbCIO92!uT
zj$AwneogCU=UgeU7U|+)#?I!l)OmkcI!{2=Yiqcsj0%tXX>O!}bB>$QQSDx4i|-h~
z1frgG{sz$P)+c?yo&$92W{gx(ujCwd^3dpKZ~b^vN=xS4bf%ZM->Q)Mpf0<FE@C<_
zlKF+eJVW(EFSpwGrsq#jSB;()pP+v5Lszd725I4{oIa`CSt}1`0Sq51YBq5htQwzF
z$9j8@AIaL{LJ<2rURD5M8=p!FKIhupdT9Tc=+Ex#OILk;(c2$jh*4Y?oL$-XdP2m`
zlc-`=S;;hSr^-;~E_Z<2#o2?cdm5kQv?$(_)YRG$rsX;6)y_FQ-JIrE?tUmMa6prw
z#USid(`@dYR#|N#+@+_(Vgvv<3U6b=8!~(nj977gTD`KKR7*D>&P{VzZ26VdA&>n>
zisDHnX7{Qbc}u#Y7%wp3D-Vs$<&_?(5#w){bC{O7c{knJonMnNh<zGVb}lyoDi0M4
zEG|4#?4vpo_-xLvNQzwjS(FBG?&8FFz3S)!dAK*wDiqixtey4D^sp`rWqI7S);)59
z!Ct<~H!93)EmgcEKVP_3Kj?kPsWLT_jP1A$9@qXx7&6-beq4W=&pqQ>QQ7pXb~fib
z=geFC=qh#nuF44>a0`)mRvrEQ?OmH|=YUN9KS^4$xQyPL+Qm}_2PxFvp)aVmB*t8w
z^1R;p>Rs18(lmZ~XOMF&uKz)Tb?G|yG1jg&H8%rX|IT2PqZN~m)fTBo2e6|xJa+Tt
z=W<{4D=dyO>TK1tKR5?8B%}BOo<tB<?wJ5Nh!6Na2gfhUHpBsa_qt>Xd!Ozt#<g^B
zj_O$b;QK^RGQUbMBp4lkXK++1x72ZxiZ<^<{y;$a<(&?5o$_&oQb6bO6`hKa_=4&(
z>edIXK{PfataINIdV5is`ctTM=00@I?<_7o$}&j|dd|#JOUZK)lqymb1E;gbM*=aA
zj`3m(_ogIHlI3dHcevi%>r2h@^~_TtWMwIEdcxA#*ts>OA?mO-G2wzYzoWiqgq978
zBTH1<mWdDiq8*WopH54@q#DG9fWiN;v%fv<U+H=#JSe7c|JI^8i<B(GPjdPeN`uwv
z6EDfia{#!`vI2bJgApHO&jG7O{;*rWf0(Oa+~yR(myrD26jF^AcFX(dP3{4PgQ?{@
zM9|)crnKziHGKcvF|YDjh~!+2sfMgZ#+OS48;|8u=;X#7K5TasjWz#PGF6G7ulg41
z&Q%?G`Nk;K)D1p08(I*Mu;tubmYL8(ShXG9iY`NzmQi1U(%11p65ov*y-@9Tp0qe@
zag2lE8Oedq@99}l2DeQdL5z%#T?(SBD*KRJD}Dy_fQDkSCFA<B?Lv;rj1j^31RWqM
z(X9V6g+<Cbz-o)*!7poNrsam!>od^Jk%Z?DSI0+Xz13!QB;Zr-dE?Ca(WLH`$`}k9
zJ3pxBKB-;%%ltOey=chN&qsL)mo9BO3Asm6CHk|yzVeLD3a!jCWy<Bn&#xYM<;>9(
z(v-`g-LvJvq2iE|B>*4<wDQrmJ`BjFvt+(F-EpuSSPtL@5kPFB`M{ReeD@r1^VWfc
zirq@HK~YYaSgO|($7dBASG~#(K+`Udd1$5!5EizwYy$kzv2GFXn;8b)%?M!44$^&x
zwJaWvUx)n$Nbn}bPatOfT+sWxk@eV8`c8@wt{jF1IGtYy0t+>(x9=|&-dMLqVe(u{
z(i+s&B%QmM2-bn0C(VoCxEkG`2uNgJ5`bs6tEv2R2@D86p=pn(GUI=LQ*<Hoy*#mP
z!wNAtacq%(+3Ze(DC*gpS9u`eoVuI_(@w5SK>Rj6@)5=viSBJhH4E8&kcB1AKc7C#
z$Aon$L<^&d+O_6nFInXDzFfV#sd1uNoDi|9kh8d|28itXJmCb%&W%l&*snT!a`WCP
aOU31^K>#@}(ZTG-?Z2-R|4z5g$NmTTvPqNx

literal 0
HcmV?d00001

diff --git a/bitbake/lib/webhob/whbgui/templates/index.html b/bitbake/lib/webhob/whbgui/templates/index.html
new file mode 100644
index 0000000..80b02d9
--- /dev/null
+++ b/bitbake/lib/webhob/whbgui/templates/index.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+	<title>GUI Page</title>
+</head>
+<body>
+{% load staticfiles %}
+
+<img src="{% static "/static/images/yocto.jpg" %}" alt="Yocto"/>
+
+This is your basic index page!
+</body>
+
+</html>
\ No newline at end of file
diff --git a/bitbake/lib/webhob/whbgui/tests.py b/bitbake/lib/webhob/whbgui/tests.py
new file mode 100644
index 0000000..501deb7
--- /dev/null
+++ b/bitbake/lib/webhob/whbgui/tests.py
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.assertEqual(1 + 1, 2)
diff --git a/bitbake/lib/webhob/whbgui/urls.py b/bitbake/lib/webhob/whbgui/urls.py
new file mode 100644
index 0000000..321955a
--- /dev/null
+++ b/bitbake/lib/webhob/whbgui/urls.py
@@ -0,0 +1,9 @@
+import os
+
+from django.conf import settings
+from django.conf.urls import patterns, include, url
+
+
+urlpatterns = patterns('whbgui.views',
+        url(r'^$', 'guihome', name='guihome'),
+)
diff --git a/bitbake/lib/webhob/whbgui/views.py b/bitbake/lib/webhob/whbgui/views.py
new file mode 100644
index 0000000..e53a776
--- /dev/null
+++ b/bitbake/lib/webhob/whbgui/views.py
@@ -0,0 +1,8 @@
+from django.shortcuts import render
+from orm.models import Build, Task
+
+
+def guihome(request):
+    template = 'index.html'
+
+    return render(request, template)
diff --git a/bitbake/lib/webhob/whbmain/settings.py b/bitbake/lib/webhob/whbmain/settings.py
index aa1d4fc..d470328 100644
--- a/bitbake/lib/webhob/whbmain/settings.py
+++ b/bitbake/lib/webhob/whbmain/settings.py
@@ -128,7 +128,7 @@ INSTALLED_APPS = (
     #'django.contrib.sessions',
     #'django.contrib.sites',
     #'django.contrib.messages',
-    #'django.contrib.staticfiles',
+    'django.contrib.staticfiles',
     # Uncomment the next line to enable the admin:
     # 'django.contrib.admin',
     # Uncomment the next line to enable admin documentation:
@@ -136,6 +136,7 @@ INSTALLED_APPS = (
     'orm',
     'whbmain',
     'bldviewer',
+    'whbgui',
 )
 
 # A sample logging configuration. The only tangible logging
diff --git a/bitbake/lib/webhob/whbmain/urls.py b/bitbake/lib/webhob/whbmain/urls.py
index c40e7c7..af536aa 100644
--- a/bitbake/lib/webhob/whbmain/urls.py
+++ b/bitbake/lib/webhob/whbmain/urls.py
@@ -6,6 +6,7 @@ from django.conf.urls import patterns, include, url
 
 urlpatterns = patterns('',
     url(r'^build/', include('bldviewer.urls')),
+    url(r'^gui/', include('whbgui.urls')),
     # Examples:
     # url(r'^webhob/', include('webhob.foo.urls')),
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 24/94] bitbake: dsi: event data change
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (22 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 23/94] bitbake: webhob: create basic structure for static files Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 25/94] bitbake: webhob: fix and cleanup start script Alex DAMIAN
                   ` (70 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Changes in the BuildInfoHelper and DSI as to record task
file names and task names coming through the bb.build.Task*
events.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 22 +++++++++++++---------
 bitbake/lib/bb/ui/dsi.py             |  1 +
 2 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 820e0f8..e43aa8c 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -116,7 +116,7 @@ class ORMWrapper(object):
         task_information['log_file'] = 'Not Available'
         task_information['work_directory'] = 'Not Available'
         task_information['script_type'] = 0
-        task_information['file_path'] = 'Not Available'
+        task_information['file_path'] = event.taskfile
         task_information['line_number'] = 0
         task_information['py_stack_trace'] = 'Not Available'
         task_information['disk_io'] = 0
@@ -132,7 +132,7 @@ class ORMWrapper(object):
 
     def create_task_object(self, task_information):
         self.task_order += 1
-        identifier = task_information['recipe'].name + task_information['task_name']
+        identifier = task_information['recipe'].file_path + task_information['task_name']
 
         task_object = Task.objects.get_or_create(
                                 build=task_information['build'],
@@ -173,14 +173,16 @@ class ORMWrapper(object):
 
         if isinstance(event, bb.runqueue.runQueueTaskCompleted):
             task.outcome = 3
+            task.save()
+
+        if isinstance(event, bb.build.TaskBase):
+            task.recipe.name = event._package
+            task.recipe.save()
 
         task_build_stats = self._get_task_build_stats(task)
         task.cpu_usage = task_build_stats['cpu_usage']
         task.disk_io = task_build_stats['disk_io']
 
-        if outcome_info.get('error', ''):
-            task.error = outcome_info['error']
-
         #TODO: get error number
         #TODO: get warnings number
         #TODO: get warning information
@@ -264,7 +266,7 @@ class ORMWrapper(object):
         build_layer_obj = self._get_build_layer_object(event)
 
         recipe_info = {}
-        recipe_info['name'] = event.taskpackage
+        recipe_info['name'] = 'N/A'
         recipe_info['version'] = 'Not Available'
         recipe_info['layer'] = build_layer_obj
         recipe_info['summary'] = 'Not Available'
@@ -275,7 +277,8 @@ class ORMWrapper(object):
         recipe_info['homepage'] = 'Not Available'
         recipe_info['bugtracker'] = 'Not Available'
         recipe_info['author'] = 'Not Available'
-        recipe_info['file_path'] = 'Not Available'
+        recipe_info['file_path'] = event.taskfile
+
 
         return recipe_info
 
@@ -406,7 +409,8 @@ class BuildInfoHelper(object):
         task_obj = self.orm_wrapper.create_task_object(task_information)
 
     def update_and_store_task(self, event):
-        identifier = event.taskpackage + event.taskname
+        identifier = event.taskfile + event.taskname
         task_dictionary = self.orm_wrapper.transport_utils[identifier]
         self.orm_wrapper.update_task_object(task_dictionary, event)
-        del self.orm_wrapper.transport_utils[identifier]
+        if isinstance(event,bb.runqueue.runQueueTaskCompleted):
+            del self.orm_wrapper.transport_utils[identifier]
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 90dd732..22b35e2 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -288,6 +288,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 buildinfohelper.store_started_build(event)
 
             if isinstance(event, (bb.build.TaskSucceeded, bb.build.TaskFailedSilent, bb.build.TaskFailed)):
+                buildinfohelper.update_and_store_task(event)
                 continue
 
             if isinstance(event, bb.event.LogExecTTY):
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 25/94] bitbake: webhob: fix and cleanup start script
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (23 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 24/94] bitbake: dsi: event data change Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 26/94] bitbake: dsi: refactor the BuildInfoHelper code Alex DAMIAN
                   ` (69 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Make sure that the script can start without a bitbake.lock
file present.
Fixes [YOCTO #4873].

Also, clean up whitespace.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/bin/webhob | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/bitbake/bin/webhob b/bitbake/bin/webhob
index 8513a3a..44a25cb 100755
--- a/bitbake/bin/webhob
+++ b/bitbake/bin/webhob
@@ -21,8 +21,8 @@
 # use as:  source webhob [start|stop]
 
 # Helper function to kill a background webhob development server
-        
-function webserverKillAllComponents() 
+
+function webserverKillAllComponents()
 {
 	local pidfile
 	for pidfile in ${BUILDDIR}/whbmain.pid; do
@@ -42,7 +42,7 @@ if [ -z "$ZSH_NAME" ] && [ `basename "$0"` = `basename $BASH_SOURCE` ]; then
     echo "Error: This script needs to be sourced. Please run as 'source webhob [start|stop]'" 1>&2;
     exit 1
 fi
-    
+
 if [ -z "$BUILDDIR" ] || [ -z `which bitbake` ]; then
     echo "Error: Build environment is not setup or bitbake is not in path." 1>&2;
     return 2
@@ -64,15 +64,17 @@ fi
 # Make sure it's safe to run by checking bitbake lock
 
 lock=1
-(flock -n 200 ) 200<$BUILDDIR/bitbake.lock || lock=0 
+if [ -e $BUILDDIR/bitbake.lock ]; then
+    (flock -n 200 ) 200<$BUILDDIR/bitbake.lock || lock=0
+fi
 
 if [ ${CMD} == "start" ] && ( [ $lock -eq 0 ] || [ -e $BUILDDIR/whbmain.pid ] ); then
     echo "Error: bitbake lock state error. System may be already on." 2>&1
-    return 3 
+    return 3
 elif [ ${CMD} == "stop" ] && ( [ $lock -eq 1 ] || ! [ -e $BUILDDIR/whbmain.pid ] ) ; then
     echo "Error: bitbake lock state error. System may be already off.
 manually stop system with bitbake -m / webserverKillAllComponents" 2>&1
-    return 3 
+    return 3
 fi
 
 
@@ -82,7 +84,7 @@ case $CMD in
     start )
         unset BBSERVER
         python $BBBASEDIR/lib/webhob/manage.py syncdb || (echo "Failed db sync, stopping system start" 1>&2 && return 0)
-        bitbake --server-only -t xmlrpc -B localhost:8200 
+        bitbake --server-only -t xmlrpc -B localhost:8200
         export BBSERVER=localhost:8200
         bitbake --observe-only -u dsi >/dev/null 2>&1 & echo $! >${BUILDDIR}/dsi.pid
         python $BBBASEDIR/lib/webhob/manage.py runserver </dev/null >${BUILDDIR}/whbmain.log 2>&1 & echo $! >${BUILDDIR}/whbmain.pid
@@ -95,4 +97,4 @@ case $CMD in
         webserverKillAllComponents
 esac
 
-    
+
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 26/94] bitbake: dsi: refactor the BuildInfoHelper code
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (24 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 25/94] bitbake: webhob: fix and cleanup start script Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 27/94] bitbake: webhob: record dependency info Alex DAMIAN
                   ` (68 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This is a in-depth refactoring of the BuildInfoHelper code
designed to promote clean separation of code behinnd the
data interfaces.

ORMWrapper now contains only database-related code.
BuildInfoHelper keeps track of the internal state, and
translated the Bitbake event-based data into dictionaries
that the ORM layer can process.

DSI now sets featureSets to get a complete dependency dump.

The code uses the dependency information dump to create
all the recipe and task entries before actual running the
build, providing safe default.

The database models have been updated to allow for NULL
values instead of creating generic placeholders.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py             | 546 ++++++++++++-----------
 bitbake/lib/bb/ui/dsi.py                         |  14 +
 bitbake/lib/webhob/bldviewer/templates/task.html |   2 +-
 bitbake/lib/webhob/orm/models.py                 |  67 +--
 4 files changed, 333 insertions(+), 296 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index e43aa8c..07296d9 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -2,12 +2,13 @@ import datetime
 import sys
 import uuid
 import bb
+import re
 
 
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 
 import webhob.whbmain.settings as whb_django_settings
-from webhob.orm.models import Machine, Build, Task, Recipe, Build_Layer, Layer
+from webhob.orm.models import Machine, Build, Task, Recipe, Build_Layer, Layer, Package, Task_Dependency, Package_Dependency
 
 class ORMWrapper(object):
     """ This class creates the dictionaries needed to store information in the database
@@ -15,21 +16,8 @@ class ORMWrapper(object):
         information in the database.
     """
 
-    def __init__(self, server):
-        self.uuid = None
-        self.task_order = 0
-        self.transport_utils = {}
-        self.server = server
-
-    def get_machine_information(self):
-        machine_info = {}
-
-        machine_info['name'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
-        machine_info['description'] = 'Not Available'
-
-        self.transport_utils['machine'] = machine_info['name']
-
-        return machine_info
+    def __init__(self):
+        pass
 
     def create_machine_object(self, machine_information):
 
@@ -38,29 +26,6 @@ class ORMWrapper(object):
 
         return machine[0]
 
-    def get_build_information(self, machine_obj):
-        build_info = {}
-
-        # Generate an identifier for each new build
-        self.uuid = str(uuid.uuid4())
-
-        build_info['uuid'] = self.uuid
-        build_info['target'] = 'Not Available'
-        build_info['machine'] = machine_obj
-        build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
-        build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
-        build_info['started_on'] = datetime.datetime.now()
-        build_info['completed_on'] = datetime.datetime.now()
-        build_info['outcome'] = 0
-        build_info['number_of_errors'] = 0
-        build_info['number_of_warnings'] = 0
-        build_info['image_fstypes'] = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"])[0]
-        build_info['cooker_log_path'] = self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
-        build_info['build_name'] = self.server.runCommand(["getVariable", "BUILDNAME"])[0]
-        build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0]
-
-        return build_info
-
     def create_build_object(self, build_info):
 
         build = Build.objects.create(uuid=build_info['uuid'],
@@ -70,14 +35,12 @@ class ORMWrapper(object):
                                     distro_version=build_info['distro_version'],
                                     started_on=build_info['started_on'],
                                     completed_on=build_info['completed_on'],
-                                    outcome=build_info['outcome'],
-                                    errors_no=build_info['number_of_errors'],
-                                    warnings_no=build_info['number_of_warnings'],
                                     image_fstypes=build_info['image_fstypes'],
                                     cooker_log_path=build_info['cooker_log_path'],
                                     build_name=build_info['build_name'],
                                     bitbake_version=build_info['bitbake_version'])
 
+
         return build
 
     def update_build_object(self, build_obj, errors, warnings, taskfailures):
@@ -93,12 +56,178 @@ class ORMWrapper(object):
         build.outcome = outcome
         build.save()
 
-    def get_task_information(self, event):
 
-        recipe = self._get_recipe_object(event)
+    def get_update_task_object(self, task_information):
+        task_object, created = Task.objects.get_or_create(
+                                build=task_information['build'],
+                                recipe=task_information['recipe'],
+                                task_name=task_information['task_name'],
+                                )
+
+        for v in vars(task_object):
+            if v in task_information.keys():
+                vars(task_object)[v] = task_information[v]
+
+        if 'start_time' in task_information.keys():
+            duration = datetime.datetime.now() - task_information['start_time']
+            task_object.elapsed_time = duration.total_seconds()
+
+        task_object.save()
+        return task_object
+
+
+    def get_update_recipe_object(self, recipe_information):
+
+        recipe_object, created = Recipe.objects.get_or_create(
+                                         build_layer=recipe_information['build_layer'],
+                                         file_path=recipe_information['file_path'])
+
+        for v in vars(recipe_object):
+            if v in recipe_information.keys():
+                vars(recipe_object)[v] = recipe_information[v]
+
+        recipe_object.save()
+
+        return recipe_object
+
+    def get_build_layer_object(self, build_layer_information):
+
+        build_layer_object = Build_Layer.objects.get_or_create(
+                                    build = build_layer_information['build'],
+                                    layer = build_layer_information['layer'],
+                                    branch = build_layer_information['branch'],
+                                    commit = build_layer_information['commit'],
+                                    priority = build_layer_information['priority']
+                                    )
+
+        build_layer_object[0].save()
+
+        return build_layer_object[0]
+
+    def get_update_layer_object(self, layer_information):
+
+        layer_object = Layer.objects.get_or_create(
+                                name=layer_information['name'],
+                                local_path=layer_information['local_path'],
+                                layer_index_url=layer_information['layer_index_url'])
+        layer_object[0].save()
+
+        return layer_object[0]
+
+
+class BuildInfoHelper(object):
+    """ This class gathers the build information from the server and sends it
+        towards the ORM wrapper for storing in the database
+        It is instantiated once per build
+        Keeps in memory all data that needs matching before writing it to the database
+    """
+
+    def __init__(self, server):
+        self._configure_django()
+        self.internal_state = {}
+        self.uuid = None
+        self.task_order = 0
+        self.server = server
+        self.orm_wrapper = ORMWrapper()
+
+    def _configure_django(self):
+        # Add webhob to sys path for importing modules
+        sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'webhob'))
+
+    ###################
+    ## methods to convert event/external info into objects that the ORM layer uses
+
+    def _get_machine_information(self):
+        machine_info = {}
+        machine_info['name'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
+        machine_info['description'] = 'Not Available'
+        self.internal_state['machine'] = machine_info['name']
+        return machine_info
+
+    def _get_layer_dict(self, layer_path):
+
+        layer_info = {}
+        layer_name = layer_path.split('/')[-1]
+        layer_url = 'http://layers.openembedded.org/layerindex/layer/{layer}/'
+        layer_url_name = self._get_url_map_name(layer_name)
+
+        layer_info['name'] = layer_name
+        layer_info['local_path'] = layer_path
+        layer_info['layer_index_url'] = layer_url.format(layer=layer_url_name)
+
+        return layer_info
+
+    def _get_url_map_name(self, layer_name):
+        """ Some layers have a different name on openembedded.org site,
+            this method returns the correct name to use in the URL
+        """
+
+        url_name = layer_name
+        url_mapping = {'meta': 'openembedded-core'}
+
+        for key in url_mapping.keys():
+            if key == layer_name:
+                url_name = url_mapping[key]
+
+        return url_name
+
+    def _get_layer_information(self):
+
+        layer_info = {}
+
+        return layer_info
+
+    def _get_build_layer_information(self, layer_object):
+
+        build_layer_info = {}
+        build_layer_info['build'] = self.internal_state['build']
+        build_layer_info['layer'] = layer_object
+        build_layer_info['branch'] = self._get_git_branch(layer_object.local_path)
+        build_layer_info['commit'] = self._get_git_revision(layer_object.local_path)
+        build_layer_info['priority'] = 0
+
+        return build_layer_info
+
+
+    def _get_git_branch(self, layer_path):
+        branch = os.popen('cd {path}; git branch 2>&1 | grep "^* " | tr -d "* "'.format(path=layer_path)).read()
+
+        if len(branch) != 0:
+            return branch
+        return "<unknown>"
+
+    def _get_git_revision(self, layer_path):
+        f = os.popen("cd {path}; git log -n 1 --pretty=oneline -- 2>&1".format(path=layer_path))
+        data = f.read()
+        if f.close() is None:
+            rev = data.split(" ")[0]
+            if len(rev) != 0:
+                return rev
+        return "<unknown>"
+
+
+    def _get_build_information(self, machine_obj):
+        build_info = {}
+        # Generate an identifier for each new build
+
+        build_info['uuid'] = self.uuid
+        build_info['machine'] = machine_obj
+        build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
+        build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
+        build_info['started_on'] = datetime.datetime.now()
+        build_info['completed_on'] = datetime.datetime.now()
+        build_info['image_fstypes'] = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"])[0]
+        build_info['cooker_log_path'] = self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
+        build_info['build_name'] = self.server.runCommand(["getVariable", "BUILDNAME"])[0]
+        build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0]
+
+        return build_info
+
+    def _get_task_information(self, event, recipe):
+
 
         task_information = {}
-        task_information['build'] = self.transport_utils['build']
+        task_information['build'] = self.internal_state['build']
         task_information['outcome'] = 4
         if isinstance(event, bb.runqueue.runQueueTaskSkipped):
             task_information['task_executed'] = False
@@ -108,86 +237,33 @@ class ORMWrapper(object):
                 task_information['outcome'] = 0
         else:
             task_information['task_executed'] = True
-        task_information['sstate_checksum'] = 'Not Available'
-        task_information['path_to_sstate_obj'] = 'Not Available'
         task_information['recipe'] = recipe
         task_information['task_name'] = event.taskname
-        task_information['source_url'] = 'Not Available'
-        task_information['log_file'] = 'Not Available'
-        task_information['work_directory'] = 'Not Available'
-        task_information['script_type'] = 0
-        task_information['file_path'] = event.taskfile
-        task_information['line_number'] = 0
-        task_information['py_stack_trace'] = 'Not Available'
-        task_information['disk_io'] = 0
-        task_information['cpu_usage'] = 0
-        task_information['elapsed_time'] = 'Not Available'
-        task_information['errors_no'] = 0
-        task_information['warnings_no'] = 0
-        task_information['error'] = 'Not Available'
-        task_information['warning'] = 'Not Available'
-        task_information['sstate_result'] = 0
-
         return task_information
 
-    def create_task_object(self, task_information):
-        self.task_order += 1
-        identifier = task_information['recipe'].file_path + task_information['task_name']
+    def _get_build_layer_for_path(self, path):
+        def _slkey(build_layer):
+            return len(build_layer.layer.local_path)
 
-        task_object = Task.objects.get_or_create(
-                                build=task_information['build'],
-                                order=self.task_order,
-                                task_executed=task_information['task_executed'],
-                                outcome=task_information['outcome'],
-                                sstate_checksum=task_information['sstate_checksum'],
-                                path_to_sstate_obj=task_information['path_to_sstate_obj'],
-                                recipe=task_information['recipe'],
-                                task_name=task_information['task_name'],
-                                source_url=task_information['source_url'],
-                                log_file=task_information['log_file'],
-                                work_directory=task_information['work_directory'],
-                                script_type=task_information['script_type'],
-                                file_path=task_information['file_path'],
-                                line_number=task_information['line_number'],
-                                py_stack_trace=task_information['py_stack_trace'],
-                                disk_io=task_information['disk_io'],
-                                cpu_usage=task_information['cpu_usage'],
-                                elapsed_time=task_information['elapsed_time'],
-                                errors_no=task_information['errors_no'],
-                                warnings_no=task_information['warnings_no'],
-                                error=task_information['error'],
-                                warning=task_information['warning'],
-                                sstate_result=task_information['sstate_result'])
-        task_object[0].save()
-
-        self.transport_utils[identifier] = {'object': task_object[0], 'start_time': datetime.datetime.now()}
-
-        return task_object[0]
-
-    def update_task_object(self, task_dictionary, event):
-
-        task = task_dictionary['object']
-
-        duration = datetime.datetime.now() - task_dictionary['start_time']
-        task.elapsed_time = duration.total_seconds()
+        # Heuristics: we always match recipe to the deepest layer path that
+        # we can match to the recipe file path
+        for bl in sorted(self.internal_state['build_layers'], reverse=True, key=_slkey):
+            if (path.startswith(bl.layer.local_path)):
+                return bl
 
-        if isinstance(event, bb.runqueue.runQueueTaskCompleted):
-            task.outcome = 3
-            task.save()
+        #TODO: if we get here, we didn't read layers correctly
+        assert False
+        return None
 
-        if isinstance(event, bb.build.TaskBase):
-            task.recipe.name = event._package
-            task.recipe.save()
+    def _get_recipe_information_from_build_event(self, event):
 
-        task_build_stats = self._get_task_build_stats(task)
-        task.cpu_usage = task_build_stats['cpu_usage']
-        task.disk_io = task_build_stats['disk_io']
+        build_layer_obj = self._get_build_layer_for_path(re.split(':', event.taskfile)[-1])
 
-        #TODO: get error number
-        #TODO: get warnings number
-        #TODO: get warning information
+        recipe_info = {}
+        recipe_info['build_layer'] = build_layer_obj
+        recipe_info['file_path'] = re.split(':', event.taskfile)[-1]
 
-        task.save()
+        return recipe_info
 
     def _get_task_build_stats(self, task_object):
         bs_path = self._get_path_information(task_object)
@@ -199,9 +275,9 @@ class ORMWrapper(object):
         build_stats_format = "{tmpdir}/buildstats/{target}-{machine}/{buildname}/{package}/"
 
         tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
-        target = self.transport_utils['target']
-        machine = self.transport_utils['build'].machine.name
-        buildname = self.transport_utils['build'].build_name
+        target = self.internal_state['target']
+        machine = self.internal_state['build'].machine.name
+        buildname = self.internal_state['build'].build_name
         package = task_object.recipe.name
 
         build_stats_path = build_stats_format.format(tmpdir=tmp_dir, target=target,
@@ -240,177 +316,121 @@ class ORMWrapper(object):
 
         return task_build_stats
 
-    def _get_recipe_object(self, event):
 
-        recipe_information = self._get_recipe_information(event)
+    ################################
+    ## external available methods to store information
 
-        recipe_object = Recipe.objects.get_or_create(
-                                name=recipe_information['name'],
-                                version=recipe_information['version'],
-                                layer=recipe_information['layer'],
-                                summary=recipe_information['summary'],
-                                description=recipe_information['description'],
-                                section=recipe_information['section'],
-                                license=recipe_information['license'],
-                                licensing_info=recipe_information['licensing_info'],
-                                homepage=recipe_information['homepage'],
-                                bugtracker=recipe_information['bugtracker'],
-                                author=recipe_information['author'],
-                                file_path=recipe_information['file_path'])
-        recipe_object[0].save()
-
-        return recipe_object[0]
-
-    def _get_recipe_information(self, event):
-
-        build_layer_obj = self._get_build_layer_object(event)
-
-        recipe_info = {}
-        recipe_info['name'] = 'N/A'
-        recipe_info['version'] = 'Not Available'
-        recipe_info['layer'] = build_layer_obj
-        recipe_info['summary'] = 'Not Available'
-        recipe_info['description'] = 'Not Available'
-        recipe_info['section'] = 'Not Available'
-        recipe_info['license'] = 'Not Available'
-        recipe_info['licensing_info'] = 'Not Available'
-        recipe_info['homepage'] = 'Not Available'
-        recipe_info['bugtracker'] = 'Not Available'
-        recipe_info['author'] = 'Not Available'
-        recipe_info['file_path'] = event.taskfile
-
-
-        return recipe_info
-
-    def _get_build_layer_object(self, event):
-
-        build_layer_information = self._get_build_layer_information(event)
-
-        build_layer_object = Build_Layer.objects.get_or_create(
-                                    build = build_layer_information['build'],
-                                    layer = build_layer_information['layer'],
-                                    branch = build_layer_information['branch'],
-                                    commit = build_layer_information['commit'],
-                                    priority = build_layer_information['priority'])
-        build_layer_object[0].save()
-
-        return build_layer_object[0]
-
-    def _get_build_layer_information(self, event):
-
-        layer_object = self._get_layer_object(event)
-
-        build_layer_info = {}
-        build_layer_info['build'] = self.transport_utils['build']
-        build_layer_info['layer'] = layer_object
-        build_layer_info['branch'] = 'Not Available'
-        build_layer_info['commit'] = 'Not Available'
-        build_layer_info['priority'] = 0
-
-        return build_layer_info
-
-    def _get_layer_object(self, event):
-
-        layer_information = self._get_layer_information(event)
-
-        layer_object = Layer.objects.get_or_create(
-                                name=layer_information['name'],
-                                local_path=layer_information['local_path'],
-                                layer_index_url=layer_information['layer_index_url'])
-        layer_object[0].save()
+    def store_layer_info(self):
+        layers = self.server.runCommand(["getVariable", "BBLAYERS"])[0].strip().split(" ")
+        self.internal_state['layers'] = []
+        for layer_path in { l for l in layers if len(l) }:
+            layer_information = self._get_layer_dict(layer_path)
+            self.internal_state['layers'].append(self.orm_wrapper.get_update_layer_object(layer_information))
 
-        return layer_object[0]
+    def store_started_build(self, event):
 
-    def _get_layer_information(self, event):
+        machine_information = self._get_machine_information()
+        machine_obj = self.orm_wrapper.create_machine_object(machine_information)
+        self.uuid = str(uuid.uuid4())
 
-        layer_info = {}
-        layer_info['name'] = 'Layer Test Name'
-        layer_info['local_path'] = 'Not Available'
-        layer_info['layer_index_url'] = 'Not Available'
+        build_information = self._get_build_information(machine_obj)
+        build_information['target'] = ' '.join(event.getPkgs())
+        build_obj = self.orm_wrapper.create_build_object(build_information)
+        self.internal_state['build'] = build_obj
+        self.internal_state['target'] = build_information['target']
+        self.internal_state['build_layers'] = []
+        for layer_object in self.internal_state['layers']:
+            build_layer_information = self._get_build_layer_information(layer_object)
+            self.internal_state['build_layers'].append(self.orm_wrapper.get_build_layer_object(build_layer_information))
 
-        return layer_info
+        del self.internal_state['layers']
 
-    def _get_layer_dict(self, layer_path):
 
-        layer_info = {}
-        layer_name = layer_path.split('/')[-1]
-        layer_url = 'http://layers.openembedded.org/layerindex/layer/{layer}/'
-        layer_url_name = self._get_url_map_name(layer_name)
-
-        layer_info['name'] = layer_name
-        layer_info['local_path'] = layer_path
-        layer_info['layer_index_url'] = layer_url.format(layer=layer_url_name)
 
-        return layer_info
+    def update_build_information(self, event, errors, warnings, taskfailures):
+        self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
+        del self.internal_state['build']
 
-    def _get_url_map_name(self, layer_name):
-        """ Some layers have a different name on openembedded.org site,
-            this method returns the correct name to use in the URL
-        """
+    def store_started_task(self, event):
+        identifier = re.split(':', event.taskfile)[-1] + event.taskname
 
-        url_name = layer_name
-        url_mapping = {'meta': 'openembedded-core'}
+        recipe_information = self._get_recipe_information_from_build_event(event)
+        recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
 
-        for key in url_mapping.keys():
-            if key == layer_name:
-                url_name = url_mapping[key]
+        task_information = self._get_task_information(event, recipe)
 
-        return url_name
+        self.task_order += 1
+        task_information['order'] = self.task_order
+        task_obj = self.orm_wrapper.get_update_task_object(task_information)
 
-    def _save_layer_object(self, layer_path):
+        self.internal_state[identifier] = {'start_time': datetime.datetime.now()}
 
-        layer_information = self._get_layer_dict(layer_path)
+    def update_and_store_task(self, event):
+        identifier = re.split(':', event.taskfile)[-1] + event.taskname
+        recipe_information = self._get_recipe_information_from_build_event(event)
+        recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
+        task_information = self._get_task_information(event,recipe)
+        try:
+            task_information['start_time'] = self.internal_state[identifier]['start_time']
+        except:
+            pass
 
-        layer_object = Layer.objects.get_or_create(
-                            name=layer_information['name'],
-                            local_path=layer_information['local_path'],
-                            layer_index_url=layer_information['layer_index_url'])
+        if isinstance(event, bb.runqueue.runQueueTaskCompleted):
+            task_information['outcome'] = 3     # TODO: needs to use constants
+            task_build_stats = self._get_task_build_stats(self.orm_wrapper.get_update_task_object(task_information))
+            task_information['cpu_usage'] = task_build_stats['cpu_usage']
+            task_information['disk_io'] = task_build_stats['disk_io']
+            del self.internal_state[identifier]
 
-        layer_object[0].save()
+        #TODO: get error number
+        #TODO: get warnings number
+        #TODO: get warning information
 
-    def store_layer_info(self):
-        layers = self.server.runCommand(["getVariable", "BBLAYERS"])[0].strip().split(" ")
-        for layer_path in layers:
-            self._save_layer_object(layer_path)
 
+        self.orm_wrapper.get_update_task_object(task_information)
 
-class BuildInfoHelper(object):
-    """ This class gathers the build information from the server and sends it
-        towards the ORM wrapper for storing in the database
-    """
 
-    def __init__(self, server):
-        self._configure_django()
-        self.orm_wrapper = ORMWrapper(server)
 
-    def _configure_django(self):
-        # Add webhob to sys path for importing modules
-        sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'webhob'))
+    def store_dependency_information(self, event):
+        self.internal_state['recipes'] = {}
+        for pn in event._depgraph['pn']:
 
-    def store_started_build(self, event):
+            file_name = re.split(':', event._depgraph['pn'][pn]['filename'])[-1]
+            build_layer_obj = self._get_build_layer_for_path(re.split(':', file_name)[-1])
 
-        self.orm_wrapper.store_layer_info()
+            assert build_layer_obj is not None
 
-        machine_information = self.orm_wrapper.get_machine_information()
-        machine_obj = self.orm_wrapper.create_machine_object(machine_information)
+            recipe_info = {}
+            recipe_info['name'] = pn
+            recipe_info['version'] = event._depgraph['pn'][pn]['version']
+            recipe_info['build_layer'] = build_layer_obj
 
-        build_information = self.orm_wrapper.get_build_information(machine_obj)
-        build_information['target'] = ' '.join(event.getPkgs())
-        build_obj = self.orm_wrapper.create_build_object(build_information)
-        self.orm_wrapper.transport_utils['build'] = build_obj
-        self.orm_wrapper.transport_utils['target'] = build_information['target']
+            # TODO: dump all this info in the deptree event
+            recipe_info['summary'] = 'Not Available'
+            recipe_info['description'] = 'Not Available'
+            recipe_info['section'] = 'Not Available'
+            recipe_info['license'] = 'Not Available'
+            recipe_info['licensing_info'] = 'Not Available'
+            recipe_info['homepage'] = 'Not Available'
+            recipe_info['bugtracker'] = 'Not Available'
+            recipe_info['author'] = 'Not Available'
+            recipe_info['file_path'] = file_name
+            recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
+            self.internal_state['recipes'][pn] = recipe
 
-    def update_build_information(self, event, errors, warnings, taskfailures):
-        self.orm_wrapper.update_build_object(self.orm_wrapper.transport_utils['build'], errors, warnings, taskfailures)
-        del self.orm_wrapper.transport_utils['build']
+        def _save_a_task(taskdesc):
+            pn, taskname = re.split(r'\.', taskdesc);
+            e = event
+            e.taskname = pn
+            recipe = self.internal_state['recipes'][pn]
+            task_info = self._get_task_information(e, recipe)
+            task_info['task_name'] = taskname
+            task_obj = self.orm_wrapper.get_update_task_object(task_info)
+            return task_obj
 
-    def store_started_task(self, event):
-        task_information = self.orm_wrapper.get_task_information(event)
-        task_obj = self.orm_wrapper.create_task_object(task_information)
+        for taskdesc in event._depgraph['tdepends']:
+            _save_a_task(taskdesc)
+            for taskdesc1 in event._depgraph['tdepends'][taskdesc]:
+                _save_a_task(taskdesc1)
 
-    def update_and_store_task(self, event):
-        identifier = event.taskfile + event.taskname
-        task_dictionary = self.orm_wrapper.transport_utils[identifier]
-        self.orm_wrapper.update_task_object(task_dictionary, event)
-        if isinstance(event,bb.runqueue.runQueueTaskCompleted):
-            del self.orm_wrapper.transport_utils[identifier]
+        del self.internal_state['recipes']
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 22b35e2..d03a7c5 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -17,6 +17,11 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 from __future__ import division
+try:
+    import bb
+except RuntimeError as exc:
+    sys.exit(str(exc))
+
 from bb.ui import uihelper
 from bb.ui.buildinfohelper import BuildInfoHelper
 
@@ -32,6 +37,7 @@ import sys
 import time
 import xmlrpclib
 
+featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.SEND_DEPENDS_TREE]
 
 logger = logging.getLogger("BitBake")
 interactive = sys.stdout.isatty()
@@ -268,7 +274,9 @@ def main(server, eventHandler, params, tf = TerminalFilter):
     errors = 0
     warnings = 0
     taskfailures = []
+
     buildinfohelper = BuildInfoHelper(server)
+    buildinfohelper.store_layer_info()
 
     termfilter = tf(main, helper, console, format)
 
@@ -464,8 +472,10 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                                   bb.command.CommandFailed,
                                   bb.command.CommandExit)):
                 buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
+
                 # we start a new build info
                 buildinfohelper = BuildInfoHelper(server)
+                buildinfohelper.store_layer_info()
                 continue
 
             # ignore
@@ -481,6 +491,10 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                                   bb.cooker.CookerExit)):
                 continue
 
+            if isinstance(event, bb.event.DepTreeGenerated):
+                buildinfohelper.store_dependency_information(event)
+                continue
+
             logger.error("Unknown event: %s", event)
 
         except EnvironmentError as ioerror:
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 26a09e9..7410489 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -64,7 +64,7 @@
 				<td>{{task.path_to_sstate_obj}}</td>
 				<td>{{task.source_url}}</td>
 				<td>{{task.get_script_type_display}}</td>
-				<td>{{task.file_path}}</td>
+				<td>{{task.recipe.file_path}}</td>
 				<td>{{task.line_number}}</td>
 				<td>{{task.py_stack_trace}}</td>
 				<td>{{task.get_sstate_result_display}}</td>
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 76b7c1d..dfbc0ae 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -6,6 +6,7 @@ class Build(models.Model):
     BUILD_OUTCOME = (
         (0, 'Succeeded'),
         (1, 'Failed'),
+        (2, 'In Progress'),
     )
 
     uuid = models.CharField(max_length=100, unique=True)
@@ -15,9 +16,9 @@ class Build(models.Model):
     distro_version = models.CharField(max_length=100)
     started_on = models.DateTimeField()
     completed_on = models.DateTimeField()
-    outcome = models.IntegerField(choices=BUILD_OUTCOME)
-    errors_no = models.IntegerField()
-    warnings_no = models.IntegerField()
+    outcome = models.IntegerField(choices=BUILD_OUTCOME, default=2)
+    errors_no = models.IntegerField(default=0)
+    warnings_no = models.IntegerField(default=0)
     image_fstypes = models.CharField(max_length=100)
     cooker_log_path = models.CharField(max_length=500)
     build_name = models.CharField(max_length=100)
@@ -47,28 +48,30 @@ class Task(models.Model):
     )
 
     build = models.ForeignKey(Build, related_name='task_build')
-    order = models.IntegerField()
-    task_executed = models.BooleanField() # True means Executed, False means Prebuilt
-    outcome = models.IntegerField(choices=TASK_OUTCOME)
-    sstate_checksum = models.CharField(max_length=100)
+    order = models.IntegerField(null=True)
+    task_executed = models.BooleanField(default=False) # True means Executed, False means Prebuilt
+    outcome = models.IntegerField(choices=TASK_OUTCOME, default=4)
+    sstate_checksum = models.CharField(max_length=100, null=True)
     path_to_sstate_obj = models.FilePathField(max_length=500, blank=True)
     recipe = models.ForeignKey('Recipe', related_name='build_recipe')
     task_name = models.CharField(max_length=100)
-    source_url = models.FilePathField(max_length=255)
+    source_url = models.FilePathField(max_length=255, null=True)
     log_file = models.FilePathField(max_length=255, blank=True)
-    work_directory = models.FilePathField(max_length=255)
-    script_type = models.IntegerField(choices=TASK_CODING)
-    file_path = models.FilePathField(max_length=255)
-    line_number = models.IntegerField()
-    py_stack_trace = models.TextField()
-    disk_io = models.IntegerField()
-    cpu_usage = models.DecimalField(max_digits=6, decimal_places=2)
-    elapsed_time = models.CharField(max_length=50)
-    errors_no = models.IntegerField()
-    warnings_no = models.IntegerField()
-    error = models.TextField()
-    warning = models.TextField()
-    sstate_result = models.IntegerField(choices=SSTATE_RESULT)
+    work_directory = models.FilePathField(max_length=255, null=True)
+    script_type = models.IntegerField(choices=TASK_CODING, default=0)
+    line_number = models.IntegerField(default=0)
+    py_stack_trace = models.TextField(null=True)
+    disk_io = models.IntegerField(default=0)
+    cpu_usage = models.DecimalField(max_digits=6, decimal_places=2, default=0)
+    elapsed_time = models.CharField(max_length=50, default=0)
+    errors_no = models.IntegerField(default=0)
+    warnings_no = models.IntegerField(default=0)
+    error = models.TextField(null=True)
+    warning = models.TextField(null=True)
+    sstate_result = models.IntegerField(choices=SSTATE_RESULT, default=0)
+
+    class Meta:
+        ordering = ('order', 'recipe' ,)
 
 
 class Task_Dependency(models.Model):
@@ -112,17 +115,17 @@ class Filelist(models.Model):
 
 
 class Recipe(models.Model):
-    name = models.CharField(max_length=100)
-    version = models.CharField(max_length=100)
-    layer = models.ForeignKey('Build_Layer', related_name='recipe_build_layer')
-    summary = models.CharField(max_length=100)
-    description = models.CharField(max_length=100)
-    section = models.CharField(max_length=100)
-    license = models.CharField(max_length=200)
-    licensing_info = models.TextField()
-    homepage = models.URLField()
-    bugtracker = models.URLField()
-    author = models.CharField(max_length=100)
+    name = models.CharField(max_length=100, null=True)
+    version = models.CharField(max_length=100, null=True)
+    build_layer = models.ForeignKey('Build_Layer', related_name='recipe_build_layer')
+    summary = models.CharField(max_length=100, null=True)
+    description = models.CharField(max_length=100, null=True)
+    section = models.CharField(max_length=100, null=True)
+    license = models.CharField(max_length=200, null=True)
+    licensing_info = models.TextField(null=True)
+    homepage = models.URLField(null=True)
+    bugtracker = models.URLField(null=True)
+    author = models.CharField(max_length=100, null=True)
     file_path = models.FilePathField(max_length=255)
 
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 27/94] bitbake: webhob: record dependency info
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (25 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 26/94] bitbake: dsi: refactor the BuildInfoHelper code Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 28/94] bitbake: webhob: use defined constants for models Alex DAMIAN
                   ` (67 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch adds the task dependency information
to the database, and displays it in a primitive form
for verification.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py             |  9 ++++---
 bitbake/lib/webhob/bldviewer/templates/task.html | 33 +++++++++---------------
 bitbake/lib/webhob/bldviewer/views.py            |  9 ++++++-
 3 files changed, 26 insertions(+), 25 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 07296d9..f5e3f25 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -8,7 +8,9 @@ import re
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 
 import webhob.whbmain.settings as whb_django_settings
-from webhob.orm.models import Machine, Build, Task, Recipe, Build_Layer, Layer, Package, Task_Dependency, Package_Dependency
+from webhob.orm.models import Machine, Build, Task, Recipe, Build_Layer, Layer, Package
+from webhob.orm.models import Task_Dependency
+
 
 class ORMWrapper(object):
     """ This class creates the dictionaries needed to store information in the database
@@ -429,8 +431,9 @@ class BuildInfoHelper(object):
             return task_obj
 
         for taskdesc in event._depgraph['tdepends']:
-            _save_a_task(taskdesc)
+            target = _save_a_task(taskdesc)
             for taskdesc1 in event._depgraph['tdepends'][taskdesc]:
-                _save_a_task(taskdesc1)
+                dep = _save_a_task(taskdesc1)
+                Task_Dependency.objects.get_or_create( task = target, depends_on = dep )
 
         del self.internal_state['recipes']
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 7410489..1797028 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -17,57 +17,47 @@
 			<tr>
 			<th>Order</th>
 			<th>Task</th>
-			<th>Recipe</th>
 			<th>Recipe Version</th>
 			<th>Task Type</th>
 			<th>Outcome</th>
 			<th>Errors</th>
 			<th>Warnings</th>
 			<th>Time</th>
-			<th>Log</th>
-			<th>Work directory</th>
 			<th>CPU usage</th>
 			<th>Disk I/O</th>
-			<th>Sstate Checksum</th>
-			<th>Path to sstate object</th>
-			<th>Source URL</th>
 			<th>Script type</th>
 			<th>File path</th>
-			<th>Line number</th>
-			<th>Python Stack Trace</th>
-			<th>Sstate Result</th>
+            <th>Depends</th>
 			</tr>
 
 			{% for task in tasks %}
 
 				<tr>
 				<td>{{task.order}}</td>
-				<td>{{task.task_name}}</td>
-				<td>{{task.recipe.name}}</td>
+				<td><a name="{{task.recipe.name}}.{{task.task_name}}">
+				{{task.recipe.name}}.{{task.task_name}}</a></td>
 				<td>{{task.recipe.version}}</td>
 
 				{% if task.task_executed %}
-					<td>Executed</td>
+				<td>Executed</td>
 				{% else %}
-					<td>Prebuilt</td>
+				<td>Prebuilt</td>
 				{% endif %}
 
 				<td>{{task.get_outcome_display}}</td>
 				<td>{{task.errors_no}}</td>
 				<td>{{task.warnings_no}}</td>
 				<td>{{task.elapsed_time}}</td>
-				<td>{{task.log_file}}</td>
-				<td>{{task.work_directory}}</td>
 				<td>{{task.cpu_usage}}</td>
 				<td>{{task.disk_io}}</td>
-				<td>{{task.sstate_checksum}}</td>
-				<td>{{task.path_to_sstate_obj}}</td>
-				<td>{{task.source_url}}</td>
 				<td>{{task.get_script_type_display}}</td>
 				<td>{{task.recipe.file_path}}</td>
-				<td>{{task.line_number}}</td>
-				<td>{{task.py_stack_trace}}</td>
-				<td>{{task.get_sstate_result_display}}</td>
+                <td>
+                {% for tt in task.depends_on %}
+                    <a href="#{{tt.recipe.name}}.{{tt.task_name}}">
+                    {{tt.recipe.name}}.{{tt.task_name}}</a><br/>
+                {% endfor %}
+                </td>
 				</tr>
 
 			{% endfor %}
@@ -79,3 +69,4 @@
 </body>
 
 </html>
+
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index f40d396..eb901a4 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -1,5 +1,5 @@
 from django.shortcuts import render
-from orm.models import Build, Task, Layer
+from orm.models import Build, Task, Layer, Task_Dependency
 
 
 def build(request):
@@ -14,6 +14,13 @@ def task(request, build_id):
     template = 'task.html'
 
     tasks = Task.objects.filter(build=build_id)
+    task_depends = Task_Dependency.objects.filter(task__in=tasks)
+
+    for t in tasks:
+        t.depends_on = []
+        for k in task_depends:
+            if t == k.task:
+                t.depends_on.append(k.depends_on)
 
     context = {'tasks': tasks}
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 28/94] bitbake: webhob: use defined constants for models
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (26 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 27/94] bitbake: webhob: record dependency info Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 29/94] bitbake: dsi: fix the reading of task event information Alex DAMIAN
                   ` (66 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We change the models to use constants to improve
the legibility of the code.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 13 +++++----
 bitbake/lib/webhob/orm/models.py     | 55 ++++++++++++++++++++++++------------
 2 files changed, 44 insertions(+), 24 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index f5e3f25..d489c87 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -47,9 +47,9 @@ class ORMWrapper(object):
 
     def update_build_object(self, build_obj, errors, warnings, taskfailures):
 
-        outcome = 0
+        outcome = Build.SUCCEEDED
         if errors or taskfailures:
-            outcome = 1
+            outcome = Build.FAILED
 
         build = Build.objects.get(uuid=build_obj.uuid)
         build.completed_on = datetime.datetime.now()
@@ -230,13 +230,13 @@ class BuildInfoHelper(object):
 
         task_information = {}
         task_information['build'] = self.internal_state['build']
-        task_information['outcome'] = 4
+        task_information['outcome'] = Task.OUTCOME_NA
         if isinstance(event, bb.runqueue.runQueueTaskSkipped):
             task_information['task_executed'] = False
             if event._skip == "covered":
-                task_information['outcome'] = 2
+                task_information['outcome'] = Task.OUTCOME_COVERED
             if event._skip == "existing":
-                task_information['outcome'] = 0
+                task_information['outcome'] = Task.OUTCOME_EXISTING
         else:
             task_information['task_executed'] = True
         task_information['recipe'] = recipe
@@ -363,6 +363,7 @@ class BuildInfoHelper(object):
 
         self.task_order += 1
         task_information['order'] = self.task_order
+        task_information['outcome'] = Task.OUTCOME_NA
         task_obj = self.orm_wrapper.get_update_task_object(task_information)
 
         self.internal_state[identifier] = {'start_time': datetime.datetime.now()}
@@ -378,7 +379,7 @@ class BuildInfoHelper(object):
             pass
 
         if isinstance(event, bb.runqueue.runQueueTaskCompleted):
-            task_information['outcome'] = 3     # TODO: needs to use constants
+            task_information['outcome'] = Task.OUTCOME_SUCCESS     # TODO: needs to use constants
             task_build_stats = self._get_task_build_stats(self.orm_wrapper.get_update_task_object(task_information))
             task_information['cpu_usage'] = task_build_stats['cpu_usage']
             task_information['disk_io'] = task_build_stats['disk_io']
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index dfbc0ae..83126d8 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -2,11 +2,14 @@ from django.db import models
 
 
 class Build(models.Model):
+    SUCCEEDED = 0
+    FAILED = 1
+    IN_PROGRESS = 2
 
     BUILD_OUTCOME = (
-        (0, 'Succeeded'),
-        (1, 'Failed'),
-        (2, 'In Progress'),
+        (SUCCEEDED, 'Succeeded'),
+        (FAILED, 'Failed'),
+        (IN_PROGRESS, 'In Progress'),
     )
 
     uuid = models.CharField(max_length=100, unique=True)
@@ -16,7 +19,7 @@ class Build(models.Model):
     distro_version = models.CharField(max_length=100)
     started_on = models.DateTimeField()
     completed_on = models.DateTimeField()
-    outcome = models.IntegerField(choices=BUILD_OUTCOME, default=2)
+    outcome = models.IntegerField(choices=BUILD_OUTCOME, default=IN_PROGRESS)
     errors_no = models.IntegerField(default=0)
     warnings_no = models.IntegerField(default=0)
     image_fstypes = models.CharField(max_length=100)
@@ -27,30 +30,46 @@ class Build(models.Model):
 
 class Task(models.Model):
 
+    SSTATE_NA = 0
+    SSTATE_MISS = 1
+    SSTATE_FAILED = 2
+    SSTATE_RESTORED = 3
+
     SSTATE_RESULT = (
-        (0, 'Not Applicable'), # For rest of tasks, but they still need checking.
-        (1, 'Unavailable'), # it is a miss
-        (2, 'Failed'), # there was a pkg, but the script failed
-        (3, 'Restored'), # succesfully restored
+        (SSTATE_NA, 'Not Applicable'), # For rest of tasks, but they still need checking.
+        (SSTATE_MISS, 'Missing'), # it is a miss
+        (SSTATE_FAILED, 'Failed'), # there was a pkg, but the script failed
+        (SSTATE_RESTORED, 'Restored'), # succesfully restored
     )
 
+    CODING_PYTHON = 0
+    CODING_SHELL = 1
+
     TASK_CODING = (
-        (0, 'Python'),
-        (1, 'Shell'),
+        (CODING_PYTHON, 'Python'),
+        (CODING_SHELL, 'Shell'),
     )
 
+    OUTCOME_SUCCESS = 0
+    OUTCOME_COVERED = 1
+    OUTCOME_SSTATE = 2
+    OUTCOME_EXISTING = 3
+    OUTCOME_FAILED = 4
+    OUTCOME_NA = 5
+
     TASK_OUTCOME = (
-        (0, 'Covered'),
-        (1, 'Sstate'),
-        (2, 'Existing'),
-        (3, 'Succeeded'),
-        (4, 'Failed'),
+        (OUTCOME_SUCCESS, 'Succeeded'),
+        (OUTCOME_COVERED, 'Covered'),
+        (OUTCOME_SSTATE, 'Sstate'),
+        (OUTCOME_EXISTING, 'Existing'),
+        (OUTCOME_FAILED, 'Failed'),
+        (OUTCOME_NA, 'Not Available'),
     )
 
     build = models.ForeignKey(Build, related_name='task_build')
     order = models.IntegerField(null=True)
     task_executed = models.BooleanField(default=False) # True means Executed, False means Prebuilt
-    outcome = models.IntegerField(choices=TASK_OUTCOME, default=4)
+    outcome = models.IntegerField(choices=TASK_OUTCOME, default=OUTCOME_NA)
     sstate_checksum = models.CharField(max_length=100, null=True)
     path_to_sstate_obj = models.FilePathField(max_length=500, blank=True)
     recipe = models.ForeignKey('Recipe', related_name='build_recipe')
@@ -58,7 +77,7 @@ class Task(models.Model):
     source_url = models.FilePathField(max_length=255, null=True)
     log_file = models.FilePathField(max_length=255, blank=True)
     work_directory = models.FilePathField(max_length=255, null=True)
-    script_type = models.IntegerField(choices=TASK_CODING, default=0)
+    script_type = models.IntegerField(choices=TASK_CODING, default=CODING_PYTHON)
     line_number = models.IntegerField(default=0)
     py_stack_trace = models.TextField(null=True)
     disk_io = models.IntegerField(default=0)
@@ -68,7 +87,7 @@ class Task(models.Model):
     warnings_no = models.IntegerField(default=0)
     error = models.TextField(null=True)
     warning = models.TextField(null=True)
-    sstate_result = models.IntegerField(choices=SSTATE_RESULT, default=0)
+    sstate_result = models.IntegerField(choices=SSTATE_RESULT, default=SSTATE_NA)
 
     class Meta:
         ordering = ('order', 'recipe' ,)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 29/94] bitbake: dsi: fix the reading of task event information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (27 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 28/94] bitbake: webhob: use defined constants for models Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 30/94] bitbake: webhob: Setup API for build model Alex DAMIAN
                   ` (65 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Fixing a problem where the event outcome wasn't processed.
Moving all the event-type-dependent code at the same level
in the class.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index d489c87..6eafea0 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -231,14 +231,6 @@ class BuildInfoHelper(object):
         task_information = {}
         task_information['build'] = self.internal_state['build']
         task_information['outcome'] = Task.OUTCOME_NA
-        if isinstance(event, bb.runqueue.runQueueTaskSkipped):
-            task_information['task_executed'] = False
-            if event._skip == "covered":
-                task_information['outcome'] = Task.OUTCOME_COVERED
-            if event._skip == "existing":
-                task_information['outcome'] = Task.OUTCOME_EXISTING
-        else:
-            task_information['task_executed'] = True
         task_information['recipe'] = recipe
         task_information['task_name'] = event.taskname
         return task_information
@@ -360,10 +352,19 @@ class BuildInfoHelper(object):
         recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
 
         task_information = self._get_task_information(event, recipe)
+        task_information['outcome'] = Task.OUTCOME_NA
+
+        if isinstance(event, bb.runqueue.runQueueTaskSkipped):
+            task_information['task_executed'] = False
+            if event.reason == "covered":
+                task_information['outcome'] = Task.OUTCOME_COVERED
+            if event.reason == "existing":
+                task_information['outcome'] = Task.OUTCOME_EXISTING
+        else:
+            task_information['task_executed'] = True
 
         self.task_order += 1
         task_information['order'] = self.task_order
-        task_information['outcome'] = Task.OUTCOME_NA
         task_obj = self.orm_wrapper.get_update_task_object(task_information)
 
         self.internal_state[identifier] = {'start_time': datetime.datetime.now()}
@@ -385,6 +386,10 @@ class BuildInfoHelper(object):
             task_information['disk_io'] = task_build_stats['disk_io']
             del self.internal_state[identifier]
 
+        if isinstance(event, bb.runqueue.runQueueTaskFailed):
+            task_information['outcome'] = Task.OUTCOME_FAILED
+            del self.internal_state[identifier]
+
         #TODO: get error number
         #TODO: get warnings number
         #TODO: get warning information
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 30/94] bitbake: webhob: Setup API for build model
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (28 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 29/94] bitbake: dsi: fix the reading of task event information Alex DAMIAN
@ 2013-09-24 16:51 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 31/94] bitbake: webhob: orm change to remove Target Alex DAMIAN
                   ` (64 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:51 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

Adding REST API version 1.0 interface of WebHob.

Build call API is implemented in this patch
Documentation is here:
https://wiki.yoctoproject.org/wiki/Webhob_REST_API_Builds

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/api.py   |  6 ++++
 bitbake/lib/webhob/bldviewer/views.py | 56 +++++++++++++++++++++++++++++++++++
 bitbake/lib/webhob/whbmain/urls.py    |  1 +
 3 files changed, 63 insertions(+)
 create mode 100644 bitbake/lib/webhob/bldviewer/api.py

diff --git a/bitbake/lib/webhob/bldviewer/api.py b/bitbake/lib/webhob/bldviewer/api.py
new file mode 100644
index 0000000..d945861
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/api.py
@@ -0,0 +1,6 @@
+from django.conf.urls import patterns, include, url
+
+
+urlpatterns = patterns('bldviewer.views',
+        url(r'^builds/$', 'builds', name='builds'),
+)
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index eb901a4..58d9444 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -33,3 +33,59 @@ def layer(request):
     context = {'layers': layer_info}
 
     return render(request, template, context)
+
+
+#### API
+
+import json
+from django.core import serializers
+from django.http import HttpResponse
+
+
+def builds(request):
+    response_data = {}
+
+    try:
+        limit = int(request.GET.get('limit', 0))
+    except ValueError:
+        limit = 0
+
+    try:
+        offset = int(request.GET.get('offset', 0))
+    except ValueError:
+        offset = 0
+
+    filter_string = request.GET.get('filter', '')
+
+    if filter_string:
+        filter_terms = _get_filtering_terms(filter_string)
+        try:
+            queryset = Build.objects.filter(**filter_terms)
+        except ValueError:
+            queryset = []
+    else:
+        queryset = Build.objects.all()
+
+    if offset and limit:
+        queryset = queryset[offset:(offset+limit)]
+    elif offset:
+        queryset = queryset[offset:]
+    elif limit:
+        queryset = queryset[:limit]
+
+    if queryset:
+        response_data['count'] = queryset.count()
+    else:
+        response_data['count'] = 0
+
+    response_data['list'] = serializers.serialize('json', queryset)
+
+    return HttpResponse(json.dumps(response_data), content_type='application/json')
+
+def _get_filtering_terms(filter_string):
+
+    search_terms = filter_string.split(":")
+    keys = search_terms[0].split(',')
+    values = search_terms[1].split(',')
+
+    return dict(zip(keys, values))
diff --git a/bitbake/lib/webhob/whbmain/urls.py b/bitbake/lib/webhob/whbmain/urls.py
index af536aa..14345f6 100644
--- a/bitbake/lib/webhob/whbmain/urls.py
+++ b/bitbake/lib/webhob/whbmain/urls.py
@@ -6,6 +6,7 @@ from django.conf.urls import patterns, include, url
 
 urlpatterns = patterns('',
     url(r'^build/', include('bldviewer.urls')),
+    url(r'^api/1.0/', include('bldviewer.api')),
     url(r'^gui/', include('whbgui.urls')),
     # Examples:
     # url(r'^webhob/', include('webhob.foo.urls')),
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 31/94] bitbake: webhob: orm change to remove Target
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (29 preceding siblings ...)
  2013-09-24 16:51 ` [PATCH 30/94] bitbake: webhob: Setup API for build model Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 32/94] bitbake: webhob: clear up ORM relations Alex DAMIAN
                   ` (63 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Removes the Target dependency as it is replaced
by a field in Build.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/orm/models.py | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 83126d8..e6663a3 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -14,6 +14,7 @@ class Build(models.Model):
 
     uuid = models.CharField(max_length=100, unique=True)
     target = models.CharField(max_length=100)
+    is_image = models.BooleanField()
     machine = models.ForeignKey('Machine', related_name='build_machine')
     distro = models.CharField(max_length=100)
     distro_version = models.CharField(max_length=100)
@@ -98,24 +99,14 @@ class Task_Dependency(models.Model):
     depends_on = models.ForeignKey(Task, related_name='task_dependencies_depends')
 
 
-class Target(models.Model):
-    build = models.ForeignKey(Build, related_name='target_build')
-    is_image = models.BooleanField()
-
-
 class Artifact(models.Model):
     build = models.ForeignKey(Build, related_name='artifact_build')
-    target = models.ForeignKey(Target, related_name='artifact_target')
     file_name = models.CharField(max_length=100)
     file_size = models.IntegerField()
 
 
-class Package_In_Image(models.Model):
-    package = models.ForeignKey('Package', related_name='package_in_image_package')
-    target = models.ForeignKey(Target, related_name='package_in_image_target')
-
-
 class Package(models.Model):
+    build = models.ForeignKey('Build', related_name='package_build')
     recipe = models.ForeignKey('Recipe', related_name='package_recipe')
     name = models.CharField(max_length=100)
     version = models.CharField(max_length=100)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 32/94] bitbake: webhob: clear up ORM relations
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (30 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 31/94] bitbake: webhob: orm change to remove Target Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 33/94] bitbake: dsi: save detailed recipe information Alex DAMIAN
                   ` (62 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

After a bit of analysis, it's become clear that Recipes
only depend on a specific Layer version, and not on
the current build.

So I clear up the Build_Layer transforming it to Layer_Version
and remove the Build dependency.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 61 ++++++++++++++++++------------------
 bitbake/lib/webhob/orm/models.py     |  7 ++---
 2 files changed, 33 insertions(+), 35 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 6eafea0..b0b56aa 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -8,7 +8,7 @@ import re
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 
 import webhob.whbmain.settings as whb_django_settings
-from webhob.orm.models import Machine, Build, Task, Recipe, Build_Layer, Layer, Package
+from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Package
 from webhob.orm.models import Task_Dependency
 
 
@@ -81,7 +81,7 @@ class ORMWrapper(object):
     def get_update_recipe_object(self, recipe_information):
 
         recipe_object, created = Recipe.objects.get_or_create(
-                                         build_layer=recipe_information['build_layer'],
+                                         layer_version=recipe_information['layer_version'],
                                          file_path=recipe_information['file_path'])
 
         for v in vars(recipe_object):
@@ -92,19 +92,18 @@ class ORMWrapper(object):
 
         return recipe_object
 
-    def get_build_layer_object(self, build_layer_information):
+    def get_layer_version_object(self, layer_version_information):
 
-        build_layer_object = Build_Layer.objects.get_or_create(
-                                    build = build_layer_information['build'],
-                                    layer = build_layer_information['layer'],
-                                    branch = build_layer_information['branch'],
-                                    commit = build_layer_information['commit'],
-                                    priority = build_layer_information['priority']
+        layer_version_object = Layer_Version.objects.get_or_create(
+                                    layer = layer_version_information['layer'],
+                                    branch = layer_version_information['branch'],
+                                    commit = layer_version_information['commit'],
+                                    priority = layer_version_information['priority']
                                     )
 
-        build_layer_object[0].save()
+        layer_version_object[0].save()
 
-        return build_layer_object[0]
+        return layer_version_object[0]
 
     def get_update_layer_object(self, layer_information):
 
@@ -179,16 +178,16 @@ class BuildInfoHelper(object):
 
         return layer_info
 
-    def _get_build_layer_information(self, layer_object):
+    def _get_layer_version_information(self, layer_object):
 
-        build_layer_info = {}
-        build_layer_info['build'] = self.internal_state['build']
-        build_layer_info['layer'] = layer_object
-        build_layer_info['branch'] = self._get_git_branch(layer_object.local_path)
-        build_layer_info['commit'] = self._get_git_revision(layer_object.local_path)
-        build_layer_info['priority'] = 0
+        layer_version_info = {}
+        layer_version_info['build'] = self.internal_state['build']
+        layer_version_info['layer'] = layer_object
+        layer_version_info['branch'] = self._get_git_branch(layer_object.local_path)
+        layer_version_info['commit'] = self._get_git_revision(layer_object.local_path)
+        layer_version_info['priority'] = 0
 
-        return build_layer_info
+        return layer_version_info
 
 
     def _get_git_branch(self, layer_path):
@@ -235,13 +234,13 @@ class BuildInfoHelper(object):
         task_information['task_name'] = event.taskname
         return task_information
 
-    def _get_build_layer_for_path(self, path):
-        def _slkey(build_layer):
-            return len(build_layer.layer.local_path)
+    def _get_layer_version_for_path(self, path):
+        def _slkey(layer_version):
+            return len(layer_version.layer.local_path)
 
         # Heuristics: we always match recipe to the deepest layer path that
         # we can match to the recipe file path
-        for bl in sorted(self.internal_state['build_layers'], reverse=True, key=_slkey):
+        for bl in sorted(self.internal_state['layer_versions'], reverse=True, key=_slkey):
             if (path.startswith(bl.layer.local_path)):
                 return bl
 
@@ -251,10 +250,10 @@ class BuildInfoHelper(object):
 
     def _get_recipe_information_from_build_event(self, event):
 
-        build_layer_obj = self._get_build_layer_for_path(re.split(':', event.taskfile)[-1])
+        layer_version_obj = self._get_layer_version_for_path(re.split(':', event.taskfile)[-1])
 
         recipe_info = {}
-        recipe_info['build_layer'] = build_layer_obj
+        recipe_info['layer_version'] = layer_version_obj
         recipe_info['file_path'] = re.split(':', event.taskfile)[-1]
 
         return recipe_info
@@ -332,10 +331,10 @@ class BuildInfoHelper(object):
         build_obj = self.orm_wrapper.create_build_object(build_information)
         self.internal_state['build'] = build_obj
         self.internal_state['target'] = build_information['target']
-        self.internal_state['build_layers'] = []
+        self.internal_state['layer_versions'] = []
         for layer_object in self.internal_state['layers']:
-            build_layer_information = self._get_build_layer_information(layer_object)
-            self.internal_state['build_layers'].append(self.orm_wrapper.get_build_layer_object(build_layer_information))
+            layer_version_information = self._get_layer_version_information(layer_object)
+            self.internal_state['layer_versions'].append(self.orm_wrapper.get_layer_version_object(layer_version_information))
 
         del self.internal_state['layers']
 
@@ -404,14 +403,14 @@ class BuildInfoHelper(object):
         for pn in event._depgraph['pn']:
 
             file_name = re.split(':', event._depgraph['pn'][pn]['filename'])[-1]
-            build_layer_obj = self._get_build_layer_for_path(re.split(':', file_name)[-1])
+            layer_version_obj = self._get_layer_version_for_path(re.split(':', file_name)[-1])
 
-            assert build_layer_obj is not None
+            assert layer_version_obj is not None
 
             recipe_info = {}
             recipe_info['name'] = pn
             recipe_info['version'] = event._depgraph['pn'][pn]['version']
-            recipe_info['build_layer'] = build_layer_obj
+            recipe_info['layer_version'] = layer_version_obj
 
             # TODO: dump all this info in the deptree event
             recipe_info['summary'] = 'Not Available'
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index e6663a3..91b352e 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -127,7 +127,7 @@ class Filelist(models.Model):
 class Recipe(models.Model):
     name = models.CharField(max_length=100, null=True)
     version = models.CharField(max_length=100, null=True)
-    build_layer = models.ForeignKey('Build_Layer', related_name='recipe_build_layer')
+    layer_version = models.ForeignKey('Layer_Version', related_name='recipe_layer_version')
     summary = models.CharField(max_length=100, null=True)
     description = models.CharField(max_length=100, null=True)
     section = models.CharField(max_length=100, null=True)
@@ -150,9 +150,8 @@ class Layer(models.Model):
     layer_index_url = models.URLField()
 
 
-class Build_Layer(models.Model):
-    build = models.ForeignKey(Build, related_name='build_layer_build')
-    layer = models.ForeignKey(Layer, related_name='build_layer_layer')
+class Layer_Version(models.Model):
+    layer = models.ForeignKey(Layer, related_name='layer_version_layer')
     branch = models.CharField(max_length=50)
     commit = models.CharField(max_length=100)
     priority = models.IntegerField()
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 33/94] bitbake: dsi: save detailed recipe information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (31 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 32/94] bitbake: webhob: clear up ORM relations Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 34/94] bitbake: webhob: determine if a build is an image Alex DAMIAN
                   ` (61 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Code to save the recipe information coming in the dependency
tree. Also marks recipes that generate images.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index b0b56aa..92f1621 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -411,22 +411,24 @@ class BuildInfoHelper(object):
             recipe_info['name'] = pn
             recipe_info['version'] = event._depgraph['pn'][pn]['version']
             recipe_info['layer_version'] = layer_version_obj
-
-            # TODO: dump all this info in the deptree event
-            recipe_info['summary'] = 'Not Available'
-            recipe_info['description'] = 'Not Available'
-            recipe_info['section'] = 'Not Available'
-            recipe_info['license'] = 'Not Available'
+            recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
+            recipe_info['license'] = event._depgraph['pn'][pn]['license']
+            recipe_info['description'] = event._depgraph['pn'][pn]['summary']
+            recipe_info['section'] = event._depgraph['pn'][pn]['section']
             recipe_info['licensing_info'] = 'Not Available'
-            recipe_info['homepage'] = 'Not Available'
-            recipe_info['bugtracker'] = 'Not Available'
+            recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
+            recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
             recipe_info['author'] = 'Not Available'
             recipe_info['file_path'] = file_name
             recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
+            recipe.is_image = True in map(lambda x: x.endswith('image.bbclass'), event._depgraph['pn'][pn]['inherits'])
             self.internal_state['recipes'][pn] = recipe
 
+        # save all task information
         def _save_a_task(taskdesc):
-            pn, taskname = re.split(r'\.', taskdesc);
+            spec = re.split(r'\.', taskdesc);
+            pn = ".".join(spec[0:-1])
+            taskname = spec[-1]
             e = event
             e.taskname = pn
             recipe = self.internal_state['recipes'][pn]
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 34/94] bitbake: webhob: determine if a build is an image
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (32 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 33/94] bitbake: dsi: save detailed recipe information Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 35/94] bitbake: webhob: clean up URL structure Alex DAMIAN
                   ` (60 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding code to see if a build will generate a image
based on the inheritance of image.bbclass in the recipe
defining the build.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py              | 4 ++++
 bitbake/lib/webhob/bldviewer/templates/build.html | 2 ++
 bitbake/lib/webhob/orm/models.py                  | 2 +-
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 92f1621..16e69c5 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -328,6 +328,7 @@ class BuildInfoHelper(object):
 
         build_information = self._get_build_information(machine_obj)
         build_information['target'] = ' '.join(event.getPkgs())
+
         build_obj = self.orm_wrapper.create_build_object(build_information)
         self.internal_state['build'] = build_obj
         self.internal_state['target'] = build_information['target']
@@ -422,6 +423,9 @@ class BuildInfoHelper(object):
             recipe_info['file_path'] = file_name
             recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
             recipe.is_image = True in map(lambda x: x.endswith('image.bbclass'), event._depgraph['pn'][pn]['inherits'])
+            if recipe.is_image and pn == self.internal_state['build'].target:
+                self.internal_state['build'].is_image = True
+                self.internal_state['build'].save()
             self.internal_state['recipes'][pn] = recipe
 
         # save all task information
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index 1b694d6..cd257e0 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -16,6 +16,7 @@
 			<th>Started On</th>
 			<th>Completed On</th>
 			<th>Target</th>
+	        <th>Image ?</th>
 			<th>Machine</th>
 			<th>Time</th>
 			<th>Errors</th>
@@ -33,6 +34,7 @@
 			<td>{{build.started_on}}</td>
 			<td>{{build.completed_on}}</td>
 			<td>{{build.target}}</td>
+	        <td>{{build.is_image}}</td>
 			<td>{{build.machine.name}}</td>
 			<td>{% time_difference build.started_on build.completed_on %}</td>
 			<td>{{build.errors_no}}</td>
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 91b352e..0d6cf15 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -14,7 +14,7 @@ class Build(models.Model):
 
     uuid = models.CharField(max_length=100, unique=True)
     target = models.CharField(max_length=100)
-    is_image = models.BooleanField()
+    is_image = models.BooleanField(default = False)
     machine = models.ForeignKey('Machine', related_name='build_machine')
     distro = models.CharField(max_length=100)
     distro_version = models.CharField(max_length=100)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 35/94] bitbake: webhob: clean up URL structure
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (33 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 34/94] bitbake: webhob: determine if a build is an image Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 36/94] bitbake: webhob: add simple pages for Recipes Alex DAMIAN
                   ` (59 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We clean up the URL structure.
* default redirect to GUI version
* basic HTML interface now available at /simple/
* clear-up simple/ paths towards object-type URLs

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/urls.py | 6 ++++--
 bitbake/lib/webhob/whbmain/urls.py   | 5 ++++-
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index 8d5dc6e..756b3fa 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -1,8 +1,10 @@
 from django.conf.urls import patterns, include, url
+from django.views.generic.simple import redirect_to
 
 
 urlpatterns = patterns('bldviewer.views',
-        url(r'^$', 'build', name='build'),
+        url(r'^build/$', 'build', name='build'),
         url(r'^layers/$', 'layer', name='layer'),
-        url(r'^(?P<build_id>\d+)/$', 'task', name='task'),
+        url(r'^build/(?P<build_id>\d+)/task/$', 'task', name='task'),
+        url(r'^$', redirect_to, {'url': 'build/'}),
 )
diff --git a/bitbake/lib/webhob/whbmain/urls.py b/bitbake/lib/webhob/whbmain/urls.py
index 14345f6..0e15eda 100644
--- a/bitbake/lib/webhob/whbmain/urls.py
+++ b/bitbake/lib/webhob/whbmain/urls.py
@@ -1,13 +1,16 @@
 from django.conf.urls import patterns, include, url
+from django.views.generic.simple import redirect_to
+
 
 # Uncomment the next two lines to enable the admin:
 # from django.contrib import admin
 # admin.autodiscover()
 
 urlpatterns = patterns('',
-    url(r'^build/', include('bldviewer.urls')),
+    url(r'^simple/', include('bldviewer.urls')),
     url(r'^api/1.0/', include('bldviewer.api')),
     url(r'^gui/', include('whbgui.urls')),
+    url(r'^$', redirect_to, {'url': '/gui/'}),
     # Examples:
     # url(r'^webhob/', include('webhob.foo.urls')),
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 36/94] bitbake: webhob: add simple pages for Recipes
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (34 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 35/94] bitbake: webhob: clean up URL structure Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 37/94] bitbake: webhob: add ordering capabilities to build api Alex DAMIAN
                   ` (58 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We add simple pages to display database information
about Layer versions and Recipes.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/layer.html  |  8 ++++
 bitbake/lib/webhob/bldviewer/templates/recipe.html | 48 ++++++++++++++++++++++
 bitbake/lib/webhob/bldviewer/urls.py               |  4 +-
 bitbake/lib/webhob/bldviewer/views.py              | 15 ++++++-
 4 files changed, 72 insertions(+), 3 deletions(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/recipe.html

diff --git a/bitbake/lib/webhob/bldviewer/templates/layer.html b/bitbake/lib/webhob/bldviewer/templates/layer.html
index 905a781..24bd86b 100644
--- a/bitbake/lib/webhob/bldviewer/templates/layer.html
+++ b/bitbake/lib/webhob/bldviewer/templates/layer.html
@@ -15,6 +15,7 @@
 			<th>Name</th>
 			<th>Local Path</th>
 			<th>Layer Index URL</th>
+            <th>Known Versions</th>
 		</tr>
 
 		{% for layer in layers %}
@@ -23,6 +24,13 @@
 			<td>{{layer.name}}</td>
 			<td>{{layer.local_path}}</td>
 			<td><a href='{{layer.layer_index_url}}'>{{layer.layer_index_url}}</a></td>
+            <td><table>
+            {% for lv in layer.versions %}
+                <tr><td>
+        <a href="/simple/layerversions/{{lv.id}}/recipes">{{lv.branch}}:{{lv.commit}} ({{lv.count}} recipes)</a>
+                </td></tr>
+            {% endfor %}
+            </table></td>
 		</tr>
 
 		{% endfor %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/recipe.html b/bitbake/lib/webhob/bldviewer/templates/recipe.html
new file mode 100644
index 0000000..8f5dcb3
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/recipe.html
@@ -0,0 +1,48 @@
+<html>
+
+	<head>
+		<title>WebHob Recipe page</title>
+	</head>
+
+<body>
+	<h1>WebHob Recipes for a Layer</h1>
+
+	<table border="1">
+
+	{% load projecttags %}
+
+		<tr>
+		</tr>
+			<th>Name</th>
+			<th>Version</th>
+			<th>Summary</th>
+			<th>Description</th>
+	        <th>Section</th>
+			<th>License</th>
+			<th>Homepage</th>
+			<th>Bugtracker</th>
+			<th>Author</th>
+			<th>File_path</th>
+
+		{% for recipe in recipes %}
+
+		<tr>
+			<td>{{recipe.name}}</td>
+			<td>{{recipe.version}}</td>
+			<td>{{recipe.summary}}</td>
+			<td>{{recipe.description}}</td>
+	        <td>{{recipe.section}}</td>
+			<td>{{recipe.license}}</td>
+			<td>{{recipe.homepage}}</td>
+			<td>{{recipe.bugtracker}}</td>
+			<td>{{recipe.author}}</td>
+			<td>{{recipe.file_path}}</td>
+		</tr>
+
+		{% endfor %}
+
+	</table>
+
+</body>
+
+</html>
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index 756b3fa..b6c6796 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -1,10 +1,10 @@
 from django.conf.urls import patterns, include, url
 from django.views.generic.simple import redirect_to
 
-
 urlpatterns = patterns('bldviewer.views',
         url(r'^build/$', 'build', name='build'),
-        url(r'^layers/$', 'layer', name='layer'),
         url(r'^build/(?P<build_id>\d+)/task/$', 'task', name='task'),
+        url(r'^layer/$', 'layer', name='layer'),
+        url(r'^layerversions/(?P<layerversion_id>\d+)/recipes$', 'layer_versions_recipes', name='layer_versions_recipes'),
         url(r'^$', redirect_to, {'url': 'build/'}),
 )
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 58d9444..de89b84 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -1,5 +1,5 @@
 from django.shortcuts import render
-from orm.models import Build, Task, Layer, Task_Dependency
+from orm.models import Build, Task, Layer, Layer_Version, Task_Dependency, Recipe
 
 
 def build(request):
@@ -30,11 +30,24 @@ def layer(request):
     template = 'layer.html'
     layer_info = Layer.objects.all()
 
+    for li in layer_info:
+        li.versions = Layer_Version.objects.filter(layer = li)
+        for liv in li.versions:
+            liv.count = Recipe.objects.filter(layer_version__id = liv.id).count()
+
     context = {'layers': layer_info}
 
     return render(request, template, context)
 
 
+def layer_versions_recipes(request, layerversion_id):
+    template = 'recipe.html'
+    recipes = Recipe.objects.filter(layer_version__id = layerversion_id)
+    
+    context = {'recipes': recipes}
+    
+    return render(request, template, context)
+
 #### API
 
 import json
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 37/94] bitbake: webhob: add ordering capabilities to build api
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (35 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 36/94] bitbake: webhob: add simple pages for Recipes Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 38/94] bitbake: webhob: validate inputs for " Alex DAMIAN
                   ` (57 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch adds the capability to order builds by a specific
field of the build model.
The format used is 'field_name:order' and the default ordering
is ascending.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/views.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index de89b84..71e95b4 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -56,6 +56,7 @@ from django.http import HttpResponse
 
 
 def builds(request):
+    DESCENDING = 'desc'
     response_data = {}
 
     try:
@@ -68,6 +69,7 @@ def builds(request):
     except ValueError:
         offset = 0
 
+    ordering_string = request.GET.get('orderby', '')
     filter_string = request.GET.get('filter', '')
 
     if filter_string:
@@ -91,6 +93,13 @@ def builds(request):
     else:
         response_data['count'] = 0
 
+    if queryset and ordering_string:
+        column, order = ordering_string.split(':')
+        if order.lower() == DESCENDING:
+            queryset = queryset.order_by('-' + column)
+        else:
+            queryset = queryset.order_by(column)
+
     response_data['list'] = serializers.serialize('json', queryset)
 
     return HttpResponse(json.dumps(response_data), content_type='application/json')
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 38/94] bitbake: webhob: validate inputs for build api
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (36 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 37/94] bitbake: webhob: add ordering capabilities to build api Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 39/94] bitbake: webhob: generic view for multiple models Alex DAMIAN
                   ` (56 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch validates the inputs for the build api
by checking the following:
- have only one colon;
- have equal number of terms on both sides of the colon;
- left side terms must be part of the Django model fields.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/views.py | 33 ++++++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 71e95b4..a71aebc 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -52,7 +52,7 @@ def layer_versions_recipes(request, layerversion_id):
 
 import json
 from django.core import serializers
-from django.http import HttpResponse
+from django.http import HttpResponse, HttpResponseBadRequest
 
 
 def builds(request):
@@ -69,8 +69,13 @@ def builds(request):
     except ValueError:
         offset = 0
 
-    ordering_string = request.GET.get('orderby', '')
-    filter_string = request.GET.get('filter', '')
+    ordering_string, invalid = _validate_input(request.GET.get('orderby', ''))
+    if invalid:
+        return HttpResponseBadRequest()
+
+    filter_string, invalid = _validate_input(request.GET.get('filter', ''))
+    if invalid:
+        return HttpResponseBadRequest()
 
     if filter_string:
         filter_terms = _get_filtering_terms(filter_string)
@@ -111,3 +116,25 @@ def _get_filtering_terms(filter_string):
     values = search_terms[1].split(',')
 
     return dict(zip(keys, values))
+
+def _validate_input(input):
+    invalid = 0
+
+    if input:
+        input_list = input.split(":")
+
+        # Check we have only one colon
+        if len(input.split(":")) != 2:
+            invalid = 1
+
+        # Check we have an equal number of terms both sides of the colon
+        if len(input_list[0].split(',')) != len(input_list[1].split(',')):
+            invalid = 1
+
+        # Check we are looking for a valid field
+        valid_fields = Build._meta.get_all_field_names()
+        for field in input_list[0].split(','):
+            if field not in valid_fields:
+                invalid = 1
+
+    return input, invalid
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 39/94] bitbake: webhob: generic view for multiple models
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (37 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 38/94] bitbake: webhob: validate inputs for " Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 40/94] bitbake: webhob: enhance Simple browser navigation Alex DAMIAN
                   ` (55 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch creates a generic view that supports multiple
models. It allows exploration of the fields of the models and
it has validation of inputs given.

Also avoids code replication.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>

Conflicts:
	bitbake/lib/webhob/bldviewer/views.py
---
 bitbake/lib/webhob/bldviewer/api.py   |  4 +++-
 bitbake/lib/webhob/bldviewer/views.py | 33 ++++++++++++++++++++++++---------
 2 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/api.py b/bitbake/lib/webhob/bldviewer/api.py
index d945861..3b2d7d2 100644
--- a/bitbake/lib/webhob/bldviewer/api.py
+++ b/bitbake/lib/webhob/bldviewer/api.py
@@ -2,5 +2,7 @@ from django.conf.urls import patterns, include, url
 
 
 urlpatterns = patterns('bldviewer.views',
-        url(r'^builds/$', 'builds', name='builds'),
+        url(r'^builds/$', 'model_explorer',  {'model_name':'build'}, name='builds'),
+        url(r'^tasks/$', 'model_explorer', {'model_name':'task'}, name='task'),
+        url(r'^packages/$', 'model_explorer', {'model_name':'package'}, name='package'),
 )
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index a71aebc..73f7ba7 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -1,5 +1,5 @@
 from django.shortcuts import render
-from orm.models import Build, Task, Layer, Layer_Version, Task_Dependency, Recipe
+from orm.models import Build, Task, Layer, Layer_Version, Task_Dependency, Recipe, Package
 
 
 def build(request):
@@ -55,9 +55,20 @@ from django.core import serializers
 from django.http import HttpResponse, HttpResponseBadRequest
 
 
-def builds(request):
+def model_explorer(request, model_name):
+
     DESCENDING = 'desc'
     response_data = {}
+    model_mapping = {
+        'build': Build,
+        'task': Task,
+        'package': Package,
+        }
+
+    if model_name not in model_mapping.keys():
+        return HttpResponseBadRequest()
+
+    model = model_mapping[model_name]
 
     try:
         limit = int(request.GET.get('limit', 0))
@@ -69,22 +80,24 @@ def builds(request):
     except ValueError:
         offset = 0
 
-    ordering_string, invalid = _validate_input(request.GET.get('orderby', ''))
+    ordering_string, invalid = _validate_input(request.GET.get('orderby', ''),
+                                               model)
     if invalid:
         return HttpResponseBadRequest()
 
-    filter_string, invalid = _validate_input(request.GET.get('filter', ''))
+    filter_string, invalid = _validate_input(request.GET.get('filter', ''),
+                                             model)
     if invalid:
         return HttpResponseBadRequest()
 
     if filter_string:
         filter_terms = _get_filtering_terms(filter_string)
         try:
-            queryset = Build.objects.filter(**filter_terms)
+            queryset = model.objects.filter(**filter_terms)
         except ValueError:
             queryset = []
     else:
-        queryset = Build.objects.all()
+        queryset = model.objects.all()
 
     if offset and limit:
         queryset = queryset[offset:(offset+limit)]
@@ -107,7 +120,8 @@ def builds(request):
 
     response_data['list'] = serializers.serialize('json', queryset)
 
-    return HttpResponse(json.dumps(response_data), content_type='application/json')
+    return HttpResponse(json.dumps(response_data),
+                        content_type='application/json')
 
 def _get_filtering_terms(filter_string):
 
@@ -117,7 +131,8 @@ def _get_filtering_terms(filter_string):
 
     return dict(zip(keys, values))
 
-def _validate_input(input):
+def _validate_input(input, model):
+
     invalid = 0
 
     if input:
@@ -132,7 +147,7 @@ def _validate_input(input):
             invalid = 1
 
         # Check we are looking for a valid field
-        valid_fields = Build._meta.get_all_field_names()
+        valid_fields = model._meta.get_all_field_names()
         for field in input_list[0].split(','):
             if field not in valid_fields:
                 invalid = 1
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 40/94] bitbake: webhob: enhance Simple browser navigation
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (38 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 39/94] bitbake: webhob: generic view for multiple models Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 41/94] bitbake: webhob: improve startup script Alex DAMIAN
                   ` (54 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We make a couple of changes to allow easier navigation
in the simple data display.

- all templates now inherit the common base.html
- simple navigation menu added on top of page
- there is a link between a task name in tasks page,
and the recipe entry
- all tabs converted to 4-spaces

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/base.html   |  16 +++
 bitbake/lib/webhob/bldviewer/templates/build.html  | 103 +++++++++----------
 bitbake/lib/webhob/bldviewer/templates/layer.html  |  44 ++++----
 bitbake/lib/webhob/bldviewer/templates/recipe.html |  90 ++++++++--------
 bitbake/lib/webhob/bldviewer/templates/task.html   | 114 ++++++++++-----------
 bitbake/lib/webhob/bldviewer/urls.py               |   2 +-
 6 files changed, 180 insertions(+), 189 deletions(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/base.html

diff --git a/bitbake/lib/webhob/bldviewer/templates/base.html b/bitbake/lib/webhob/bldviewer/templates/base.html
new file mode 100644
index 0000000..ab3c3b7
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/base.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<html>
+	<head>
+		<title>WebHob Simple Explorer</title>
+	</head>
+
+<body>
+<div>Menu: <a href="/simple/build/">All Builds</a>&nbsp;|&nbsp;<a href="/simple/layer/">All Layers</a>
+</div>
+{% block pagecontent %}
+
+{% endblock %}
+</body>
+</html>
+
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index cd257e0..8ee3800 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -1,54 +1,49 @@
-<html>
-
-	<head>
-		<title>WebHob Build page</title>
-	</head>
-
-<body>
-	<h1>WebHob Builds</h1>
-
-	<table border="1">
-
-	{% load projecttags %}
-
-		<tr>
-			<th>Outcome</th>
-			<th>Started On</th>
-			<th>Completed On</th>
-			<th>Target</th>
-	        <th>Image ?</th>
-			<th>Machine</th>
-			<th>Time</th>
-			<th>Errors</th>
-			<th>Warnings</th>
-			<th>Output</th>
-			<th>Log</th>
-			<th>Bitbake Version</th>
-			<th>Build Name</th>
-		</tr>
-
-		{% for build in builds %}
-
-		<tr>
-			<td><a href="/build/{{build.id}}">{{build.get_outcome_display}}</a></td>
-			<td>{{build.started_on}}</td>
-			<td>{{build.completed_on}}</td>
-			<td>{{build.target}}</td>
-	        <td>{{build.is_image}}</td>
-			<td>{{build.machine.name}}</td>
-			<td>{% time_difference build.started_on build.completed_on %}</td>
-			<td>{{build.errors_no}}</td>
-			<td>{{build.warnings_no}}</td>
-			<td>{{build.image_fstypes}}</td>
-			<td>{{build.cooker_log_path}}</td>
-			<td>{{build.bitbake_version}}</td>
-			<td>{{build.build_name}}</td>
-		</tr>
-
-		{% endfor %}
-
-	</table>
-
-</body>
-
-</html>
+{% extends "base.html" %}
+
+{% block pagecontent %}
+    <h1>WebHob Builds</h1>
+
+    <table border="1">
+
+    {% load projecttags %}
+
+        <tr>
+            <th>Outcome</th>
+            <th>Started On</th>
+            <th>Completed On</th>
+            <th>Target</th>
+            <th>Image ?</th>
+            <th>Machine</th>
+            <th>Time</th>
+            <th>Errors</th>
+            <th>Warnings</th>
+            <th>Output</th>
+            <th>Log</th>
+            <th>Bitbake Version</th>
+            <th>Build Name</th>
+        </tr>
+
+        {% for build in builds %}
+
+        <tr>
+            <td><a href="/simple/build/{{build.id}}/task/">{{build.get_outcome_display}}</a></td>
+            <td>{{build.started_on}}</td>
+            <td>{{build.completed_on}}</td>
+            <td>{{build.target}}</td>
+            <td>{{build.is_image}}</td>
+            <td>{{build.machine.name}}</td>
+            <td>{% time_difference build.started_on build.completed_on %}</td>
+            <td>{{build.errors_no}}</td>
+            <td>{{build.warnings_no}}</td>
+            <td>{{build.image_fstypes}}</td>
+            <td>{{build.cooker_log_path}}</td>
+            <td>{{build.bitbake_version}}</td>
+            <td>{{build.build_name}}</td>
+        </tr>
+
+        {% endfor %}
+
+    </table>
+{% endblock %}
+
+
diff --git a/bitbake/lib/webhob/bldviewer/templates/layer.html b/bitbake/lib/webhob/bldviewer/templates/layer.html
index 24bd86b..9d44f17 100644
--- a/bitbake/lib/webhob/bldviewer/templates/layer.html
+++ b/bitbake/lib/webhob/bldviewer/templates/layer.html
@@ -1,29 +1,25 @@
-<html>
+{% extends "base.html" %}
 
-	<head>
-		<title>WebHob Layer page</title>
-	</head>
+{% block pagecontent %}
+    <h1>WebHob Layers</h1>
 
-<body>
-	<h1>WebHob Layers</h1>
+    <table border="1">
 
-	<table border="1">
+    {% load projecttags %}
 
-	{% load projecttags %}
-
-		<tr>
-			<th>Name</th>
-			<th>Local Path</th>
-			<th>Layer Index URL</th>
+        <tr>
+            <th>Name</th>
+            <th>Local Path</th>
+            <th>Layer Index URL</th>
             <th>Known Versions</th>
-		</tr>
+        </tr>
 
-		{% for layer in layers %}
+        {% for layer in layers %}
 
-		<tr>
-			<td>{{layer.name}}</td>
-			<td>{{layer.local_path}}</td>
-			<td><a href='{{layer.layer_index_url}}'>{{layer.layer_index_url}}</a></td>
+        <tr>
+            <td>{{layer.name}}</td>
+            <td>{{layer.local_path}}</td>
+            <td><a href='{{layer.layer_index_url}}'>{{layer.layer_index_url}}</a></td>
             <td><table>
             {% for lv in layer.versions %}
                 <tr><td>
@@ -31,12 +27,10 @@
                 </td></tr>
             {% endfor %}
             </table></td>
-		</tr>
-
-		{% endfor %}
+        </tr>
 
-	</table>
+        {% endfor %}
 
-</body>
+    </table>
 
-</html>
+{% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/recipe.html b/bitbake/lib/webhob/bldviewer/templates/recipe.html
index 8f5dcb3..5ebd880 100644
--- a/bitbake/lib/webhob/bldviewer/templates/recipe.html
+++ b/bitbake/lib/webhob/bldviewer/templates/recipe.html
@@ -1,48 +1,42 @@
-<html>
-
-	<head>
-		<title>WebHob Recipe page</title>
-	</head>
-
-<body>
-	<h1>WebHob Recipes for a Layer</h1>
-
-	<table border="1">
-
-	{% load projecttags %}
-
-		<tr>
-		</tr>
-			<th>Name</th>
-			<th>Version</th>
-			<th>Summary</th>
-			<th>Description</th>
-	        <th>Section</th>
-			<th>License</th>
-			<th>Homepage</th>
-			<th>Bugtracker</th>
-			<th>Author</th>
-			<th>File_path</th>
-
-		{% for recipe in recipes %}
-
-		<tr>
-			<td>{{recipe.name}}</td>
-			<td>{{recipe.version}}</td>
-			<td>{{recipe.summary}}</td>
-			<td>{{recipe.description}}</td>
-	        <td>{{recipe.section}}</td>
-			<td>{{recipe.license}}</td>
-			<td>{{recipe.homepage}}</td>
-			<td>{{recipe.bugtracker}}</td>
-			<td>{{recipe.author}}</td>
-			<td>{{recipe.file_path}}</td>
-		</tr>
-
-		{% endfor %}
-
-	</table>
-
-</body>
-
-</html>
+{% extends "base.html" %}
+
+{% block pagecontent %}
+    <h1>WebHob Recipes for a Layer</h1>
+
+    <table border="1">
+
+    {% load projecttags %}
+
+        <tr>
+        </tr>
+            <th>Name</th>
+            <th>Version</th>
+            <th>Summary</th>
+            <th>Description</th>
+            <th>Section</th>
+            <th>License</th>
+            <th>Homepage</th>
+            <th>Bugtracker</th>
+            <th>Author</th>
+            <th>File_path</th>
+
+        {% for recipe in recipes %}
+
+        <tr>
+            <td><a name="{{recipe.name}}">{{recipe.name}}</a></td>
+            <td>{{recipe.version}}</td>
+            <td>{{recipe.summary}}</td>
+            <td>{{recipe.description}}</td>
+            <td>{{recipe.section}}</td>
+            <td>{{recipe.license}}</td>
+            <td>{{recipe.homepage}}</td>
+            <td>{{recipe.bugtracker}}</td>
+            <td>{{recipe.author}}</td>
+            <td>{{recipe.file_path}}</td>
+        </tr>
+
+        {% endfor %}
+
+    </table>
+
+{% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 1797028..88c82d8 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -1,72 +1,64 @@
-<html>
-
-	<head>
-		<title>WebHob Task page</title>
-	</head>
-
-<body>
-
-	<h1>WebHob Tasks</h1>
-
-	{% if not tasks %}
-		<p>No tasks were executed in this build!</p>
-	{% else %}
-
-		<table border="1">
-
-			<tr>
-			<th>Order</th>
-			<th>Task</th>
-			<th>Recipe Version</th>
-			<th>Task Type</th>
-			<th>Outcome</th>
-			<th>Errors</th>
-			<th>Warnings</th>
-			<th>Time</th>
-			<th>CPU usage</th>
-			<th>Disk I/O</th>
-			<th>Script type</th>
-			<th>File path</th>
+{% extends "base.html" %}
+
+{% block pagecontent %}
+    <h1>WebHob Tasks</h1>
+
+    {% if not tasks %}
+        <p>No tasks were executed in this build!</p>
+    {% else %}
+
+        <table border="1">
+
+            <tr>
+            <th>Order</th>
+            <th>Task</th>
+            <th>Recipe Version</th>
+            <th>Task Type</th>
+            <th>Outcome</th>
+            <th>Errors</th>
+            <th>Warnings</th>
+            <th>Time</th>
+            <th>CPU usage</th>
+            <th>Disk I/O</th>
+            <th>Script type</th>
+            <th>File path</th>
             <th>Depends</th>
-			</tr>
-
-			{% for task in tasks %}
-
-				<tr>
-				<td>{{task.order}}</td>
-				<td><a name="{{task.recipe.name}}.{{task.task_name}}">
-				{{task.recipe.name}}.{{task.task_name}}</a></td>
-				<td>{{task.recipe.version}}</td>
-
-				{% if task.task_executed %}
-				<td>Executed</td>
-				{% else %}
-				<td>Prebuilt</td>
-				{% endif %}
-
-				<td>{{task.get_outcome_display}}</td>
-				<td>{{task.errors_no}}</td>
-				<td>{{task.warnings_no}}</td>
-				<td>{{task.elapsed_time}}</td>
-				<td>{{task.cpu_usage}}</td>
-				<td>{{task.disk_io}}</td>
-				<td>{{task.get_script_type_display}}</td>
-				<td>{{task.recipe.file_path}}</td>
+            </tr>
+
+            {% for task in tasks %}
+
+                <tr>
+                <td>{{task.order}}</td>
+                <td><a name="{{task.recipe.name}}.{{task.task_name}}">
+                <a href="/simple/layerversions/{{task.recipe.layer_version_id}}/recipes/#{{task.recipe.name}}">{{task.recipe.name}}</a>.{{task.task_name}}</a></td>
+                <td>{{task.recipe.version}}</td>
+
+                {% if task.task_executed %}
+                <td>Executed</td>
+                {% else %}
+                <td>Prebuilt</td>
+                {% endif %}
+
+                <td>{{task.get_outcome_display}}</td>
+                <td>{{task.errors_no}}</td>
+                <td>{{task.warnings_no}}</td>
+                <td>{{task.elapsed_time}}</td>
+                <td>{{task.cpu_usage}}</td>
+                <td>{{task.disk_io}}</td>
+                <td>{{task.get_script_type_display}}</td>
+                <td>{{task.recipe.file_path}}</td>
                 <td>
                 {% for tt in task.depends_on %}
                     <a href="#{{tt.recipe.name}}.{{tt.task_name}}">
                     {{tt.recipe.name}}.{{tt.task_name}}</a><br/>
                 {% endfor %}
                 </td>
-				</tr>
-
-			{% endfor %}
-
-		</table>
+                </tr>
 
-	{% endif %}
+            {% endfor %}
 
-</body>
+        </table>
 
-</html>
+    {% endif %}
 
+{% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index b6c6796..5f07161 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -5,6 +5,6 @@ urlpatterns = patterns('bldviewer.views',
         url(r'^build/$', 'build', name='build'),
         url(r'^build/(?P<build_id>\d+)/task/$', 'task', name='task'),
         url(r'^layer/$', 'layer', name='layer'),
-        url(r'^layerversions/(?P<layerversion_id>\d+)/recipes$', 'layer_versions_recipes', name='layer_versions_recipes'),
+        url(r'^layerversions/(?P<layerversion_id>\d+)/recipes/.*$', 'layer_versions_recipes', name='layer_versions_recipes'),
         url(r'^$', redirect_to, {'url': 'build/'}),
 )
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 41/94] bitbake: webhob: improve startup script
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (39 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 40/94] bitbake: webhob: enhance Simple browser navigation Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 42/94] bitbake: webhob: improve validation code flow Alex DAMIAN
                   ` (53 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

A couple of modification targeted at making the
system start/stop script more resilient.

Also, it now checks for the correct Django version.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/bin/webhob | 53 ++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 42 insertions(+), 11 deletions(-)

diff --git a/bitbake/bin/webhob b/bitbake/bin/webhob
index 44a25cb..7b74465 100755
--- a/bitbake/bin/webhob
+++ b/bitbake/bin/webhob
@@ -22,13 +22,13 @@
 
 # Helper function to kill a background webhob development server
 
-function webserverKillAllComponents()
+function webserverKillAll()
 {
 	local pidfile
 	for pidfile in ${BUILDDIR}/whbmain.pid; do
 		if [ -f ${pidfile} ]; then
 		while kill -0 $(< ${pidfile}) 2>/dev/null; do
-			kill -SIGTERM -$(< ${pidfile})
+			kill -SIGTERM -$(< ${pidfile}) 2>/dev/null
 			sleep 1;
 		done;
 		rm  ${pidfile}
@@ -36,6 +36,15 @@ function webserverKillAllComponents()
 	done
 }
 
+
+function webserverStartAll()
+{
+        python $BBBASEDIR/lib/webhob/manage.py syncdb || (echo "Failed db sync, stopping system start" 1>&2 && return 2)
+        python $BBBASEDIR/lib/webhob/manage.py runserver </dev/null >${BUILDDIR}/whbmain.log 2>&1 & echo $! >${BUILDDIR}/whbmain.pid
+        return 0
+}
+
+
 # We make sure we're running in the current shell and in a good environment
 
 if [ -z "$ZSH_NAME" ] && [ `basename "$0"` = `basename $BASH_SOURCE` ]; then
@@ -50,6 +59,16 @@ fi
 
 BBBASEDIR=`dirname ${BASH_SOURCE}`/..
 
+
+# Verify prerequisites
+
+if ! echo "import django; print (1,4,5) == django.VERSION[0:3]" | python 2>/dev/null | grep True >/dev/null; then
+    echo -e "This program needs Django 1.4.5. Please install with\n\nsudo pip install django==1.4.5"
+    return 2
+fi
+
+
+
 # Determine the action. If specified by arguments, fine, if not, toggle it
 if [ "x$1" == "xstart" ] || [ "x$1" == "xstop" ]; then
     CMD="$1"
@@ -61,6 +80,13 @@ else
     fi;
 fi
 
+NODSI=0
+if [ "x$2" == "xnodsi" ]; then
+    NODSI=1
+fi
+
+echo "The system will $CMD."
+
 # Make sure it's safe to run by checking bitbake lock
 
 lock=1
@@ -69,11 +95,11 @@ if [ -e $BUILDDIR/bitbake.lock ]; then
 fi
 
 if [ ${CMD} == "start" ] && ( [ $lock -eq 0 ] || [ -e $BUILDDIR/whbmain.pid ] ); then
-    echo "Error: bitbake lock state error. System may be already on." 2>&1
+    echo "Error: bitbake lock state error. System is already on." 2>&1
     return 3
 elif [ ${CMD} == "stop" ] && ( [ $lock -eq 1 ] || ! [ -e $BUILDDIR/whbmain.pid ] ) ; then
-    echo "Error: bitbake lock state error. System may be already off.
-manually stop system with bitbake -m / webserverKillAllComponents" 2>&1
+    echo "Error: bitbake lock state error. Trying to stop a stopped system ?
+manually stop system with bitbake -m / webserverKill" 2>&1
     return 3
 fi
 
@@ -82,19 +108,24 @@ fi
 
 case $CMD in
     start )
+        webserverStartAll || return 4
         unset BBSERVER
-        python $BBBASEDIR/lib/webhob/manage.py syncdb || (echo "Failed db sync, stopping system start" 1>&2 && return 0)
         bitbake --server-only -t xmlrpc -B localhost:8200
         export BBSERVER=localhost:8200
-        bitbake --observe-only -u dsi >/dev/null 2>&1 & echo $! >${BUILDDIR}/dsi.pid
-        python $BBBASEDIR/lib/webhob/manage.py runserver </dev/null >${BUILDDIR}/whbmain.log 2>&1 & echo $! >${BUILDDIR}/whbmain.pid
+        if [ $NODSI == 0 ]; then        # we start the DSI only if not inhibited
+            bitbake --observe-only -u dsi >/dev/null 2>&1 & echo $! >${BUILDDIR}/dsi.pid
+        fi
     ;;
     stop )
-        kill $(< ${BUILDDIR}/dsi.pid )
-        rm ${BUILDDIR}/dsi.pid
+        if [ -f ${BUILDDIR}/dsi.pid ]; then
+            kill $(< ${BUILDDIR}/dsi.pid )
+            rm ${BUILDDIR}/dsi.pid
+        fi
         bitbake -m
         unset BBSERVER
-        webserverKillAllComponents
+        webserverKillAll
+    ;;
 esac
 
+echo "Successful ${CMD}."
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 42/94] bitbake: webhob: improve validation code flow
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (40 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 41/94] bitbake: webhob: improve startup script Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 43/94] bitbake: webhob: add more models to webhob API Alex DAMIAN
                   ` (52 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch improves the code flow in the validation method
by not repeating the split method over the same list and
by stoping at any invalid input encountered.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/views.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 73f7ba7..647a1f2 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -139,17 +139,20 @@ def _validate_input(input, model):
         input_list = input.split(":")
 
         # Check we have only one colon
-        if len(input.split(":")) != 2:
+        if len(input_list) != 2:
             invalid = 1
+            return None, invalid
 
         # Check we have an equal number of terms both sides of the colon
         if len(input_list[0].split(',')) != len(input_list[1].split(',')):
             invalid = 1
+            return None, invalid
 
         # Check we are looking for a valid field
         valid_fields = model._meta.get_all_field_names()
         for field in input_list[0].split(','):
             if field not in valid_fields:
                 invalid = 1
+                return None, invalid
 
     return input, invalid
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 43/94] bitbake: webhob: add more models to webhob API
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (41 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 42/94] bitbake: webhob: improve validation code flow Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 44/94] bitbake: webhob: add search for build model Alex DAMIAN
                   ` (51 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch enables webhob API to support recipe, layer and
layer_version models.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/api.py   | 3 +++
 bitbake/lib/webhob/bldviewer/views.py | 7 +++++--
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/api.py b/bitbake/lib/webhob/bldviewer/api.py
index 3b2d7d2..9fa701c 100644
--- a/bitbake/lib/webhob/bldviewer/api.py
+++ b/bitbake/lib/webhob/bldviewer/api.py
@@ -5,4 +5,7 @@ urlpatterns = patterns('bldviewer.views',
         url(r'^builds/$', 'model_explorer',  {'model_name':'build'}, name='builds'),
         url(r'^tasks/$', 'model_explorer', {'model_name':'task'}, name='task'),
         url(r'^packages/$', 'model_explorer', {'model_name':'package'}, name='package'),
+        url(r'^layers/$', 'model_explorer', {'model_name':'layer'}, name='layer'),
+        url(r'^recipes/$', 'model_explorer', {'model_name':'recipe'}, name='recipe'),
+        url(r'^layersversions/$', 'model_explorer', {'model_name':'layerversion'}, name='layerversion'),
 )
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 647a1f2..3ef6940 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -43,9 +43,9 @@ def layer(request):
 def layer_versions_recipes(request, layerversion_id):
     template = 'recipe.html'
     recipes = Recipe.objects.filter(layer_version__id = layerversion_id)
-    
+
     context = {'recipes': recipes}
-    
+
     return render(request, template, context)
 
 #### API
@@ -63,6 +63,9 @@ def model_explorer(request, model_name):
         'build': Build,
         'task': Task,
         'package': Package,
+        'layer': Layer,
+        'layerversion': Layer_Version,
+        'recipe': Recipe,
         }
 
     if model_name not in model_mapping.keys():
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 44/94] bitbake: webhob: add search for build model
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (42 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 43/94] bitbake: webhob: add more models to webhob API Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 45/94] bitbake: webhob: fix ordering issue Alex DAMIAN
                   ` (50 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch enables search on the build model. However, the
search can be executed only on a predefined list of fields
allowed to be searched.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/views.py | 17 +++++++++++++++++
 bitbake/lib/webhob/orm/models.py      |  3 +++
 2 files changed, 20 insertions(+)

diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 3ef6940..423fdf7 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -1,3 +1,6 @@
+import operator
+
+from django.db.models import Q
 from django.shortcuts import render
 from orm.models import Build, Task, Layer, Layer_Version, Task_Dependency, Recipe, Package
 
@@ -93,6 +96,8 @@ def model_explorer(request, model_name):
     if invalid:
         return HttpResponseBadRequest()
 
+    search_term = request.GET.get('search', '')
+
     if filter_string:
         filter_terms = _get_filtering_terms(filter_string)
         try:
@@ -102,6 +107,9 @@ def model_explorer(request, model_name):
     else:
         queryset = model.objects.all()
 
+    if search_term:
+        queryset = _get_search_results(search_term, queryset, model)
+
     if offset and limit:
         queryset = queryset[offset:(offset+limit)]
     elif offset:
@@ -159,3 +167,12 @@ def _validate_input(input, model):
                 return None, invalid
 
     return input, invalid
+
+def _get_search_results(search_term, queryset, model):
+    q_map = map(lambda x: Q(**{x+'__icontains': search_term}),
+                model.search_allowed_fields)
+
+    search_object = reduce(operator.or_, q_map)
+    queryset = queryset.filter(search_object)
+
+    return queryset
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 0d6cf15..a4721db 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -12,6 +12,9 @@ class Build(models.Model):
         (IN_PROGRESS, 'In Progress'),
     )
 
+    search_allowed_fields = ['target', 'machine__name',
+                             'cooker_log_path', 'image_fstypes']
+
     uuid = models.CharField(max_length=100, unique=True)
     target = models.CharField(max_length=100)
     is_image = models.BooleanField(default = False)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 45/94] bitbake: webhob: fix ordering issue
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (43 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 44/94] bitbake: webhob: add search for build model Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 46/94] bitbake: webhob: extend search for multiple terms Alex DAMIAN
                   ` (49 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel

From: Calin Dragomir <calinx.l.dragomir@intel.com>

This patch fixes the AssertionError issue that caused the
build page to break.
This happened because ordering cannot be done after a slicing
operation has been executed.
By placing the ordering before the slicing occurs, the issue
stops appearing.

Signed-off-by: Calin Dragomir <calinx.l.dragomir@intel.com>

Conflicts:
	bitbake/lib/webhob/bldviewer/views.py
---
 bitbake/lib/webhob/bldviewer/views.py | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 423fdf7..a5c4387 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -110,6 +110,13 @@ def model_explorer(request, model_name):
     if search_term:
         queryset = _get_search_results(search_term, queryset, model)
 
+    if ordering_string and queryset:
+        column, order = ordering_string.split(':')
+        if order.lower() == DESCENDING:
+            queryset = queryset.order_by('-' + column)
+        else:
+            queryset = queryset.order_by(column)
+
     if offset and limit:
         queryset = queryset[offset:(offset+limit)]
     elif offset:
@@ -122,13 +129,6 @@ def model_explorer(request, model_name):
     else:
         response_data['count'] = 0
 
-    if queryset and ordering_string:
-        column, order = ordering_string.split(':')
-        if order.lower() == DESCENDING:
-            queryset = queryset.order_by('-' + column)
-        else:
-            queryset = queryset.order_by(column)
-
     response_data['list'] = serializers.serialize('json', queryset)
 
     return HttpResponse(json.dumps(response_data),
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 46/94] bitbake: webhob: extend search for multiple terms
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (44 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 45/94] bitbake: webhob: fix ordering issue Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 47/94] bitbake: webhob: force bitbake server stop Alex DAMIAN
                   ` (48 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We extend the search functionality as to be able to narrow
down search results based on multiple terms.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/views.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index a5c4387..4f7b46c 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -169,10 +169,13 @@ def _validate_input(input, model):
     return input, invalid
 
 def _get_search_results(search_term, queryset, model):
-    q_map = map(lambda x: Q(**{x+'__icontains': search_term}),
+    search_objects = []
+    for st in search_term.split(" "):
+        q_map = map(lambda x: Q(**{x+'__icontains': st}),
                 model.search_allowed_fields)
 
-    search_object = reduce(operator.or_, q_map)
+        search_objects.append(reduce(operator.or_, q_map))
+    search_object = reduce(operator.and_, search_objects)
     queryset = queryset.filter(search_object)
 
     return queryset
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 47/94] bitbake: webhob: force bitbake server stop
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (45 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 46/94] bitbake: webhob: extend search for multiple terms Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 48/94] bitbake: webhob: simple interface dependency list Alex DAMIAN
                   ` (47 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We force a bitbake server stop by killing it on the stop command.

This will have no effect on well behaving servers since
they will be stopped anyway by the bitbake -m command above

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/bin/webhob | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/bitbake/bin/webhob b/bitbake/bin/webhob
index 7b74465..099f2d3 100755
--- a/bitbake/bin/webhob
+++ b/bitbake/bin/webhob
@@ -124,6 +124,8 @@ case $CMD in
         bitbake -m
         unset BBSERVER
         webserverKillAll
+        # force stop any misbehaving bitbake server
+        lsof bitbake.lock | awk '{print $2}' | grep "[0-9]\+" | xargs -n1 kill
     ;;
 esac
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 48/94] bitbake: webhob: simple interface dependency list
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (46 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 47/94] bitbake: webhob: force bitbake server stop Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 49/94] bitbake: dsi: add feature to store package information Alex DAMIAN
                   ` (46 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We add a diff to obtain a consistent layout of the dependency
field information for tasks.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/task.html | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 88c82d8..2d78668 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -48,10 +48,12 @@
                 <td>{{task.get_script_type_display}}</td>
                 <td>{{task.recipe.file_path}}</td>
                 <td>
+            <div style="height: 3em; overflow:auto">
                 {% for tt in task.depends_on %}
                     <a href="#{{tt.recipe.name}}.{{tt.task_name}}">
                     {{tt.recipe.name}}.{{tt.task_name}}</a><br/>
                 {% endfor %}
+            </div>
                 </td>
                 </tr>
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 49/94] bitbake: dsi: add feature to store package information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (47 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 48/94] bitbake: webhob: simple interface dependency list Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 50/94] bitbake: webhob: add simple viewer for " Alex DAMIAN
                   ` (45 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

I'm adding code to read package information and
package dependency off the buildhistory, if the
buildhistory feature is enabled, and the target is an image.

There are small changes to the events trapped since we
need to capture package information after the build is
completed.

Minor changes to internal state handling of BuildInfoHelper.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 75 ++++++++++++++++++++++++++++++++----
 bitbake/lib/bb/ui/dsi.py             | 30 +++++++--------
 bitbake/lib/webhob/orm/models.py     |  4 +-
 3 files changed, 85 insertions(+), 24 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 16e69c5..59b6fa3 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -8,7 +8,7 @@ import re
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 
 import webhob.whbmain.settings as whb_django_settings
-from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Package
+from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Package, Package_Dependency
 from webhob.orm.models import Task_Dependency
 
 
@@ -42,16 +42,14 @@ class ORMWrapper(object):
                                     build_name=build_info['build_name'],
                                     bitbake_version=build_info['bitbake_version'])
 
-
         return build
 
-    def update_build_object(self, build_obj, errors, warnings, taskfailures):
+    def update_build_object(self, build, errors, warnings, taskfailures):
 
         outcome = Build.SUCCEEDED
         if errors or taskfailures:
             outcome = Build.FAILED
 
-        build = Build.objects.get(uuid=build_obj.uuid)
         build.completed_on = datetime.datetime.now()
         build.errors_no = errors
         build.warnings_no = warnings
@@ -116,6 +114,22 @@ class ORMWrapper(object):
         return layer_object[0]
 
 
+    def save_package_information(self, build_obj, packagedict, bldpkgs, recipes):
+        for p in packagedict:
+            packagedict[p]['object'] = Package.objects.create( build = build_obj,
+                                        name = p,
+                                        size = packagedict[p]['size'])
+            if p in bldpkgs:
+                packagedict[p]['object'].version = bldpkgs[p]['version']
+                packagedict[p]['object'].recipe =  recipes[bldpkgs[p]['pn']]
+                packagedict[p]['object'].save()
+
+        for p in packagedict:
+            for px in packagedict[p]['depends']:
+                Package_Dependency.objects.create( package = packagedict[p]['object'],
+                                        depends_on = packagedict[px]['object'] );
+
+
 class BuildInfoHelper(object):
     """ This class gathers the build information from the server and sends it
         towards the ORM wrapper for storing in the database
@@ -123,13 +137,14 @@ class BuildInfoHelper(object):
         Keeps in memory all data that needs matching before writing it to the database
     """
 
-    def __init__(self, server):
+    def __init__(self, server, has_build_history = False):
         self._configure_django()
         self.internal_state = {}
         self.uuid = None
         self.task_order = 0
         self.server = server
         self.orm_wrapper = ORMWrapper()
+        self.has_build_history = has_build_history
 
     def _configure_django(self):
         # Add webhob to sys path for importing modules
@@ -343,7 +358,6 @@ class BuildInfoHelper(object):
 
     def update_build_information(self, event, errors, warnings, taskfailures):
         self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
-        del self.internal_state['build']
 
     def store_started_task(self, event):
         identifier = re.split(':', event.taskfile)[-1] + event.taskname
@@ -398,8 +412,56 @@ class BuildInfoHelper(object):
         self.orm_wrapper.get_update_task_object(task_information)
 
 
+    def read_package_dep_data(self, event):
+        # verify that we have something to read
+        if not self.internal_state['build'].is_image or not self.has_build_history:
+            print "not collecting package info ", self.internal_state['build'].is_image, self.has_build_history
+            return
+
+        # fair warning: code correlates to buildhistory.bbclass; anything changes there, needs to chage here too
+        TOPDIR, error = self.server.runCommand(['getVariable', 'TOPDIR'])
+        MACHINE_ARCH, error = self.server.runCommand(['getVariable', 'MACHINE_ARCH'])
+        TCLIBC, error = self.server.runCommand(['getVariable', 'TCLIBC'])
+        MULTIMACH_TARGET_SYS, error = self.server.runCommand(['getVariable', 'MULTIMACH_TARGET_SYS'])
+        SDK_NAME, error = self.server.runCommand(['getVariable', 'SDK_NAME'])
+        BUILDHISTORY_DIR = "%s/buildhistory" % TOPDIR
+        BUILDHISTORY_DIR_IMAGE = "%s/images/%s/%s/%s" % (BUILDHISTORY_DIR, MACHINE_ARCH, TCLIBC, self.internal_state['build'].target)
+
+        self.internal_state['packages'] = {}
+
+        with open("%s/installed-package-sizes.txt" % BUILDHISTORY_DIR_IMAGE, "r") as fin:
+            for line in fin:
+                line = line.rstrip(";")
+                psize, px = line.split("\t")
+                punit, pname = px.split(" ")
+                self.internal_state['packages'][pname.strip()] = {'size':psize, 'depends' : []}
+
+        with open("%s/depends.dot" % BUILDHISTORY_DIR_IMAGE, "r") as fin:
+            p = re.compile(r' -> ')
+            for line in fin:
+                line = line.rstrip(';')
+                linesplit = p.split(line)
+                if len(linesplit) == 2:
+                    pname = linesplit[0]
+                    dependsname = linesplit[1].split(" ")[0].strip().strip(";")
+                    if not pname in self.internal_state['packages']:
+                        self.internal_state['packages'][pname] = {'size': 0, 'depends' : []}
+                    if not dependsname in self.internal_state['packages']:
+                        self.internal_state['packages'][dependsname] = {'size': 0, 'depends' : []}
+                    self.internal_state['packages'][pname]['depends'].append(dependsname)
+
+        self.orm_wrapper.save_package_information(self.internal_state['build'], self.internal_state['packages'],
+                    self.internal_state['bldpkgs'], self.internal_state['recipes'])
+
 
     def store_dependency_information(self, event):
+
+        # save build time package information
+        self.internal_state['bldpkgs'] = {}
+        for pkg  in event._depgraph['packages']:
+            self.internal_state['bldpkgs'][pkg] = event._depgraph['packages'][pkg]
+
+        # save recipe information
         self.internal_state['recipes'] = {}
         for pn in event._depgraph['pn']:
 
@@ -447,4 +509,3 @@ class BuildInfoHelper(object):
                 dep = _save_a_task(taskdesc1)
                 Task_Dependency.objects.get_or_create( task = target, depends_on = dep )
 
-        del self.internal_state['recipes']
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index d03a7c5..98d225b 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -243,6 +243,14 @@ def main(server, eventHandler, params, tf = TerminalFilter):
 
     includelogs, loglines, consolelogfile = _log_settings_from_server(server)
 
+
+    # verify and warn
+    build_history_enabled = True
+    inheritlist, error = server.runCommand(["getVariable", "INHERIT"])
+    if not "buildhistory" in inheritlist.split(" "):
+        logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.")
+        build_history_enabled = False
+
     if sys.stdin.isatty() and sys.stdout.isatty():
         log_exec_tty = True
     else:
@@ -275,7 +283,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
     warnings = 0
     taskfailures = []
 
-    buildinfohelper = BuildInfoHelper(server)
+    buildinfohelper = BuildInfoHelper(server, build_history_enabled)
     buildinfohelper.store_layer_info()
 
     termfilter = tf(main, helper, console, format)
@@ -453,28 +461,20 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 # timestamp should be added for this
                 continue
 
-            if isinstance(event, bb.event.OperationStarted):
-                # timestamp should be added for this
+            if isinstance(event, (bb.event.TreeDataPreparationStarted, bb.event.TreeDataPreparationCompleted)):
                 continue
 
-            if isinstance(event, bb.event.OperationCompleted):
-                # timestamp should be added for this
-                # signal a complete operation
-                # calculate timing
-                continue
-
-            if isinstance(event, bb.event.DiskFull):
-                # trigger an error
+            if isinstance(event, (bb.event.BuildCompleted)):
+                buildinfohelper.read_package_dep_data(event)
+                buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
                 continue
 
-            if isinstance(event, (bb.event.BuildCompleted,
-                                  bb.command.CommandCompleted,
+            if isinstance(event, (bb.command.CommandCompleted,
                                   bb.command.CommandFailed,
                                   bb.command.CommandExit)):
-                buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
 
                 # we start a new build info
-                buildinfohelper = BuildInfoHelper(server)
+                buildinfohelper = BuildInfoHelper(server, build_history_enabled)
                 buildinfohelper.store_layer_info()
                 continue
 
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index a4721db..774cdff 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -110,9 +110,9 @@ class Artifact(models.Model):
 
 class Package(models.Model):
     build = models.ForeignKey('Build', related_name='package_build')
-    recipe = models.ForeignKey('Recipe', related_name='package_recipe')
+    recipe = models.ForeignKey('Recipe', related_name='package_recipe', null=True)
     name = models.CharField(max_length=100)
-    version = models.CharField(max_length=100)
+    version = models.CharField(max_length=100, default="")
     size = models.IntegerField()
 
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 50/94] bitbake: webhob: add simple viewer for package information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (48 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 49/94] bitbake: dsi: add feature to store package information Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 51/94] bitbake: dsi: fix build stats data gathering Alex DAMIAN
                   ` (44 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

If the target built is an image, we add a link to
a page which lists all the packages built for this build,
with sizes, version names and recipes linked in.
The package dependency information is also shown.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/build.html  |  2 +-
 .../lib/webhob/bldviewer/templates/package.html    | 42 ++++++++++++++++++++++
 bitbake/lib/webhob/bldviewer/urls.py               |  1 +
 bitbake/lib/webhob/bldviewer/views.py              | 18 +++++++++-
 4 files changed, 61 insertions(+), 2 deletions(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/package.html

diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index 8ee3800..15d88a0 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -30,7 +30,7 @@
             <td>{{build.started_on}}</td>
             <td>{{build.completed_on}}</td>
             <td>{{build.target}}</td>
-            <td>{{build.is_image}}</td>
+            <td>{% if build.is_image %} <a href="/simple/build/{{build.id}}/package/">{{build.is_image}}</a>{% else %} {{build.is_image}} {% endif %}</td>
             <td>{{build.machine.name}}</td>
             <td>{% time_difference build.started_on build.completed_on %}</td>
             <td>{{build.errors_no}}</td>
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
new file mode 100644
index 0000000..d31ecbb
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -0,0 +1,42 @@
+{% extends "base.html" %}
+
+{% block pagecontent %}
+    <h1>WebHob Packages</h1>
+
+    {% if not packages %}
+        <p>No packages were build in this build!</p>
+    {% else %}
+
+        <table border="1">
+
+            <tr>
+            <th>Name</th>
+            <th>Version</th>
+            <th>Size (in KiB)</th>
+            <th>Recipe</th>
+            <th>Depends on</th>
+            </tr>
+
+            {% for package in packages %}
+
+                <tr>
+                <td><a name="#{{package.name}}">{{package.name}}</a></td>
+                <td>{{package.version}}</td>
+                <td>{{package.size}}</td>
+                <td><a name="{{package.recipe.name}}.{{package.package_name}}">
+                <a href="/simple/layerversions/{{package.recipe.layer_version_id}}/recipes/#{{package.recipe.name}}">{{package.recipe.name}}</a>{{package.package_name}}</a></td>
+                <td>
+            <div style="height: 3em; overflow:auto">
+                    {% for d in package.depends_on %}
+                    <a href="#{{d.name}}">{{d.name}}</a><br/>
+                    {% endfor %} 
+            </div>
+                </td>
+            
+            {% endfor %}        
+
+        </table>
+
+    {% endif %}
+
+{% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index 5f07161..8b8b5c3 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -4,6 +4,7 @@ from django.views.generic.simple import redirect_to
 urlpatterns = patterns('bldviewer.views',
         url(r'^build/$', 'build', name='build'),
         url(r'^build/(?P<build_id>\d+)/task/$', 'task', name='task'),
+        url(r'^build/(?P<build_id>\d+)/package/$', 'package', name='package'),
         url(r'^layer/$', 'layer', name='layer'),
         url(r'^layerversions/(?P<layerversion_id>\d+)/recipes/.*$', 'layer_versions_recipes', name='layer_versions_recipes'),
         url(r'^$', redirect_to, {'url': 'build/'}),
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 4f7b46c..99e4b99 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -2,7 +2,7 @@ import operator
 
 from django.db.models import Q
 from django.shortcuts import render
-from orm.models import Build, Task, Layer, Layer_Version, Task_Dependency, Recipe, Package
+from orm.models import Build, Task, Layer, Layer_Version, Task_Dependency, Recipe, Package, Package_Dependency
 
 
 def build(request):
@@ -29,6 +29,22 @@ def task(request, build_id):
 
     return render(request, template, context)
 
+def package(request, build_id):
+    template = 'package.html'
+
+    packages = Package.objects.filter(build=build_id)
+    package_depends = Package_Dependency.objects.filter(package__in=packages)
+
+    for t in packages:
+        t.depends_on = []
+        for k in package_depends:
+            if t == k.package:
+                t.depends_on.append(k.depends_on)
+
+    context = {'packages': packages}
+
+    return render(request, template, context)
+
 def layer(request):
     template = 'layer.html'
     layer_info = Layer.objects.all()
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 51/94] bitbake: dsi: fix build stats data gathering
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (49 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 50/94] bitbake: webhob: add simple viewer for " Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 52/94] bitbake: dsi: update build object on command end Alex DAMIAN
                   ` (43 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We set the default values for build stats to be NULL so
that we can distinguish the value from a legitimate 0.

Also fixing the data gathering since model field semantics
changed - the version is kept on a separate field.

[YOCTO #4967]

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 2 +-
 bitbake/lib/webhob/orm/models.py     | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 59b6fa3..1d8cb44 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -286,7 +286,7 @@ class BuildInfoHelper(object):
         target = self.internal_state['target']
         machine = self.internal_state['build'].machine.name
         buildname = self.internal_state['build'].build_name
-        package = task_object.recipe.name
+        package = task_object.recipe.name + "-" + task_object.recipe.version.strip(":")
 
         build_stats_path = build_stats_format.format(tmpdir=tmp_dir, target=target,
                                                      machine=machine, buildname=buildname,
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 774cdff..f2cc7d9 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -84,8 +84,8 @@ class Task(models.Model):
     script_type = models.IntegerField(choices=TASK_CODING, default=CODING_PYTHON)
     line_number = models.IntegerField(default=0)
     py_stack_trace = models.TextField(null=True)
-    disk_io = models.IntegerField(default=0)
-    cpu_usage = models.DecimalField(max_digits=6, decimal_places=2, default=0)
+    disk_io = models.IntegerField(null=True)
+    cpu_usage = models.DecimalField(max_digits=6, decimal_places=2, null=True)
     elapsed_time = models.CharField(max_length=50, default=0)
     errors_no = models.IntegerField(default=0)
     warnings_no = models.IntegerField(default=0)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 52/94] bitbake: dsi: update build object on command end
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (50 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 51/94] bitbake: dsi: fix build stats data gathering Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 53/94] bitbake: dsi: fix sstate task information gathering Alex DAMIAN
                   ` (42 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We need to make the final update to the build object on
command completion, regardless of the status of the completion,
in order to avoid leaving builds in the "In Progress" state.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/dsi.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 98d225b..3e0b7ff 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -474,6 +474,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                                   bb.command.CommandExit)):
 
                 # we start a new build info
+                buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
                 buildinfohelper = BuildInfoHelper(server, build_history_enabled)
                 buildinfohelper.store_layer_info()
                 continue
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 53/94] bitbake: dsi: fix sstate task information gathering
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (51 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 52/94] bitbake: dsi: update build object on command end Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 54/94] bitbake: webhob: clean up starting script Alex DAMIAN
                   ` (41 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Fixing the information gathering for sstate-related tasks -
now the start and completion events are correctly handled,
and we get an update for tasks that were covered by sstate
tasks.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 6 +++++-
 bitbake/lib/bb/ui/dsi.py             | 5 ++---
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 1d8cb44..bfd7fef 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -67,7 +67,11 @@ class ORMWrapper(object):
         for v in vars(task_object):
             if v in task_information.keys():
                 vars(task_object)[v] = task_information[v]
+        # if we got covered by a setscene task, we're SSTATE
+        if task_object.outcome == Task.OUTCOME_COVERED and 1 == Task.objects.filter(task_executed=True, build = task_object.build, recipe = task_object.recipe, task_name=task_object.task_name+"_setscene").count():
+            task_object.outcome = Task.OUTCOME_SSTATE
 
+        # mark down duration if we have a start time
         if 'start_time' in task_information.keys():
             duration = datetime.datetime.now() - task_information['start_time']
             task_object.elapsed_time = duration.total_seconds()
@@ -393,7 +397,7 @@ class BuildInfoHelper(object):
         except:
             pass
 
-        if isinstance(event, bb.runqueue.runQueueTaskCompleted):
+        if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.sceneQueueTaskCompleted)):
             task_information['outcome'] = Task.OUTCOME_SUCCESS     # TODO: needs to use constants
             task_build_stats = self._get_task_build_stats(self.orm_wrapper.get_update_task_object(task_information))
             task_information['cpu_usage'] = task_build_stats['cpu_usage']
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 3e0b7ff..0305121 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -415,6 +415,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 continue
 
             if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+                buildinfohelper.store_started_task(event)
                 logger.info("Running setscene task %d of %d (%s)" % (event.stats.completed + event.stats.active + event.stats.failed + 1, event.stats.total, event.taskstring))
                 continue
 
@@ -446,10 +447,8 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                              event.taskid, event.taskstring, event.exitcode)
                 continue
 
-            if isinstance(event, bb.runqueue.sceneQueueTaskFailed):
+            if isinstance(event, (bb.runqueue.sceneQueueTaskCompleted, bb.runqueue.sceneQueueTaskFailed)):
                 buildinfohelper.update_and_store_task(event)
-                logger.warn("Setscene task %s (%s) failed with exit code '%s' - real task will be run instead",
-                             event.taskid, event.taskstring, event.exitcode)
                 continue
 
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 54/94] bitbake: webhob: clean up starting script
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (52 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 53/94] bitbake: dsi: fix sstate task information gathering Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 55/94] bitbake: dsi: store log information Alex DAMIAN
                   ` (40 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Clean up of webhob start-up script as to make sure
it aborts the startup on database errors, and
that it doesn't execute superflous kill commands when
stopping.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/bin/webhob | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/bitbake/bin/webhob b/bitbake/bin/webhob
index 099f2d3..aa35294 100755
--- a/bitbake/bin/webhob
+++ b/bitbake/bin/webhob
@@ -39,9 +39,13 @@ function webserverKillAll()
 
 function webserverStartAll()
 {
-        python $BBBASEDIR/lib/webhob/manage.py syncdb || (echo "Failed db sync, stopping system start" 1>&2 && return 2)
+        retval=0
+        python $BBBASEDIR/lib/webhob/manage.py syncdb || retval=1
+        if [ $retval -eq 1 ]; then
+                echo "Failed db sync, stopping system start" 1>&2
+        fi
         python $BBBASEDIR/lib/webhob/manage.py runserver </dev/null >${BUILDDIR}/whbmain.log 2>&1 & echo $! >${BUILDDIR}/whbmain.pid
-        return 0
+        return $retval
 }
 
 
@@ -125,7 +129,7 @@ case $CMD in
         unset BBSERVER
         webserverKillAll
         # force stop any misbehaving bitbake server
-        lsof bitbake.lock | awk '{print $2}' | grep "[0-9]\+" | xargs -n1 kill
+        lsof bitbake.lock | awk '{print $2}' | grep "[0-9]\+" | xargs -n1 -r kill
     ;;
 esac
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 55/94] bitbake: dsi: store log information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (53 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 54/94] bitbake: webhob: clean up starting script Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 56/94] bitbake: webhob: add simple visualisation for build errors Alex DAMIAN
                   ` (39 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding database model for storing Log Information

Adding code to store log information for errors
and warnings coming through the build system.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 41 +++++++++++++++++++++++++++++++++---
 bitbake/lib/bb/ui/dsi.py             |  9 ++++++--
 bitbake/lib/webhob/orm/models.py     | 15 +++++++++++++
 3 files changed, 60 insertions(+), 5 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index bfd7fef..d435f19 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -8,9 +8,9 @@ import re
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 
 import webhob.whbmain.settings as whb_django_settings
-from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Package, Package_Dependency
-from webhob.orm.models import Task_Dependency
-
+from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Package, LogMessage
+from webhob.orm.models import Task_Dependency, Package_Dependency
+from bb.msg import BBLogFormatter as format
 
 class ORMWrapper(object):
     """ This class creates the dictionaries needed to store information in the database
@@ -134,6 +134,18 @@ class ORMWrapper(object):
                                         depends_on = packagedict[px]['object'] );
 
 
+    def create_logmessage(self, log_information):
+        log_object = LogMessage.objects.create(
+                        build = log_information['build'],
+                        level = log_information['level'],
+                        message = log_information['message'])
+
+        for v in vars(log_object):
+            if v in log_information.keys():
+                vars(log_object)[v] = log_information[v]
+
+        return log_object.save()
+
 class BuildInfoHelper(object):
     """ This class gathers the build information from the server and sends it
         towards the ORM wrapper for storing in the database
@@ -513,3 +525,26 @@ class BuildInfoHelper(object):
                 dep = _save_a_task(taskdesc1)
                 Task_Dependency.objects.get_or_create( task = target, depends_on = dep )
 
+    def store_log_information(self, level, text):
+        log_information = {}
+        log_information['build'] = self.internal_state['build']
+        log_information['level'] = level
+        log_information['message'] = text
+        self.orm_wrapper.create_logmessage(log_information)
+
+    def store_log_event(self, event):
+        if event.levelno < format.WARNING:
+            return
+        if not 'build' in self.internal_state:
+            return
+        log_information = {}
+        log_information['build'] = self.internal_state['build']
+        if event.levelno >= format.ERROR:
+            log_information['level'] = LogMessage.ERROR
+        elif event.levelno == format.WARNING:
+            log_information['level'] = LogMessage.WARNING
+        log_information['message'] = event.msg
+        log_information['pathname'] = event.pathname
+        log_information['lineno'] = event.lineno
+        self.orm_wrapper.create_logmessage(log_information)
+
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 0305121..78982f2 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -332,6 +332,8 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 # include verbose/debug messages
                 if event.taskpid != 0 and event.levelno <= format.NOTE:
                     continue
+
+                buildinfohelper.store_log_event(event)
                 logger.handle(event)
                 continue
 
@@ -406,9 +408,12 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                     r = ""
 
                 if event._dependees:
-                    logger.error("Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)", r, event._item, ", ".join(event._dependees), r)
+                    text = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)" % (r, event._item, ", ".join(event._dependees), r)
                 else:
-                    logger.error("Nothing %sPROVIDES '%s'", r, event._item)
+                    text = "Nothing %sPROVIDES '%s'" % (r, event._item)
+
+                logger.error(text)
+                buildinfohelper.store_log_information(2, text)  # don't judge me
                 if event._reasons:
                     for reason in event._reasons:
                         logger.error("%s", reason)
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index f2cc7d9..91a51ff 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -174,3 +174,18 @@ class Machine(models.Model):
     name = models.CharField(max_length=100)
     description = models.TextField()
 
+
+class LogMessage(models.Model):
+    INFO = 0
+    WARNING = 1
+    ERROR = 2
+
+    LOG_LEVEL = ( (INFO, "info"),
+            (WARNING, "warn"),
+            (ERROR, "error") )
+
+    build = models.ForeignKey(Build)
+    level = models.IntegerField(choices=LOG_LEVEL, default=INFO)
+    message=models.CharField(max_length=240)
+    pathname = models.FilePathField(max_length=255, null=True)
+    lineno = models.IntegerField(null=True)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 56/94] bitbake: webhob: add simple visualisation for build errors
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (54 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 55/94] bitbake: dsi: store log information Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 57/94] bitbake: webhob: store logfile and message for Tasks Alex DAMIAN
                   ` (38 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding Simple web error viewing in the builds page.
Now the error/warning column will contain the actual
content of the error.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/build.html | 4 ++--
 bitbake/lib/webhob/bldviewer/views.py             | 6 ++++--
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index 15d88a0..bef84a9 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -33,8 +33,8 @@
             <td>{% if build.is_image %} <a href="/simple/build/{{build.id}}/package/">{{build.is_image}}</a>{% else %} {{build.is_image}} {% endif %}</td>
             <td>{{build.machine.name}}</td>
             <td>{% time_difference build.started_on build.completed_on %}</td>
-            <td>{{build.errors_no}}</td>
-            <td>{{build.warnings_no}}</td>
+            <td>{% if  build.errors_no %}{% for error in logs %}{% if error.build == build %}{% if error.level == 2 %}<p>{{error.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
+            <td>{% if  build.warnings_no %}{% for warning in logs %}{% if warning.build == build %}{% if warning.level == 1 %}<p>{{warning.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
             <td>{{build.image_fstypes}}</td>
             <td>{{build.cooker_log_path}}</td>
             <td>{{build.bitbake_version}}</td>
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 99e4b99..8535963 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -2,14 +2,16 @@ import operator
 
 from django.db.models import Q
 from django.shortcuts import render
-from orm.models import Build, Task, Layer, Layer_Version, Task_Dependency, Recipe, Package, Package_Dependency
+from orm.models import Build, Task, Layer, Layer_Version, Recipe, Package, LogMessage
+from orm.models import Task_Dependency, Package_Dependency
 
 
 def build(request):
     template = 'build.html'
     build_info = Build.objects.all()
+    logs = LogMessage.objects.all()
 
-    context = {'builds': build_info}
+    context = {'builds': build_info, 'logs': logs }
 
     return render(request, template, context)
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 57/94] bitbake: webhob: store logfile and message for Tasks
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (55 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 56/94] bitbake: webhob: add simple visualisation for build errors Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 58/94] bitbake: webhob: display proper information in Simple Alex DAMIAN
                   ` (37 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Since we get the reference to the log file, and a corresponding
message with Task* events, we store that in the database.

Corresponding changes to the model file.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 6 ++++++
 bitbake/lib/bb/ui/dsi.py             | 4 +++-
 bitbake/lib/webhob/orm/models.py     | 6 ++----
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index d435f19..232eea8 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -409,6 +409,12 @@ class BuildInfoHelper(object):
         except:
             pass
 
+        if 'logfile' in vars(event):
+            task_information['logfile'] = event.logfile
+
+        if '_message' in vars(event):
+            task_information['message'] = event._message
+
         if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.sceneQueueTaskCompleted)):
             task_information['outcome'] = Task.OUTCOME_SUCCESS     # TODO: needs to use constants
             task_build_stats = self._get_task_build_stats(self.orm_wrapper.get_update_task_object(task_information))
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 78982f2..f72b1be 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -303,7 +303,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
             if isinstance(event, bb.event.BuildStarted):
                 buildinfohelper.store_started_build(event)
 
-            if isinstance(event, (bb.build.TaskSucceeded, bb.build.TaskFailedSilent, bb.build.TaskFailed)):
+            if isinstance(event, (bb.build.TaskSucceeded, bb.build.TaskFailedSilent)):
                 buildinfohelper.update_and_store_task(event)
                 continue
 
@@ -338,6 +338,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 continue
 
             if isinstance(event, bb.build.TaskFailed):
+                buildinfohelper.update_and_store_task(event)
                 return_value = 1
                 logfile = event.logfile
                 if logfile and os.path.exists(logfile):
@@ -362,6 +363,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                         if lines:
                             for line in lines:
                                 print(line)
+
             if isinstance(event, bb.build.TaskBase):
                 logger.info(event._message)
                 continue
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 91a51ff..f51f53d 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -87,11 +87,9 @@ class Task(models.Model):
     disk_io = models.IntegerField(null=True)
     cpu_usage = models.DecimalField(max_digits=6, decimal_places=2, null=True)
     elapsed_time = models.CharField(max_length=50, default=0)
-    errors_no = models.IntegerField(default=0)
-    warnings_no = models.IntegerField(default=0)
-    error = models.TextField(null=True)
-    warning = models.TextField(null=True)
     sstate_result = models.IntegerField(choices=SSTATE_RESULT, default=SSTATE_NA)
+    message = models.CharField(max_length=240)
+    logfile = models.FilePathField(max_length=255, null=True)
 
     class Meta:
         ordering = ('order', 'recipe' ,)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 58/94] bitbake: webhob: display proper information in Simple
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (56 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 57/94] bitbake: webhob: store logfile and message for Tasks Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 59/94] bitbake: dsi: clear up global variables between builds Alex DAMIAN
                   ` (36 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We modify the Simple interface to match the changes in the
Task file.

Also a couple of display-related changes in the Simple interface.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/build.html |  4 ++--
 bitbake/lib/webhob/bldviewer/templates/task.html  | 10 +++++-----
 bitbake/lib/webhob/bldviewer/views.py             |  2 +-
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index bef84a9..20aa310 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -33,8 +33,8 @@
             <td>{% if build.is_image %} <a href="/simple/build/{{build.id}}/package/">{{build.is_image}}</a>{% else %} {{build.is_image}} {% endif %}</td>
             <td>{{build.machine.name}}</td>
             <td>{% time_difference build.started_on build.completed_on %}</td>
-            <td>{% if  build.errors_no %}{% for error in logs %}{% if error.build == build %}{% if error.level == 2 %}<p>{{error.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
-            <td>{% if  build.warnings_no %}{% for warning in logs %}{% if warning.build == build %}{% if warning.level == 1 %}<p>{{warning.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
+            <td>{{build.errors_no}}:{% if  build.errors_no %}{% for error in logs %}{% if error.build == build %}{% if error.level == 2 %}<p>{{error.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
+            <td>{{build.warnings_no}}:{% if  build.warnings_no %}{% for warning in logs %}{% if warning.build == build %}{% if warning.level == 1 %}<p>{{warning.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
             <td>{{build.image_fstypes}}</td>
             <td>{{build.cooker_log_path}}</td>
             <td>{{build.bitbake_version}}</td>
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 2d78668..d87d261 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -15,8 +15,8 @@
             <th>Recipe Version</th>
             <th>Task Type</th>
             <th>Outcome</th>
-            <th>Errors</th>
-            <th>Warnings</th>
+            <th>Message</th>
+            <th>Logfile</th>
             <th>Time</th>
             <th>CPU usage</th>
             <th>Disk I/O</th>
@@ -40,13 +40,13 @@
                 {% endif %}
 
                 <td>{{task.get_outcome_display}}</td>
-                <td>{{task.errors_no}}</td>
-                <td>{{task.warnings_no}}</td>
+                <td><p>{{task.message}}</td>
+                <td><a target="_fileview" href="file:///{{task.logfile}}">{{task.logfile}}</a></td>
                 <td>{{task.elapsed_time}}</td>
                 <td>{{task.cpu_usage}}</td>
                 <td>{{task.disk_io}}</td>
                 <td>{{task.get_script_type_display}}</td>
-                <td>{{task.recipe.file_path}}</td>
+                <td><a target="_fileview" href="file:///{{task.recipe.file_path}}">{{task.recipe.file_path}}</a></td>
                 <td>
             <div style="height: 3em; overflow:auto">
                 {% for tt in task.depends_on %}
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 8535963..7f97312 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -19,7 +19,7 @@ def task(request, build_id):
     template = 'task.html'
 
     tasks = Task.objects.filter(build=build_id)
-    task_depends = Task_Dependency.objects.filter(task__in=tasks)
+    task_depends = Task_Dependency.objects.filter(task__in=tasks).select_related()
 
     for t in tasks:
         t.depends_on = []
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 59/94] bitbake: dsi: clear up global variables between builds
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (57 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 58/94] bitbake: webhob: display proper information in Simple Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 60/94] bitbake: dsi: small bugfixes in data collection Alex DAMIAN
                   ` (35 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We're making sure that global variables that track
a build data get reset when the build ends.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/dsi.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index f72b1be..c139cd2 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -479,8 +479,12 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                                   bb.command.CommandFailed,
                                   bb.command.CommandExit)):
 
-                # we start a new build info
                 buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
+
+                # we start a new build info
+                errors = 0
+                warnings = 0
+                taskfailures = []
                 buildinfohelper = BuildInfoHelper(server, build_history_enabled)
                 buildinfohelper.store_layer_info()
                 continue
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 60/94] bitbake: dsi: small bugfixes in data collection
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (58 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 59/94] bitbake: dsi: clear up global variables between builds Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 62/94] bitbake: webhob: add toggle column functionality to build page Alex DAMIAN
                   ` (34 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

A couple of bugfixes in the data collection:
- cache the TMPDIR since it's not gonna change during the build
- CommandCompleted can be issues outside of a build,  so
update the build only if we're running one

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 232eea8..6f4789c 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -161,6 +161,7 @@ class BuildInfoHelper(object):
         self.server = server
         self.orm_wrapper = ORMWrapper()
         self.has_build_history = has_build_history
+        self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
 
     def _configure_django(self):
         # Add webhob to sys path for importing modules
@@ -298,13 +299,12 @@ class BuildInfoHelper(object):
     def _get_path_information(self, task_object):
         build_stats_format = "{tmpdir}/buildstats/{target}-{machine}/{buildname}/{package}/"
 
-        tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
         target = self.internal_state['target']
         machine = self.internal_state['build'].machine.name
         buildname = self.internal_state['build'].build_name
         package = task_object.recipe.name + "-" + task_object.recipe.version.strip(":")
 
-        build_stats_path = build_stats_format.format(tmpdir=tmp_dir, target=target,
+        build_stats_path = build_stats_format.format(tmpdir=self.tmp_dir, target=target,
                                                      machine=machine, buildname=buildname,
                                                      package=package)
 
@@ -373,7 +373,8 @@ class BuildInfoHelper(object):
 
 
     def update_build_information(self, event, errors, warnings, taskfailures):
-        self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
+        if 'build' in self.internal_state:
+            self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
 
     def store_started_task(self, event):
         identifier = re.split(':', event.taskfile)[-1] + event.taskname
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 62/94] bitbake: webhob: add toggle column functionality to build page
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (59 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 60/94] bitbake: dsi: small bugfixes in data collection Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 63/94] bitbake: webhob: add search functionality to the Simple interface Alex DAMIAN
                   ` (33 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We add demo toggle column functionality for the Simple interface,
webhob builds page.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/base.html  | 15 +++++++++++++++
 bitbake/lib/webhob/bldviewer/templates/build.html |  6 ++++++
 bitbake/lib/webhob/bldviewer/views.py             |  3 ++-
 3 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/base.html b/bitbake/lib/webhob/bldviewer/templates/base.html
index ab3c3b7..3f056a0 100644
--- a/bitbake/lib/webhob/bldviewer/templates/base.html
+++ b/bitbake/lib/webhob/bldviewer/templates/base.html
@@ -3,9 +3,24 @@
 <html>
 	<head>
 		<title>WebHob Simple Explorer</title>
+<script src="/static/js/jquery-2.0.3.js">
+</script>
+<script src="/static/js/bootstrap.js">
+</script>
+<link href="/static/css/bootstrap.css" rel="stylesheet" type="text/css">
 	</head>
 
 <body>
+<script>
+function showhideTableColumn(i, sh) {
+    if (sh)
+        $('td:nth-child('+i+'),th:nth-child('+i+')').show();
+    else
+        $('td:nth-child('+i+'),th:nth-child('+i+')').hide();
+g
+}
+</script>
+
 <div>Menu: <a href="/simple/build/">All Builds</a>&nbsp;|&nbsp;<a href="/simple/layer/">All Layers</a>
 </div>
 {% block pagecontent %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index 20aa310..e30801b 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -5,6 +5,12 @@
 
     <table border="1">
 
+<div align="right" style="display: float">Show/Hide columns:
+<span>Output: <input type="checkbox" id="ctoutput" onchange="showhideTableColumn(10, $('#ctoutput').is(':checked'))" checked autocomplete="off"></span> |
+<span>Log: <input type="checkbox" id="ctlog" onchange="showhideTableColumn(11, $('#ctlog').is(':checked'))"checked autocomplete="off"></span> |
+<span>Build Name: <input type="checkbox" id="ctbuildname" onchange="showhideTableColumn(13, $('#ctbuildname').is(':checked'))" checked autocomplete="off"></span> |
+</div>
+
     {% load projecttags %}
 
         <tr>
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 7f97312..ab69c91 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -4,8 +4,9 @@ from django.db.models import Q
 from django.shortcuts import render
 from orm.models import Build, Task, Layer, Layer_Version, Recipe, Package, LogMessage
 from orm.models import Task_Dependency, Package_Dependency
+from django.views.decorators.cache import cache_control
 
-
+@cache_control(no_store=True)
 def build(request):
     template = 'build.html'
     build_info = Build.objects.all()
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 63/94] bitbake: webhob: add search functionality to the Simple interface
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (60 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 62/94] bitbake: webhob: add toggle column functionality to build page Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 64/94] bitbake: webhob: refactor column hiding code Alex DAMIAN
                   ` (32 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We add generic table search function for the builds table,
and make it generic to be able to expand to all other table views.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/base.html    | 12 +++++++++++-
 bitbake/lib/webhob/bldviewer/templates/build.html   | 12 +++++++-----
 bitbake/lib/webhob/bldviewer/templates/package.html |  2 +-
 3 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/base.html b/bitbake/lib/webhob/bldviewer/templates/base.html
index 3f056a0..45f8652 100644
--- a/bitbake/lib/webhob/bldviewer/templates/base.html
+++ b/bitbake/lib/webhob/bldviewer/templates/base.html
@@ -17,7 +17,17 @@ function showhideTableColumn(i, sh) {
         $('td:nth-child('+i+'),th:nth-child('+i+')').show();
     else
         $('td:nth-child('+i+'),th:nth-child('+i+')').hide();
-g
+}
+
+
+function filterTableRows(test) {
+    if (test.length > 0) {
+        var r = new RegExp(test);
+        $('tr.data').map( function (i, el) { if (! r.test($(el).html())) {$(el).hide()} else $(el).show()});
+    } else
+    {
+        $('tr.data').show();
+    }
 }
 </script>
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index e30801b..aa92929 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -3,14 +3,16 @@
 {% block pagecontent %}
     <h1>WebHob Builds</h1>
 
-    <table border="1">
-
-<div align="right" style="display: float">Show/Hide columns:
+<div style="padding-top:1em ;padding-bottom: 1em">
+<div align="left" style="display:inline-block; width: 49%"> Search: <input type="search" id="filterstring" style="width: 30em" onkeyup="filterTableRows($('#filterstring').val())" autocomplete="off">
+</div>
+<div align="right" style="display: inline-block; width: 49%">Show/Hide columns:
 <span>Output: <input type="checkbox" id="ctoutput" onchange="showhideTableColumn(10, $('#ctoutput').is(':checked'))" checked autocomplete="off"></span> |
 <span>Log: <input type="checkbox" id="ctlog" onchange="showhideTableColumn(11, $('#ctlog').is(':checked'))"checked autocomplete="off"></span> |
 <span>Build Name: <input type="checkbox" id="ctbuildname" onchange="showhideTableColumn(13, $('#ctbuildname').is(':checked'))" checked autocomplete="off"></span> |
 </div>
-
+</div>
+    <table border="1">
     {% load projecttags %}
 
         <tr>
@@ -31,7 +33,7 @@
 
         {% for build in builds %}
 
-        <tr>
+        <tr class="data">
             <td><a href="/simple/build/{{build.id}}/task/">{{build.get_outcome_display}}</a></td>
             <td>{{build.started_on}}</td>
             <td>{{build.completed_on}}</td>
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
index d31ecbb..d1259e9 100644
--- a/bitbake/lib/webhob/bldviewer/templates/package.html
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -19,7 +19,7 @@
 
             {% for package in packages %}
 
-                <tr>
+                <tr class="data">
                 <td><a name="#{{package.name}}">{{package.name}}</a></td>
                 <td>{{package.version}}</td>
                 <td>{{package.size}}</td>
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 64/94] bitbake: webhob: refactor column hiding code
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (61 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 63/94] bitbake: webhob: add search functionality to the Simple interface Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 65/94] bitbake: webhob: improve search functionality Alex DAMIAN
                   ` (31 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We're refactoring the column show/hide code to get it
more generic, and easier to apply it to all table pages.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/build.html | 6 +++---
 bitbake/lib/webhob/bldviewer/views.py             | 6 +++++-
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index aa92929..5db72db 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -7,9 +7,9 @@
 <div align="left" style="display:inline-block; width: 49%"> Search: <input type="search" id="filterstring" style="width: 30em" onkeyup="filterTableRows($('#filterstring').val())" autocomplete="off">
 </div>
 <div align="right" style="display: inline-block; width: 49%">Show/Hide columns:
-<span>Output: <input type="checkbox" id="ctoutput" onchange="showhideTableColumn(10, $('#ctoutput').is(':checked'))" checked autocomplete="off"></span> |
-<span>Log: <input type="checkbox" id="ctlog" onchange="showhideTableColumn(11, $('#ctlog').is(':checked'))"checked autocomplete="off"></span> |
-<span>Build Name: <input type="checkbox" id="ctbuildname" onchange="showhideTableColumn(13, $('#ctbuildname').is(':checked'))" checked autocomplete="off"></span> |
+{% for i in hideshowcols %}
+    <span>{{i.name}} <input type="checkbox" id="ct{{i.name}}" onchange="showhideTableColumn({{i.order}}, $('#ct{{i.name}}').is(':checked'))" checked autocomplete="off"></span> |
+{% endfor %}
 </div>
 </div>
     <table border="1">
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index ab69c91..2ef905a 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -12,7 +12,11 @@ def build(request):
     build_info = Build.objects.all()
     logs = LogMessage.objects.all()
 
-    context = {'builds': build_info, 'logs': logs }
+    context = {'builds': build_info, 'logs': logs , 
+        'hideshowcols' : [
+                {'name': 'Output', 'order':10},
+                {'name': 'Log', 'order':11},
+            ]}
 
     return render(request, template, context)
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 65/94] bitbake: webhob: improve search functionality
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (62 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 64/94] bitbake: webhob: refactor column hiding code Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 66/94] bitbake: webhob: refactor Simple web interface Alex DAMIAN
                   ` (30 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

we improve the search functionality to allow for
multiple search terms separated by space.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/base.html | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/base.html b/bitbake/lib/webhob/bldviewer/templates/base.html
index 45f8652..12b2f01 100644
--- a/bitbake/lib/webhob/bldviewer/templates/base.html
+++ b/bitbake/lib/webhob/bldviewer/templates/base.html
@@ -22,8 +22,11 @@ function showhideTableColumn(i, sh) {
 
 function filterTableRows(test) {
     if (test.length > 0) {
-        var r = new RegExp(test);
-        $('tr.data').map( function (i, el) { if (! r.test($(el).html())) {$(el).hide()} else $(el).show()});
+
+        var r = test.split(/[ ,]+/).map(function (e) { return new RegExp(e, 'i') });
+        $('tr.data').map( function (i, el) {
+            (! r.map(function (j) { return j.test($(el).html())}).reduce(function (c, p) { return c && p;} )) ? $(el).hide() : $(el).show();
+        });
     } else
     {
         $('tr.data').show();
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 66/94] bitbake: webhob: refactor Simple web interface
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (63 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 65/94] bitbake: webhob: improve search functionality Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 67/94] bitbake: webhob: simple interface CSS Alex DAMIAN
                   ` (29 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We're refactoring the inheritance model and code
placement in the Simple interface as to bring the
search functionality and column selection to all
table-based pages.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/base.html    | 15 ++++++++++++++-
 bitbake/lib/webhob/bldviewer/templates/build.html   | 17 +++++------------
 bitbake/lib/webhob/bldviewer/templates/layer.html   |  6 ++++--
 bitbake/lib/webhob/bldviewer/templates/package.html | 12 +++++++-----
 bitbake/lib/webhob/bldviewer/templates/recipe.html  |  6 ++++--
 bitbake/lib/webhob/bldviewer/templates/task.html    |  6 ++++--
 6 files changed, 38 insertions(+), 24 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/base.html b/bitbake/lib/webhob/bldviewer/templates/base.html
index 12b2f01..0d24d36 100644
--- a/bitbake/lib/webhob/bldviewer/templates/base.html
+++ b/bitbake/lib/webhob/bldviewer/templates/base.html
@@ -36,8 +36,21 @@ function filterTableRows(test) {
 
 <div>Menu: <a href="/simple/build/">All Builds</a>&nbsp;|&nbsp;<a href="/simple/layer/">All Layers</a>
 </div>
-{% block pagecontent %}
 
+{% block pagename %}
+{% endblock %}
+
+<div style="padding-top:1em ;padding-bottom: 1em">
+<div align="left" style="display:inline-block; width: 49%"> Search: <input type="search" id="filterstring" style="width: 30em" onkeyup="filterTableRows($('#filterstring').val())" autocomplete="off">
+</div>
+<div align="right" style="display: inline-block; width: 49%">Show/Hide columns:
+{% for i in hideshowcols %}
+    <span>{{i.name}} <input type="checkbox" id="ct{{i.name}}" onchange="showhideTableColumn({{i.order}}, $('#ct{{i.name}}').is(':checked'))" checked autocomplete="off"></span> |
+{% endfor %}
+</div>
+</div>
+
+{% block pagetable %}
 {% endblock %}
 </body>
 </html>
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index 5db72db..2828404 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -1,17 +1,10 @@
 {% extends "base.html" %}
 
-{% block pagecontent %}
-    <h1>WebHob Builds</h1>
-
-<div style="padding-top:1em ;padding-bottom: 1em">
-<div align="left" style="display:inline-block; width: 49%"> Search: <input type="search" id="filterstring" style="width: 30em" onkeyup="filterTableRows($('#filterstring').val())" autocomplete="off">
-</div>
-<div align="right" style="display: inline-block; width: 49%">Show/Hide columns:
-{% for i in hideshowcols %}
-    <span>{{i.name}} <input type="checkbox" id="ct{{i.name}}" onchange="showhideTableColumn({{i.order}}, $('#ct{{i.name}}').is(':checked'))" checked autocomplete="off"></span> |
-{% endfor %}
-</div>
-</div>
+{% block pagename %}
+    <h1>Toaster - Builds</h1>
+{% endblock %}
+
+{% block pagetable %}
     <table border="1">
     {% load projecttags %}
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/layer.html b/bitbake/lib/webhob/bldviewer/templates/layer.html
index 9d44f17..0ffdaba 100644
--- a/bitbake/lib/webhob/bldviewer/templates/layer.html
+++ b/bitbake/lib/webhob/bldviewer/templates/layer.html
@@ -1,8 +1,10 @@
 {% extends "base.html" %}
 
-{% block pagecontent %}
-    <h1>WebHob Layers</h1>
+{% block pagename %}
+    <h1>Toaster - Layers</h1>
+{% endblock %}
 
+{% block pagetable %}
     <table border="1">
 
     {% load projecttags %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
index d1259e9..e7856ba 100644
--- a/bitbake/lib/webhob/bldviewer/templates/package.html
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -1,8 +1,10 @@
 {% extends "base.html" %}
 
-{% block pagecontent %}
-    <h1>WebHob Packages</h1>
+{% block pagename %}
+    <h1>Toaster - Packages</h1>
+{% endblock %}
 
+{% block pagetable %}
     {% if not packages %}
         <p>No packages were build in this build!</p>
     {% else %}
@@ -29,11 +31,11 @@
             <div style="height: 3em; overflow:auto">
                     {% for d in package.depends_on %}
                     <a href="#{{d.name}}">{{d.name}}</a><br/>
-                    {% endfor %} 
+                    {% endfor %}
             </div>
                 </td>
-            
-            {% endfor %}        
+
+            {% endfor %}
 
         </table>
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/recipe.html b/bitbake/lib/webhob/bldviewer/templates/recipe.html
index 5ebd880..0c9c077 100644
--- a/bitbake/lib/webhob/bldviewer/templates/recipe.html
+++ b/bitbake/lib/webhob/bldviewer/templates/recipe.html
@@ -1,8 +1,10 @@
 {% extends "base.html" %}
 
-{% block pagecontent %}
-    <h1>WebHob Recipes for a Layer</h1>
+{% block pagename %}
+    <h1>Toaster - Recipes for a Layer</h1>
+{% endblock %}
 
+{% block pagetable %}
     <table border="1">
 
     {% load projecttags %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index d87d261..0ed5f9d 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -1,8 +1,10 @@
 {% extends "base.html" %}
 
-{% block pagecontent %}
-    <h1>WebHob Tasks</h1>
+{% block pagename %}
+    <h1>Toaster - Tasks</h1>
+{% endblock %}
 
+{% block pagetable %}
     {% if not tasks %}
         <p>No tasks were executed in this build!</p>
     {% else %}
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 67/94] bitbake: webhob: simple interface CSS
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (64 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 66/94] bitbake: webhob: refactor Simple web interface Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 68/94] bitbake: dsi: use get vars command to store configuration Alex DAMIAN
                   ` (28 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adds Bootstrap CSS features to the Simple interface,
as to make it more visually appealing.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/base.html   | 44 +++++++++++++---------
 bitbake/lib/webhob/bldviewer/templates/build.html  |  7 +---
 bitbake/lib/webhob/bldviewer/templates/layer.html  |  6 +--
 .../lib/webhob/bldviewer/templates/package.html    |  6 +--
 bitbake/lib/webhob/bldviewer/templates/recipe.html |  6 +--
 bitbake/lib/webhob/bldviewer/templates/task.html   |  6 +--
 6 files changed, 31 insertions(+), 44 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/base.html b/bitbake/lib/webhob/bldviewer/templates/base.html
index 0d24d36..e97c8f6 100644
--- a/bitbake/lib/webhob/bldviewer/templates/base.html
+++ b/bitbake/lib/webhob/bldviewer/templates/base.html
@@ -10,7 +10,7 @@
 <link href="/static/css/bootstrap.css" rel="stylesheet" type="text/css">
 	</head>
 
-<body>
+<body style="height: 100%">
 <script>
 function showhideTableColumn(i, sh) {
     if (sh)
@@ -22,7 +22,6 @@ function showhideTableColumn(i, sh) {
 
 function filterTableRows(test) {
     if (test.length > 0) {
-
         var r = test.split(/[ ,]+/).map(function (e) { return new RegExp(e, 'i') });
         $('tr.data').map( function (i, el) {
             (! r.map(function (j) { return j.test($(el).html())}).reduce(function (c, p) { return c && p;} )) ? $(el).hide() : $(el).show();
@@ -33,25 +32,34 @@ function filterTableRows(test) {
     }
 }
 </script>
-
-<div>Menu: <a href="/simple/build/">All Builds</a>&nbsp;|&nbsp;<a href="/simple/layer/">All Layers</a>
-</div>
-
-{% block pagename %}
-{% endblock %}
-
-<div style="padding-top:1em ;padding-bottom: 1em">
-<div align="left" style="display:inline-block; width: 49%"> Search: <input type="search" id="filterstring" style="width: 30em" onkeyup="filterTableRows($('#filterstring').val())" autocomplete="off">
-</div>
-<div align="right" style="display: inline-block; width: 49%">Show/Hide columns:
-{% for i in hideshowcols %}
-    <span>{{i.name}} <input type="checkbox" id="ct{{i.name}}" onchange="showhideTableColumn({{i.order}}, $('#ct{{i.name}}').is(':checked'))" checked autocomplete="off"></span> |
-{% endfor %}
-</div>
+<div style="position:fixed; top:0; z-index: 999; width: 100%; background:white"> 
+    <ul class="nav nav-tabs">
+        <li><a href="/simple/build/">All Builds</a></li>
+        <li><a href="/simple/layer/">All Layers</a></li>
+    </ul>
+    
+    {% block pagename %}
+    {% endblock %}
+    
+    <div style="padding-top:1em ;padding-bottom: 1em">
+    <div align="left" style="display:inline-block; width: 49%; margin-left: 2em"> Search: <input type="search" id="filterstring" style="width: 30em" onkeyup="filterTableRows($('#filterstring').val())" autocomplete="off">
+    </div>
+    {% if hideshowcols %}
+    <div align="right" style="display: inline-block; width: 49%">Show/Hide columns:
+    {% for i in hideshowcols %}
+        <span>{{i.name}} <input type="checkbox" id="ct{{i.name}}" onchange="showhideTableColumn({{i.order}}, $('#ct{{i.name}}').is(':checked'))" checked autocomplete="off"></span> |
+    {% endfor %}
+    </div>
+    {% endif %}
+    </div>
 </div>
-
+<div style="padding-top: 13em; width:98%; align:center; margin:auto;">
+    <table class="table table-striped table-condensed">
 {% block pagetable %}
 {% endblock %}
+    </table>
+</div>
+<div class="navbar"><br/>About Toaster | Yocto Project </div>
 </body>
 </html>
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index 2828404..20c415b 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -5,9 +5,8 @@
 {% endblock %}
 
 {% block pagetable %}
-    <table border="1">
-    {% load projecttags %}
 
+    {% load projecttags %}
         <tr>
             <th>Outcome</th>
             <th>Started On</th>
@@ -23,9 +22,7 @@
             <th>Bitbake Version</th>
             <th>Build Name</th>
         </tr>
-
         {% for build in builds %}
-
         <tr class="data">
             <td><a href="/simple/build/{{build.id}}/task/">{{build.get_outcome_display}}</a></td>
             <td>{{build.started_on}}</td>
@@ -43,8 +40,6 @@
         </tr>
 
         {% endfor %}
-
-    </table>
 {% endblock %}
 
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/layer.html b/bitbake/lib/webhob/bldviewer/templates/layer.html
index 0ffdaba..5b317ba 100644
--- a/bitbake/lib/webhob/bldviewer/templates/layer.html
+++ b/bitbake/lib/webhob/bldviewer/templates/layer.html
@@ -5,8 +5,6 @@
 {% endblock %}
 
 {% block pagetable %}
-    <table border="1">
-
     {% load projecttags %}
 
         <tr>
@@ -18,7 +16,7 @@
 
         {% for layer in layers %}
 
-        <tr>
+        <tr class="data">
             <td>{{layer.name}}</td>
             <td>{{layer.local_path}}</td>
             <td><a href='{{layer.layer_index_url}}'>{{layer.layer_index_url}}</a></td>
@@ -33,6 +31,4 @@
 
         {% endfor %}
 
-    </table>
-
 {% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
index e7856ba..f70b022 100644
--- a/bitbake/lib/webhob/bldviewer/templates/package.html
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -9,8 +9,6 @@
         <p>No packages were build in this build!</p>
     {% else %}
 
-        <table border="1">
-
             <tr>
             <th>Name</th>
             <th>Version</th>
@@ -21,7 +19,7 @@
 
             {% for package in packages %}
 
-                <tr class="data">
+            <tr class="data">
                 <td><a name="#{{package.name}}">{{package.name}}</a></td>
                 <td>{{package.version}}</td>
                 <td>{{package.size}}</td>
@@ -37,8 +35,6 @@
 
             {% endfor %}
 
-        </table>
-
     {% endif %}
 
 {% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/recipe.html b/bitbake/lib/webhob/bldviewer/templates/recipe.html
index 0c9c077..515053a 100644
--- a/bitbake/lib/webhob/bldviewer/templates/recipe.html
+++ b/bitbake/lib/webhob/bldviewer/templates/recipe.html
@@ -5,8 +5,6 @@
 {% endblock %}
 
 {% block pagetable %}
-    <table border="1">
-
     {% load projecttags %}
 
         <tr>
@@ -24,7 +22,7 @@
 
         {% for recipe in recipes %}
 
-        <tr>
+        <tr class="data">
             <td><a name="{{recipe.name}}">{{recipe.name}}</a></td>
             <td>{{recipe.version}}</td>
             <td>{{recipe.summary}}</td>
@@ -39,6 +37,4 @@
 
         {% endfor %}
 
-    </table>
-
 {% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 0ed5f9d..6d06d0f 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -9,8 +9,6 @@
         <p>No tasks were executed in this build!</p>
     {% else %}
 
-        <table border="1">
-
             <tr>
             <th>Order</th>
             <th>Task</th>
@@ -29,7 +27,7 @@
 
             {% for task in tasks %}
 
-                <tr>
+            <tr class="data">
                 <td>{{task.order}}</td>
                 <td><a name="{{task.recipe.name}}.{{task.task_name}}">
                 <a href="/simple/layerversions/{{task.recipe.layer_version_id}}/recipes/#{{task.recipe.name}}">{{task.recipe.name}}</a>.{{task.task_name}}</a></td>
@@ -61,8 +59,6 @@
 
             {% endfor %}
 
-        </table>
-
     {% endif %}
 
 {% endblock %}
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 68/94] bitbake: dsi: use get vars command to store configuration
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (65 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 67/94] bitbake: webhob: simple interface CSS Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 69/94] bitbake: webhob: refactor CSS display in Simple Alex DAMIAN
                   ` (27 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Patch to store variable configuration for a build using
the getAllKeysWithFlags command. Only variables that are
not functions are saved.

Minor fixes to the Variable model as to match data we receive.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 15 +++++++++++++--
 bitbake/lib/webhob/orm/models.py     |  6 +++---
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 6f4789c..89d5f15 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -9,7 +9,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 
 import webhob.whbmain.settings as whb_django_settings
 from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Package, LogMessage
-from webhob.orm.models import Task_Dependency, Package_Dependency
+from webhob.orm.models import Task_Dependency, Package_Dependency, Variable
 from bb.msg import BBLogFormatter as format
 
 class ORMWrapper(object):
@@ -146,6 +146,15 @@ class ORMWrapper(object):
 
         return log_object.save()
 
+    def save_build_variables(self, build_obj, vardump):
+        for k in vardump:
+            if not bool(vardump[k]['func']):
+                Variable.objects.create( build = build_obj,
+                    variable_name = k,
+                    variable_value = vardump[k]['v'],
+                    description = vardump[k]['doc'])
+
+
 class BuildInfoHelper(object):
     """ This class gathers the build information from the server and sends it
         towards the ORM wrapper for storing in the database
@@ -363,13 +372,15 @@ class BuildInfoHelper(object):
         build_obj = self.orm_wrapper.create_build_object(build_information)
         self.internal_state['build'] = build_obj
         self.internal_state['target'] = build_information['target']
+        # Load layer information for the build
         self.internal_state['layer_versions'] = []
         for layer_object in self.internal_state['layers']:
             layer_version_information = self._get_layer_version_information(layer_object)
             self.internal_state['layer_versions'].append(self.orm_wrapper.get_layer_version_object(layer_version_information))
 
         del self.internal_state['layers']
-
+        # Save build configuration
+        self.orm_wrapper.save_build_variables(build_obj, self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0])
 
 
     def update_build_information(self, event, errors, warnings, taskfailures):
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index f51f53d..1d67e40 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -161,11 +161,11 @@ class Layer_Version(models.Model):
 class Variable(models.Model):
     build = models.ForeignKey(Build, related_name='variable_build')
     variable_name = models.CharField(max_length=100)
-    variable_value = models.TextField()
+    variable_value = models.TextField(null=True)
     file = models.FilePathField(max_length=255)
-    changed = models.BooleanField()
+    changed = models.BooleanField(default=False)
     human_readable_name = models.CharField(max_length=200)
-    description = models.TextField()
+    description = models.TextField(null=True)
 
 
 class Machine(models.Model):
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 69/94] bitbake: webhob: refactor CSS display in Simple
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (66 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 68/94] bitbake: dsi: use get vars command to store configuration Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 70/94] bitbake: webhob: add Configuration visualisation " Alex DAMIAN
                   ` (26 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Refactoring CSS display in Simple to separate
the generic layout to the table display code.

With the new layout we can add the generic display
to more than just table pages.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/base.html   | 51 ++++------------------
 .../lib/webhob/bldviewer/templates/basetable.html  | 46 +++++++++++++++++++
 bitbake/lib/webhob/bldviewer/templates/build.html  |  2 +-
 bitbake/lib/webhob/bldviewer/templates/layer.html  |  2 +-
 .../lib/webhob/bldviewer/templates/package.html    |  2 +-
 bitbake/lib/webhob/bldviewer/templates/recipe.html |  2 +-
 bitbake/lib/webhob/bldviewer/templates/task.html   |  2 +-
 7 files changed, 59 insertions(+), 48 deletions(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/basetable.html

diff --git a/bitbake/lib/webhob/bldviewer/templates/base.html b/bitbake/lib/webhob/bldviewer/templates/base.html
index e97c8f6..31abeb1 100644
--- a/bitbake/lib/webhob/bldviewer/templates/base.html
+++ b/bitbake/lib/webhob/bldviewer/templates/base.html
@@ -2,7 +2,7 @@
 
 <html>
 	<head>
-		<title>WebHob Simple Explorer</title>
+		<title>Toaster Simple Explorer</title>
 <script src="/static/js/jquery-2.0.3.js">
 </script>
 <script src="/static/js/bootstrap.js">
@@ -11,55 +11,20 @@
 	</head>
 
 <body style="height: 100%">
-<script>
-function showhideTableColumn(i, sh) {
-    if (sh)
-        $('td:nth-child('+i+'),th:nth-child('+i+')').show();
-    else
-        $('td:nth-child('+i+'),th:nth-child('+i+')').hide();
-}
-
-
-function filterTableRows(test) {
-    if (test.length > 0) {
-        var r = test.split(/[ ,]+/).map(function (e) { return new RegExp(e, 'i') });
-        $('tr.data').map( function (i, el) {
-            (! r.map(function (j) { return j.test($(el).html())}).reduce(function (c, p) { return c && p;} )) ? $(el).hide() : $(el).show();
-        });
-    } else
-    {
-        $('tr.data').show();
-    }
-}
-</script>
-<div style="position:fixed; top:0; z-index: 999; width: 100%; background:white"> 
+<div style="width:100%; height: 100%; position:absolute">
+<div style="width: 100%; height: 3em" class="nav">
     <ul class="nav nav-tabs">
         <li><a href="/simple/build/">All Builds</a></li>
         <li><a href="/simple/layer/">All Layers</a></li>
     </ul>
-    
-    {% block pagename %}
-    {% endblock %}
-    
-    <div style="padding-top:1em ;padding-bottom: 1em">
-    <div align="left" style="display:inline-block; width: 49%; margin-left: 2em"> Search: <input type="search" id="filterstring" style="width: 30em" onkeyup="filterTableRows($('#filterstring').val())" autocomplete="off">
-    </div>
-    {% if hideshowcols %}
-    <div align="right" style="display: inline-block; width: 49%">Show/Hide columns:
-    {% for i in hideshowcols %}
-        <span>{{i.name}} <input type="checkbox" id="ct{{i.name}}" onchange="showhideTableColumn({{i.order}}, $('#ct{{i.name}}').is(':checked'))" checked autocomplete="off"></span> |
-    {% endfor %}
-    </div>
-    {% endif %}
-    </div>
 </div>
-<div style="padding-top: 13em; width:98%; align:center; margin:auto;">
-    <table class="table table-striped table-condensed">
-{% block pagetable %}
+
+<div style="overflow-y:scroll; width: 100%; position: absolute; top: 3em; bottom:70px ">
+{% block pagecontent %}
 {% endblock %}
-    </table>
 </div>
-<div class="navbar"><br/>About Toaster | Yocto Project </div>
+<div class="navbar" style="position: absolute; bottom: 0; width:100%"><br/>About Toaster | Yocto Project </div>
+</div>
 </body>
 </html>
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/basetable.html b/bitbake/lib/webhob/bldviewer/templates/basetable.html
new file mode 100644
index 0000000..083bcb8
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/basetable.html
@@ -0,0 +1,46 @@
+{% extends "base.html" %}
+
+{% block pagecontent %}
+<script>
+function showhideTableColumn(i, sh) {
+    if (sh)
+        $('td:nth-child('+i+'),th:nth-child('+i+')').show();
+    else
+        $('td:nth-child('+i+'),th:nth-child('+i+')').hide();
+}
+
+
+function filterTableRows(test) {
+    if (test.length > 0) {
+        var r = test.split(/[ ,]+/).map(function (e) { return new RegExp(e, 'i') });
+        $('tr.data').map( function (i, el) {
+            (! r.map(function (j) { return j.test($(el).html())}).reduce(function (c, p) { return c && p;} )) ? $(el).hide() : $(el).show();
+        });
+    } else
+    {
+        $('tr.data').show();
+    }
+}
+</script>
+<div style="margin-bottom: 0.5em">
+
+    {% block pagename %}
+    {% endblock %}
+    <div align="left" style="display:inline-block; width: 40%; margin-left: 2em"> Search: <input type="search" id="filterstring" style="width: 80%" onkeyup="filterTableRows($('#filterstring').val())" autocomplete="off">
+    </div>
+    {% if hideshowcols %}
+    <div align="right" style="display: inline-block; width: 40%">Show/Hide columns:
+    {% for i in hideshowcols %}
+        <span>{{i.name}} <input type="checkbox" id="ct{{i.name}}" onchange="showhideTableColumn({{i.order}}, $('#ct{{i.name}}').is(':checked'))" checked autocomplete="off"></span> |
+    {% endfor %}
+    </div>
+    {% endif %}
+</div>
+<div>
+    <table class="table table-striped table-condensed" style="width:95%">
+{% block pagetable %}
+{% endblock %}
+    </table>
+</div>
+
+{% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index 20c415b..53271a7 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "basetable.html" %}
 
 {% block pagename %}
     <h1>Toaster - Builds</h1>
diff --git a/bitbake/lib/webhob/bldviewer/templates/layer.html b/bitbake/lib/webhob/bldviewer/templates/layer.html
index 5b317ba..508f254 100644
--- a/bitbake/lib/webhob/bldviewer/templates/layer.html
+++ b/bitbake/lib/webhob/bldviewer/templates/layer.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "basetable.html" %}
 
 {% block pagename %}
     <h1>Toaster - Layers</h1>
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
index f70b022..622c23d 100644
--- a/bitbake/lib/webhob/bldviewer/templates/package.html
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "basetable.html" %}
 
 {% block pagename %}
     <h1>Toaster - Packages</h1>
diff --git a/bitbake/lib/webhob/bldviewer/templates/recipe.html b/bitbake/lib/webhob/bldviewer/templates/recipe.html
index 515053a..e855625 100644
--- a/bitbake/lib/webhob/bldviewer/templates/recipe.html
+++ b/bitbake/lib/webhob/bldviewer/templates/recipe.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "basetable.html" %}
 
 {% block pagename %}
     <h1>Toaster - Recipes for a Layer</h1>
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 6d06d0f..05d8e12 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "basetable.html" %}
 
 {% block pagename %}
     <h1>Toaster - Tasks</h1>
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 70/94] bitbake: webhob: add Configuration visualisation in Simple
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (67 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 69/94] bitbake: webhob: refactor CSS display in Simple Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 71/94] bitbake: webhob: add navigation links Simple interface Alex DAMIAN
                   ` (25 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We add a page to show the build configuration variables
in the Simple display.

We add a bit of navigation menu to allow moving between
pages of the same build.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 .../webhob/bldviewer/templates/configuration.html  | 28 ++++++++++++++++++++++
 .../lib/webhob/bldviewer/templates/package.html    |  7 +++++-
 bitbake/lib/webhob/bldviewer/templates/task.html   |  5 ++++
 bitbake/lib/webhob/bldviewer/urls.py               |  1 +
 bitbake/lib/webhob/bldviewer/views.py              | 12 +++++++---
 5 files changed, 49 insertions(+), 4 deletions(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/configuration.html

diff --git a/bitbake/lib/webhob/bldviewer/templates/configuration.html b/bitbake/lib/webhob/bldviewer/templates/configuration.html
new file mode 100644
index 0000000..2f197c0
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/configuration.html
@@ -0,0 +1,28 @@
+{% extends "basetable.html" %}
+
+{% block pagename %}
+<ul class="nav nav-tabs" style="display: inline-block">
+  <li><a href="/simple/build/{{build}}/task/"> Tasks </a></li>
+  <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
+  <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
+</ul>
+     <h1>Toaster - Build Configuration </h1>
+{% endblock %}
+
+{% block pagetable %}
+
+            <tr>
+            <th>Name</th>
+            <th>Value</th>
+            <th>Description</th>
+            </tr>
+
+            {% for variable in configuration %}
+
+            <tr class="data">
+                <td>{{variable.variable_name}}</td>
+                <td>{{variable.variable_value}}</td>
+                <td>{% if variable.description %}{{variable.description}}{% endif %}</td>
+            {% endfor %}
+
+{% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
index 622c23d..339b411 100644
--- a/bitbake/lib/webhob/bldviewer/templates/package.html
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -1,7 +1,12 @@
 {% extends "basetable.html" %}
 
 {% block pagename %}
-    <h1>Toaster - Packages</h1>
+<ul class="nav nav-tabs" style="display: inline-block">
+  <li><a href="/simple/build/{{build}}/task/"> Tasks </a></li>
+  <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
+  <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
+</ul>
+     <h1>Toaster - Packages</h1>
 {% endblock %}
 
 {% block pagetable %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 05d8e12..4939531 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -1,6 +1,11 @@
 {% extends "basetable.html" %}
 
 {% block pagename %}
+<ul class="nav nav-tabs" style="display: inline-block">
+  <li><a href="/simple/build/{{build}}/task/"> Tasks </a></li>
+  <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
+  <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
+</ul>
     <h1>Toaster - Tasks</h1>
 {% endblock %}
 
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index 8b8b5c3..8f9bc0d 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -5,6 +5,7 @@ urlpatterns = patterns('bldviewer.views',
         url(r'^build/$', 'build', name='build'),
         url(r'^build/(?P<build_id>\d+)/task/$', 'task', name='task'),
         url(r'^build/(?P<build_id>\d+)/package/$', 'package', name='package'),
+        url(r'^build/(?P<build_id>\d+)/configuration/$', 'configuration', name='configuration'),
         url(r'^layer/$', 'layer', name='layer'),
         url(r'^layerversions/(?P<layerversion_id>\d+)/recipes/.*$', 'layer_versions_recipes', name='layer_versions_recipes'),
         url(r'^$', redirect_to, {'url': 'build/'}),
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 2ef905a..77be08b 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -2,7 +2,7 @@ import operator
 
 from django.db.models import Q
 from django.shortcuts import render
-from orm.models import Build, Task, Layer, Layer_Version, Recipe, Package, LogMessage
+from orm.models import Build, Task, Layer, Layer_Version, Recipe, Package, LogMessage, Variable
 from orm.models import Task_Dependency, Package_Dependency
 from django.views.decorators.cache import cache_control
 
@@ -32,10 +32,16 @@ def task(request, build_id):
             if t == k.task:
                 t.depends_on.append(k.depends_on)
 
-    context = {'tasks': tasks}
+    context = {'build': build_id, 'tasks': tasks}
 
     return render(request, template, context)
 
+def configuration(request, build_id):
+    template = 'configuration.html'
+    variables = Variable.objects.filter(build=build_id)
+    context = {'build': build_id, 'configuration' : variables}
+    return render(request, template, context)
+
 def package(request, build_id):
     template = 'package.html'
 
@@ -48,7 +54,7 @@ def package(request, build_id):
             if t == k.package:
                 t.depends_on.append(k.depends_on)
 
-    context = {'packages': packages}
+    context = {'build' : build_id ,'packages': packages}
 
     return render(request, template, context)
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 71/94] bitbake: webhob: add navigation links Simple interface
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (68 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 70/94] bitbake: webhob: add Configuration visualisation " Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 72/94] bitbake: webhob: startup script fixing Alex DAMIAN
                   ` (24 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding a couple of navigation links on top of pages
so that we provide easier movement between pages and
a bit more context.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/build.html         | 2 +-
 bitbake/lib/webhob/bldviewer/templates/configuration.html | 1 +
 bitbake/lib/webhob/bldviewer/templates/package.html       | 1 +
 bitbake/lib/webhob/bldviewer/templates/recipe.html        | 3 +++
 bitbake/lib/webhob/bldviewer/templates/task.html          | 1 +
 bitbake/lib/webhob/bldviewer/views.py                     | 6 ++++--
 6 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index 53271a7..bbd283e 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -24,7 +24,7 @@
         </tr>
         {% for build in builds %}
         <tr class="data">
-            <td><a href="/simple/build/{{build.id}}/task/">{{build.get_outcome_display}}</a></td>
+            <td><a href="/simple/build/{{build.id}}/configuration/">{{build.get_outcome_display}}</a></td>
             <td>{{build.started_on}}</td>
             <td>{{build.completed_on}}</td>
             <td>{{build.target}}</td>
diff --git a/bitbake/lib/webhob/bldviewer/templates/configuration.html b/bitbake/lib/webhob/bldviewer/templates/configuration.html
index 2f197c0..4db4765 100644
--- a/bitbake/lib/webhob/bldviewer/templates/configuration.html
+++ b/bitbake/lib/webhob/bldviewer/templates/configuration.html
@@ -2,6 +2,7 @@
 
 {% block pagename %}
 <ul class="nav nav-tabs" style="display: inline-block">
+  <li><a>Build {{build}} : </a></li>
   <li><a href="/simple/build/{{build}}/task/"> Tasks </a></li>
   <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
   <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
index 339b411..b4091c6 100644
--- a/bitbake/lib/webhob/bldviewer/templates/package.html
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -2,6 +2,7 @@
 
 {% block pagename %}
 <ul class="nav nav-tabs" style="display: inline-block">
+  <li><a>Build {{build}} : </a></li>
   <li><a href="/simple/build/{{build}}/task/"> Tasks </a></li>
   <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
   <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
diff --git a/bitbake/lib/webhob/bldviewer/templates/recipe.html b/bitbake/lib/webhob/bldviewer/templates/recipe.html
index e855625..32fcdb8 100644
--- a/bitbake/lib/webhob/bldviewer/templates/recipe.html
+++ b/bitbake/lib/webhob/bldviewer/templates/recipe.html
@@ -1,6 +1,9 @@
 {% extends "basetable.html" %}
 
 {% block pagename %}
+<ul class="nav nav-tabs" style="display: inline-block">
+  <li><a>Layer  {{layer_version.layer.name}}&nbsp;:&nbsp;{{layer_version.branch}}&nbsp;:&nbsp;{{layer_version.commit}}</a></li>
+</ul>
     <h1>Toaster - Recipes for a Layer</h1>
 {% endblock %}
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 4939531..9736b4a 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -2,6 +2,7 @@
 
 {% block pagename %}
 <ul class="nav nav-tabs" style="display: inline-block">
+  <li><a>Build {{build}} : </a></li>
   <li><a href="/simple/build/{{build}}/task/"> Tasks </a></li>
   <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
   <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 77be08b..cb7cfc7 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -12,7 +12,7 @@ def build(request):
     build_info = Build.objects.all()
     logs = LogMessage.objects.all()
 
-    context = {'builds': build_info, 'logs': logs , 
+    context = {'builds': build_info, 'logs': logs ,
         'hideshowcols' : [
                 {'name': 'Output', 'order':10},
                 {'name': 'Log', 'order':11},
@@ -76,7 +76,9 @@ def layer_versions_recipes(request, layerversion_id):
     template = 'recipe.html'
     recipes = Recipe.objects.filter(layer_version__id = layerversion_id)
 
-    context = {'recipes': recipes}
+    context = {'recipes': recipes,
+            'layer_version' : Layer_Version.objects.filter( id = layerversion_id )[0]
+    }
 
     return render(request, template, context)
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 72/94] bitbake: webhob: startup script fixing
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (69 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 71/94] bitbake: webhob: add navigation links Simple interface Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 73/94] bitbake: webhob: change database models and related Alex DAMIAN
                   ` (23 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Let's not start the webserver if the database sync went wrong.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/bin/webhob | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/bitbake/bin/webhob b/bitbake/bin/webhob
index aa35294..0c4d9ea 100755
--- a/bitbake/bin/webhob
+++ b/bitbake/bin/webhob
@@ -43,8 +43,9 @@ function webserverStartAll()
         python $BBBASEDIR/lib/webhob/manage.py syncdb || retval=1
         if [ $retval -eq 1 ]; then
                 echo "Failed db sync, stopping system start" 1>&2
+        else
+            python $BBBASEDIR/lib/webhob/manage.py runserver </dev/null >${BUILDDIR}/whbmain.log 2>&1 & echo $! >${BUILDDIR}/whbmain.pid
         fi
-        python $BBBASEDIR/lib/webhob/manage.py runserver </dev/null >${BUILDDIR}/whbmain.log 2>&1 & echo $! >${BUILDDIR}/whbmain.pid
         return $retval
 }
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 73/94] bitbake: webhob: change database models and related
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (70 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 72/94] bitbake: webhob: startup script fixing Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 74/94] bitbake: webhob: navigation in the Simple interface Alex DAMIAN
                   ` (22 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We change the database models as to allow storage of
data based on new Package design:

* there is a separate Target model that will hold each
of the targets of a build.
* the package information from an image file is stored
in Target_Package model
* the package information from the build process is stored
in Build_Package model
* there is a provision to store dependency type between
Packages
* the code in the DSI has been altered to cope with the
new separate Targets for each build
* the code in Simple interface has been changed to provision
for the new model code.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py               | 153 +++++++++++----------
 bitbake/lib/bb/ui/dsi.py                           |   5 +-
 bitbake/lib/webhob/bldviewer/templates/build.html  |   2 +-
 .../lib/webhob/bldviewer/templates/package.html    |   4 +-
 bitbake/lib/webhob/bldviewer/urls.py               |   1 +
 bitbake/lib/webhob/bldviewer/views.py              |  11 +-
 bitbake/lib/webhob/orm/models.py                   |  71 +++++++---
 7 files changed, 151 insertions(+), 96 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 89d5f15..0860d2a 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -1,6 +1,5 @@
 import datetime
 import sys
-import uuid
 import bb
 import re
 
@@ -8,8 +7,9 @@ import re
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 
 import webhob.whbmain.settings as whb_django_settings
-from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Package, LogMessage
-from webhob.orm.models import Task_Dependency, Package_Dependency, Variable
+from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage
+from webhob.orm.models import Target_Package, Build_Package, Variable
+from webhob.orm.models import Task_Dependency, Build_Package_Dependency, Target_Package_Dependency
 from bb.msg import BBLogFormatter as format
 
 class ORMWrapper(object):
@@ -30,20 +30,30 @@ class ORMWrapper(object):
 
     def create_build_object(self, build_info):
 
-        build = Build.objects.create(uuid=build_info['uuid'],
-                                    target=build_info['target'],
+        build = Build.objects.create(
                                     machine=build_info['machine'],
                                     distro=build_info['distro'],
                                     distro_version=build_info['distro_version'],
                                     started_on=build_info['started_on'],
                                     completed_on=build_info['completed_on'],
-                                    image_fstypes=build_info['image_fstypes'],
                                     cooker_log_path=build_info['cooker_log_path'],
                                     build_name=build_info['build_name'],
                                     bitbake_version=build_info['bitbake_version'])
 
         return build
 
+    def create_target_objects(self, target_info):
+        targets = []
+        for tgt_name in target_info['targets']:
+            tgt_object = Target.objects.create( build = target_info['build'],
+                                    target = tgt_name,
+                                    is_image = False,
+                                    image_fstypes = "",
+                                    file_name = "",
+                                    file_size = 0);
+            targets.append(tgt_object)
+        return targets
+
     def update_build_object(self, build, errors, warnings, taskfailures):
 
         outcome = Build.SUCCEEDED
@@ -118,9 +128,9 @@ class ORMWrapper(object):
         return layer_object[0]
 
 
-    def save_package_information(self, build_obj, packagedict, bldpkgs, recipes):
+    def save_target_package_information(self, target_obj, packagedict, bldpkgs, recipes):
         for p in packagedict:
-            packagedict[p]['object'] = Package.objects.create( build = build_obj,
+            packagedict[p]['object'] = Target_Package.objects.create( target = target_obj,
                                         name = p,
                                         size = packagedict[p]['size'])
             if p in bldpkgs:
@@ -130,7 +140,7 @@ class ORMWrapper(object):
 
         for p in packagedict:
             for px in packagedict[p]['depends']:
-                Package_Dependency.objects.create( package = packagedict[p]['object'],
+                Target_Package_Dependency.objects.create( package = packagedict[p]['object'],
                                         depends_on = packagedict[px]['object'] );
 
 
@@ -165,7 +175,6 @@ class BuildInfoHelper(object):
     def __init__(self, server, has_build_history = False):
         self._configure_django()
         self.internal_state = {}
-        self.uuid = None
         self.task_order = 0
         self.server = server
         self.orm_wrapper = ORMWrapper()
@@ -252,7 +261,6 @@ class BuildInfoHelper(object):
         build_info = {}
         # Generate an identifier for each new build
 
-        build_info['uuid'] = self.uuid
         build_info['machine'] = machine_obj
         build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
         build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
@@ -307,15 +315,17 @@ class BuildInfoHelper(object):
 
     def _get_path_information(self, task_object):
         build_stats_format = "{tmpdir}/buildstats/{target}-{machine}/{buildname}/{package}/"
+        build_stats_path = []
 
-        target = self.internal_state['target']
-        machine = self.internal_state['build'].machine.name
-        buildname = self.internal_state['build'].build_name
-        package = task_object.recipe.name + "-" + task_object.recipe.version.strip(":")
+        for t in self.internal_state['targets']:
+            target = t.target
+            machine = self.internal_state['build'].machine.name
+            buildname = self.internal_state['build'].build_name
+            package = task_object.recipe.name + "-" + task_object.recipe.version.strip(":")
 
-        build_stats_path = build_stats_format.format(tmpdir=self.tmp_dir, target=target,
+            build_stats_path.append(build_stats_format.format(tmpdir=self.tmp_dir, target=target,
                                                      machine=machine, buildname=buildname,
-                                                     package=package)
+                                                     package=package))
 
         return build_stats_path
 
@@ -364,14 +374,19 @@ class BuildInfoHelper(object):
 
         machine_information = self._get_machine_information()
         machine_obj = self.orm_wrapper.create_machine_object(machine_information)
-        self.uuid = str(uuid.uuid4())
 
         build_information = self._get_build_information(machine_obj)
-        build_information['target'] = ' '.join(event.getPkgs())
 
         build_obj = self.orm_wrapper.create_build_object(build_information)
         self.internal_state['build'] = build_obj
-        self.internal_state['target'] = build_information['target']
+
+        # create target information
+        target_information = {}
+        target_information['targets'] = event.getPkgs()
+        target_information['build'] = build_obj
+
+        self.internal_state['targets'] = self.orm_wrapper.create_target_objects(target_information)
+
         # Load layer information for the build
         self.internal_state['layer_versions'] = []
         for layer_object in self.internal_state['layers']:
@@ -428,7 +443,7 @@ class BuildInfoHelper(object):
             task_information['message'] = event._message
 
         if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.sceneQueueTaskCompleted)):
-            task_information['outcome'] = Task.OUTCOME_SUCCESS     # TODO: needs to use constants
+            task_information['outcome'] = Task.OUTCOME_SUCCESS
             task_build_stats = self._get_task_build_stats(self.orm_wrapper.get_update_task_object(task_information))
             task_information['cpu_usage'] = task_build_stats['cpu_usage']
             task_information['disk_io'] = task_build_stats['disk_io']
@@ -438,54 +453,52 @@ class BuildInfoHelper(object):
             task_information['outcome'] = Task.OUTCOME_FAILED
             del self.internal_state[identifier]
 
-        #TODO: get error number
-        #TODO: get warnings number
-        #TODO: get warning information
-
-
         self.orm_wrapper.get_update_task_object(task_information)
 
 
-    def read_package_dep_data(self, event):
-        # verify that we have something to read
-        if not self.internal_state['build'].is_image or not self.has_build_history:
-            print "not collecting package info ", self.internal_state['build'].is_image, self.has_build_history
-            return
-
-        # fair warning: code correlates to buildhistory.bbclass; anything changes there, needs to chage here too
-        TOPDIR, error = self.server.runCommand(['getVariable', 'TOPDIR'])
-        MACHINE_ARCH, error = self.server.runCommand(['getVariable', 'MACHINE_ARCH'])
-        TCLIBC, error = self.server.runCommand(['getVariable', 'TCLIBC'])
-        MULTIMACH_TARGET_SYS, error = self.server.runCommand(['getVariable', 'MULTIMACH_TARGET_SYS'])
-        SDK_NAME, error = self.server.runCommand(['getVariable', 'SDK_NAME'])
-        BUILDHISTORY_DIR = "%s/buildhistory" % TOPDIR
-        BUILDHISTORY_DIR_IMAGE = "%s/images/%s/%s/%s" % (BUILDHISTORY_DIR, MACHINE_ARCH, TCLIBC, self.internal_state['build'].target)
-
-        self.internal_state['packages'] = {}
-
-        with open("%s/installed-package-sizes.txt" % BUILDHISTORY_DIR_IMAGE, "r") as fin:
-            for line in fin:
-                line = line.rstrip(";")
-                psize, px = line.split("\t")
-                punit, pname = px.split(" ")
-                self.internal_state['packages'][pname.strip()] = {'size':psize, 'depends' : []}
-
-        with open("%s/depends.dot" % BUILDHISTORY_DIR_IMAGE, "r") as fin:
-            p = re.compile(r' -> ')
-            for line in fin:
-                line = line.rstrip(';')
-                linesplit = p.split(line)
-                if len(linesplit) == 2:
-                    pname = linesplit[0]
-                    dependsname = linesplit[1].split(" ")[0].strip().strip(";")
-                    if not pname in self.internal_state['packages']:
-                        self.internal_state['packages'][pname] = {'size': 0, 'depends' : []}
-                    if not dependsname in self.internal_state['packages']:
-                        self.internal_state['packages'][dependsname] = {'size': 0, 'depends' : []}
-                    self.internal_state['packages'][pname]['depends'].append(dependsname)
-
-        self.orm_wrapper.save_package_information(self.internal_state['build'], self.internal_state['packages'],
-                    self.internal_state['bldpkgs'], self.internal_state['recipes'])
+    def read_target_package_dep_data(self, event):
+        # for all targets
+        for target in self.internal_state['targets']:
+            # verify that we have something to read
+            if not target.is_image or not self.has_build_history:
+                print "not collecting package info ", target.is_image, self.has_build_history
+                break
+
+            # fair warning: code correlates to buildhistory.bbclass; anything changes there, needs to chage here too
+            TOPDIR, error = self.server.runCommand(['getVariable', 'TOPDIR'])
+            MACHINE_ARCH, error = self.server.runCommand(['getVariable', 'MACHINE_ARCH'])
+            TCLIBC, error = self.server.runCommand(['getVariable', 'TCLIBC'])
+            MULTIMACH_TARGET_SYS, error = self.server.runCommand(['getVariable', 'MULTIMACH_TARGET_SYS'])
+            SDK_NAME, error = self.server.runCommand(['getVariable', 'SDK_NAME'])
+            BUILDHISTORY_DIR = "%s/buildhistory" % TOPDIR
+            BUILDHISTORY_DIR_IMAGE = "%s/images/%s/%s/%s" % (BUILDHISTORY_DIR, MACHINE_ARCH, TCLIBC, target.target)
+
+            self.internal_state['packages'] = {}
+
+            with open("%s/installed-package-sizes.txt" % BUILDHISTORY_DIR_IMAGE, "r") as fin:
+                for line in fin:
+                    line = line.rstrip(";")
+                    psize, px = line.split("\t")
+                    punit, pname = px.split(" ")
+                    self.internal_state['packages'][pname.strip()] = {'size':psize, 'depends' : []}
+
+            with open("%s/depends.dot" % BUILDHISTORY_DIR_IMAGE, "r") as fin:
+                p = re.compile(r' -> ')
+                for line in fin:
+                    line = line.rstrip(';')
+                    linesplit = p.split(line)
+                    if len(linesplit) == 2:
+                        pname = linesplit[0]
+                        dependsname = linesplit[1].split(" ")[0].strip().strip(";")
+                        if not pname in self.internal_state['packages']:
+                            self.internal_state['packages'][pname] = {'size': 0, 'depends' : []}
+                        if not dependsname in self.internal_state['packages']:
+                            self.internal_state['packages'][dependsname] = {'size': 0, 'depends' : []}
+                        self.internal_state['packages'][pname]['depends'].append(dependsname)
+
+            self.orm_wrapper.save_target_package_information(target,
+                        self.internal_state['packages'],
+                        self.internal_state['bldpkgs'], self.internal_state['recipes'])
 
 
     def store_dependency_information(self, event):
@@ -519,9 +532,11 @@ class BuildInfoHelper(object):
             recipe_info['file_path'] = file_name
             recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
             recipe.is_image = True in map(lambda x: x.endswith('image.bbclass'), event._depgraph['pn'][pn]['inherits'])
-            if recipe.is_image and pn == self.internal_state['build'].target:
-                self.internal_state['build'].is_image = True
-                self.internal_state['build'].save()
+            if recipe.is_image:
+                for t in self.internal_state['targets']:
+                    if pn == t.target:
+                        t.is_image = True
+                        t.save()
             self.internal_state['recipes'][pn] = recipe
 
         # save all task information
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index c139cd2..3284ea2 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -471,7 +471,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 continue
 
             if isinstance(event, (bb.event.BuildCompleted)):
-                buildinfohelper.read_package_dep_data(event)
+                buildinfohelper.read_target_package_dep_data(event)
                 buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
                 continue
 
@@ -531,6 +531,9 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                     logger.error("Unable to cleanly shutdown: %s" % error)
             main.shutdown = main.shutdown + 1
             pass
+        except Exception as e:
+            print(e)
+            pass
 
     summary = ""
     if taskfailures:
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index bbd283e..eed81a3 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -27,7 +27,7 @@
             <td><a href="/simple/build/{{build.id}}/configuration/">{{build.get_outcome_display}}</a></td>
             <td>{{build.started_on}}</td>
             <td>{{build.completed_on}}</td>
-            <td>{{build.target}}</td>
+            <td>{% for t in build.target_set.all %}<a href="/simple/build/{{build.id}}/target/{{t.id}}">{{t.target}}</a><br/>{% endfor %}</td>
             <td>{% if build.is_image %} <a href="/simple/build/{{build.id}}/package/">{{build.is_image}}</a>{% else %} {{build.is_image}} {% endif %}</td>
             <td>{{build.machine.name}}</td>
             <td>{% time_difference build.started_on build.completed_on %}</td>
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
index b4091c6..7764f05 100644
--- a/bitbake/lib/webhob/bldviewer/templates/package.html
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -7,12 +7,12 @@
   <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
   <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
 </ul>
-     <h1>Toaster - Packages</h1>
+     <h1>Toaster - Target Install Packages</h1>
 {% endblock %}
 
 {% block pagetable %}
     {% if not packages %}
-        <p>No packages were build in this build!</p>
+        <p>No packages were recorded for this target!</p>
     {% else %}
 
             <tr>
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index 8f9bc0d..11bdbe9 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -4,6 +4,7 @@ from django.views.generic.simple import redirect_to
 urlpatterns = patterns('bldviewer.views',
         url(r'^build/$', 'build', name='build'),
         url(r'^build/(?P<build_id>\d+)/task/$', 'task', name='task'),
+        url(r'^build/(?P<build_id>\d+)/targets/$', 'task', name='task'),
         url(r'^build/(?P<build_id>\d+)/package/$', 'package', name='package'),
         url(r'^build/(?P<build_id>\d+)/configuration/$', 'configuration', name='configuration'),
         url(r'^layer/$', 'layer', name='layer'),
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index cb7cfc7..ab222ac 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -2,14 +2,15 @@ import operator
 
 from django.db.models import Q
 from django.shortcuts import render
-from orm.models import Build, Task, Layer, Layer_Version, Recipe, Package, LogMessage, Variable
-from orm.models import Task_Dependency, Package_Dependency
+from orm.models import Build, Task, Layer, Layer_Version, Recipe, Target_Package, LogMessage, Variable
+from orm.models import Task_Dependency, Target_Package_Dependency
 from django.views.decorators.cache import cache_control
 
 @cache_control(no_store=True)
 def build(request):
     template = 'build.html'
     build_info = Build.objects.all()
+
     logs = LogMessage.objects.all()
 
     context = {'builds': build_info, 'logs': logs ,
@@ -45,8 +46,8 @@ def configuration(request, build_id):
 def package(request, build_id):
     template = 'package.html'
 
-    packages = Package.objects.filter(build=build_id)
-    package_depends = Package_Dependency.objects.filter(package__in=packages)
+    packages = Target_Package.objects.filter(build=build_id)
+    package_depends = Target_Package_Dependency.objects.filter(package__in=packages)
 
     for t in packages:
         t.depends_on = []
@@ -96,7 +97,7 @@ def model_explorer(request, model_name):
     model_mapping = {
         'build': Build,
         'task': Task,
-        'package': Package,
+        'package': Target_Package,
         'layer': Layer,
         'layerversion': Layer_Version,
         'recipe': Recipe,
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 1d67e40..4b5c413 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -12,12 +12,9 @@ class Build(models.Model):
         (IN_PROGRESS, 'In Progress'),
     )
 
-    search_allowed_fields = ['target', 'machine__name',
-                             'cooker_log_path', 'image_fstypes']
+    search_allowed_fields = ['machine__name',
+                             'cooker_log_path']
 
-    uuid = models.CharField(max_length=100, unique=True)
-    target = models.CharField(max_length=100)
-    is_image = models.BooleanField(default = False)
     machine = models.ForeignKey('Machine', related_name='build_machine')
     distro = models.CharField(max_length=100)
     distro_version = models.CharField(max_length=100)
@@ -26,12 +23,21 @@ class Build(models.Model):
     outcome = models.IntegerField(choices=BUILD_OUTCOME, default=IN_PROGRESS)
     errors_no = models.IntegerField(default=0)
     warnings_no = models.IntegerField(default=0)
-    image_fstypes = models.CharField(max_length=100)
     cooker_log_path = models.CharField(max_length=500)
     build_name = models.CharField(max_length=100)
     bitbake_version = models.CharField(max_length=50)
 
 
+class Target(models.Model):
+    search_allowed_fields = ['target', 'image_fstypes', 'file_name']
+    build = models.ForeignKey(Build)
+    target = models.CharField(max_length=100)
+    is_image = models.BooleanField(default = False)
+    image_fstypes = models.CharField(max_length=100)
+    file_name = models.CharField(max_length=100)
+    file_size = models.IntegerField()
+
+
 class Task(models.Model):
 
     SSTATE_NA = 0
@@ -100,27 +106,56 @@ class Task_Dependency(models.Model):
     depends_on = models.ForeignKey(Task, related_name='task_dependencies_depends')
 
 
-class Artifact(models.Model):
-    build = models.ForeignKey(Build, related_name='artifact_build')
-    file_name = models.CharField(max_length=100)
-    file_size = models.IntegerField()
+class Build_Package(models.Model):
+    build = models.ForeignKey('Build')
+    recipe = models.ForeignKey('Recipe', null=True)
+    name = models.CharField(max_length=100)
+    version = models.CharField(max_length=100, default="")
+    size = models.IntegerField()
+    license = models.CharField(max_length=200, null=True)
+
+class Build_Package_Dependency(models.Model):
+    TYPE_DEPENDS = 0
+    TYPE_RDEPENDS = 1
+    DEPENDS_TYPE = (
+        (TYPE_DEPENDS, "depends"),
+        (TYPE_RDEPENDS, "rdepends"),
+    )
+    package = models.ForeignKey(Build_Package, related_name='bpackage_dependencies_package')
+    depends_on = models.ForeignKey(Build_Package, related_name='bpackage_dependencies_depends')
+    dep_type = models.IntegerField(choices=DEPENDS_TYPE)
 
 
-class Package(models.Model):
-    build = models.ForeignKey('Build', related_name='package_build')
-    recipe = models.ForeignKey('Recipe', related_name='package_recipe', null=True)
+class Target_Package(models.Model):
+    target = models.ForeignKey('Target')
+    recipe = models.ForeignKey('Recipe', null=True)
     name = models.CharField(max_length=100)
     version = models.CharField(max_length=100, default="")
     size = models.IntegerField()
 
 
-class Package_Dependency(models.Model):
-    package = models.ForeignKey(Package, related_name='package_dependencies_package')
-    depends_on = models.ForeignKey(Package, related_name='package_dependencies_depends')
+class Target_Package_Dependency(models.Model):
+    TYPE_DEPENDS = 0
+    TYPE_RDEPENDS = 1
+    TYPE_RECOMMENDS = 2
+
+    DEPENDS_TYPE = (
+        (TYPE_DEPENDS, "depends"),
+        (TYPE_RDEPENDS, "rdepends"),
+        (TYPE_RECOMMENDS, "recommends"),
+    )
+    package = models.ForeignKey(Target_Package, related_name='tpackage_dependencies_package')
+    depends_on = models.ForeignKey(Target_Package, related_name='tpackage_dependencies_depends')
+    dep_type = models.IntegerField(choices=DEPENDS_TYPE)
+
 
+class Build_Filelist(models.Model):
+    bpackage = models.ForeignKey(Build_Package, related_name='filelist_bpackage')
+    complete_file_path = models.FilePathField(max_length=255, blank=True)
+    file_size = models.IntegerField()
 
-class Filelist(models.Model):
-    package = models.ForeignKey(Package, related_name='filelist_package')
+class Target_Filelist(models.Model):
+    tpackage = models.ForeignKey(Target_Package, related_name='filelist_tpackage')
     complete_file_path = models.FilePathField(max_length=255, blank=True)
     file_size = models.IntegerField()
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 74/94] bitbake: webhob: navigation in the Simple interface
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (71 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 73/94] bitbake: webhob: change database models and related Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 75/94] bitbake: dsi: save build-time package information Alex DAMIAN
                   ` (21 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Modifies the navigation in the Simple interface to
allow new multiple targets per build model.

Refactoring the code to load relationships as to be
based on Django's RelationshipManager automatic follower.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/base.html   |  4 ++--
 .../webhob/bldviewer/templates/configuration.html  | 13 ++----------
 .../lib/webhob/bldviewer/templates/package.html    |  2 +-
 bitbake/lib/webhob/bldviewer/templates/task.html   | 19 +++++-------------
 bitbake/lib/webhob/bldviewer/urls.py               | 10 +++++-----
 bitbake/lib/webhob/bldviewer/views.py              | 23 +++++-----------------
 bitbake/lib/webhob/orm/models.py                   |  6 +++++-
 bitbake/lib/webhob/whbmain/urls.py                 |  2 +-
 8 files changed, 26 insertions(+), 53 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/base.html b/bitbake/lib/webhob/bldviewer/templates/base.html
index 31abeb1..1a2278a 100644
--- a/bitbake/lib/webhob/bldviewer/templates/base.html
+++ b/bitbake/lib/webhob/bldviewer/templates/base.html
@@ -14,8 +14,8 @@
 <div style="width:100%; height: 100%; position:absolute">
 <div style="width: 100%; height: 3em" class="nav">
     <ul class="nav nav-tabs">
-        <li><a href="/simple/build/">All Builds</a></li>
-        <li><a href="/simple/layer/">All Layers</a></li>
+        <li><a href="/simple/builds/">All Builds</a></li>
+        <li><a href="/simple/layers/">All Layers</a></li>
     </ul>
 </div>
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/configuration.html b/bitbake/lib/webhob/bldviewer/templates/configuration.html
index 4db4765..052c37c 100644
--- a/bitbake/lib/webhob/bldviewer/templates/configuration.html
+++ b/bitbake/lib/webhob/bldviewer/templates/configuration.html
@@ -1,15 +1,6 @@
-{% extends "basetable.html" %}
-
-{% block pagename %}
-<ul class="nav nav-tabs" style="display: inline-block">
-  <li><a>Build {{build}} : </a></li>
-  <li><a href="/simple/build/{{build}}/task/"> Tasks </a></li>
-  <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
-  <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
-</ul>
-     <h1>Toaster - Build Configuration </h1>
-{% endblock %}
+{% extends "basebuildpage.html" %}
 
+{% block pagetitle %}Configuration{% endblock %}
 {% block pagetable %}
 
             <tr>
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
index 7764f05..cba21e1 100644
--- a/bitbake/lib/webhob/bldviewer/templates/package.html
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -1,4 +1,4 @@
-{% extends "basetable.html" %}
+{% extends "basebuildpage.html" %}
 
 {% block pagename %}
 <ul class="nav nav-tabs" style="display: inline-block">
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 9736b4a..65d75cb 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -1,15 +1,6 @@
-{% extends "basetable.html" %}
-
-{% block pagename %}
-<ul class="nav nav-tabs" style="display: inline-block">
-  <li><a>Build {{build}} : </a></li>
-  <li><a href="/simple/build/{{build}}/task/"> Tasks </a></li>
-  <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
-  <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
-</ul>
-    <h1>Toaster - Tasks</h1>
-{% endblock %}
+{% extends "basebuildpage.html" %}
 
+{% block pagetitle %}Tasks{% endblock %}
 {% block pagetable %}
     {% if not tasks %}
         <p>No tasks were executed in this build!</p>
@@ -55,9 +46,9 @@
                 <td><a target="_fileview" href="file:///{{task.recipe.file_path}}">{{task.recipe.file_path}}</a></td>
                 <td>
             <div style="height: 3em; overflow:auto">
-                {% for tt in task.depends_on %}
-                    <a href="#{{tt.recipe.name}}.{{tt.task_name}}">
-                    {{tt.recipe.name}}.{{tt.task_name}}</a><br/>
+                {% for tt in task.task_dependencies_task.all %}
+                    <a href="#{{tt.depends_on.recipe.name}}.{{tt.depends_on.task_name}}">
+                    {{tt.depends_on.recipe.name}}.{{tt.depends_on.task_name}}</a><br/>
                 {% endfor %}
             </div>
                 </td>
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index 11bdbe9..d49f8a5 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -2,12 +2,12 @@ from django.conf.urls import patterns, include, url
 from django.views.generic.simple import redirect_to
 
 urlpatterns = patterns('bldviewer.views',
-        url(r'^build/$', 'build', name='build'),
+        url(r'^builds/$', 'build', name='build'),
         url(r'^build/(?P<build_id>\d+)/task/$', 'task', name='task'),
-        url(r'^build/(?P<build_id>\d+)/targets/$', 'task', name='task'),
-        url(r'^build/(?P<build_id>\d+)/package/$', 'package', name='package'),
+        url(r'^build/(?P<build_id>\d+)/packages/$', 'bpackage', name='bpackage'),
+        url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)/packages/$', 'tpackage', name='tpackage'),
         url(r'^build/(?P<build_id>\d+)/configuration/$', 'configuration', name='configuration'),
-        url(r'^layer/$', 'layer', name='layer'),
+        url(r'^layers/$', 'layer', name='layer'),
         url(r'^layerversions/(?P<layerversion_id>\d+)/recipes/.*$', 'layer_versions_recipes', name='layer_versions_recipes'),
-        url(r'^$', redirect_to, {'url': 'build/'}),
+        url(r'^$', redirect_to, {'url': 'builds/'}),
 )
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index ab222ac..cfc2a89 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -25,37 +25,24 @@ def task(request, build_id):
     template = 'task.html'
 
     tasks = Task.objects.filter(build=build_id)
-    task_depends = Task_Dependency.objects.filter(task__in=tasks).select_related()
 
-    for t in tasks:
-        t.depends_on = []
-        for k in task_depends:
-            if t == k.task:
-                t.depends_on.append(k.depends_on)
-
-    context = {'build': build_id, 'tasks': tasks}
+    context = {'build': Build.objects.filter(pk=build_id)[0], 'tasks': tasks}
 
     return render(request, template, context)
 
 def configuration(request, build_id):
     template = 'configuration.html'
     variables = Variable.objects.filter(build=build_id)
-    context = {'build': build_id, 'configuration' : variables}
+    context = {'build': Build.objects.filter(pk=build_id)[0], 'configuration' : variables}
     return render(request, template, context)
 
-def package(request, build_id):
+def tpackage(request, build_id, target_id):
     template = 'package.html'
 
-    packages = Target_Package.objects.filter(build=build_id)
+    packages = Target_Package.objects.filter(target=target_id)
     package_depends = Target_Package_Dependency.objects.filter(package__in=packages)
 
-    for t in packages:
-        t.depends_on = []
-        for k in package_depends:
-            if t == k.package:
-                t.depends_on.append(k.depends_on)
-
-    context = {'build' : build_id ,'packages': packages}
+    context = {'build' : Build.objects.filter(pk=build_id)[0],'packages': packages}
 
     return render(request, template, context)
 
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 4b5c413..f3d988e 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -1,4 +1,5 @@
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 
 
 class Build(models.Model):
@@ -27,7 +28,7 @@ class Build(models.Model):
     build_name = models.CharField(max_length=100)
     bitbake_version = models.CharField(max_length=50)
 
-
+@python_2_unicode_compatible
 class Target(models.Model):
     search_allowed_fields = ['target', 'image_fstypes', 'file_name']
     build = models.ForeignKey(Build)
@@ -37,6 +38,9 @@ class Target(models.Model):
     file_name = models.CharField(max_length=100)
     file_size = models.IntegerField()
 
+    def __str__(self):
+        return self.target
+
 
 class Task(models.Model):
 
diff --git a/bitbake/lib/webhob/whbmain/urls.py b/bitbake/lib/webhob/whbmain/urls.py
index 0e15eda..73f9370 100644
--- a/bitbake/lib/webhob/whbmain/urls.py
+++ b/bitbake/lib/webhob/whbmain/urls.py
@@ -10,7 +10,7 @@ urlpatterns = patterns('',
     url(r'^simple/', include('bldviewer.urls')),
     url(r'^api/1.0/', include('bldviewer.api')),
     url(r'^gui/', include('whbgui.urls')),
-    url(r'^$', redirect_to, {'url': '/gui/'}),
+    url(r'^$', redirect_to, {'url': '/simple/'}),
     # Examples:
     # url(r'^webhob/', include('webhob.foo.urls')),
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 75/94] bitbake: dsi: save build-time package information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (72 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 74/94] bitbake: webhob: navigation in the Simple interface Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 76/94] bitbake: webhob: Simple visualisation for the build-time packages Alex DAMIAN
                   ` (20 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Saving the build-time package information in the database.
Slight additions to the ORM model as to record more information.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 20 ++++++++++++++++++++
 bitbake/lib/bb/ui/dsi.py             |  5 +++++
 bitbake/lib/webhob/orm/models.py     |  8 ++++++--
 3 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 0860d2a..06ca703 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -156,6 +156,20 @@ class ORMWrapper(object):
 
         return log_object.save()
 
+
+    def save_build_package_information(self, build_obj, package_info, recipes):
+        bp_object = Build_Package.objects.create( build = build_obj,
+                                       recipe = recipes[package_info['PN']],
+                                       name = package_info['PKG'],
+                                       version = package_info['PKGV'],
+                                       revision = package_info['PKGR'],
+                                       summary = package_info['SUMMARY'],
+                                       description = package_info['DESCRIPTION'],
+                                       section = package_info['SECTION'],
+                                       license = package_info['LICENSE'],
+                                       )
+        return bp_object
+
     def save_build_variables(self, build_obj, vardump):
         for k in vardump:
             if not bool(vardump[k]['func']):
@@ -558,6 +572,12 @@ class BuildInfoHelper(object):
                 dep = _save_a_task(taskdesc1)
                 Task_Dependency.objects.get_or_create( task = target, depends_on = dep )
 
+    def store_build_package_information(self, event):
+        package_info = event.data
+        self.orm_wrapper.save_build_package_information(self.internal_state['build'],
+                            package_info, self.internal_state['recipes'])
+
+
     def store_log_information(self, level, text):
         log_information = {}
         log_information['build'] = self.internal_state['build']
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 3284ea2..643e98e 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -489,6 +489,11 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 buildinfohelper.store_layer_info()
                 continue
 
+            if isinstance(event, bb.event.MetadataEvent):
+                if event.type == "SinglePackageInfo":
+                    buildinfohelper.store_build_package_information(event)
+                continue
+
             # ignore
             if isinstance(event, (bb.event.BuildBase,
                                   bb.event.StampUpdate,
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index f3d988e..7e57c1d 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -115,8 +115,12 @@ class Build_Package(models.Model):
     recipe = models.ForeignKey('Recipe', null=True)
     name = models.CharField(max_length=100)
     version = models.CharField(max_length=100, default="")
-    size = models.IntegerField()
-    license = models.CharField(max_length=200, null=True)
+    revision = models.CharField(max_length=32, default="")
+    summary = models.CharField(max_length=200, default="")
+    description = models.CharField(max_length=200, default="")
+    size = models.IntegerField(default=0)
+    section = models.CharField(max_length=80, null=True)
+    license = models.CharField(max_length=80, null=True)
 
 class Build_Package_Dependency(models.Model):
     TYPE_DEPENDS = 0
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 76/94] bitbake: webhob: Simple visualisation for the build-time packages
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (73 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 75/94] bitbake: dsi: save build-time package information Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 77/94] bitbake: webhob: store file size information Alex DAMIAN
                   ` (19 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding pages and views to allow viewing the data collected
about the build-time packages.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 .../webhob/bldviewer/templates/basebuildpage.html  | 17 ++++++++++
 .../lib/webhob/bldviewer/templates/bpackage.html   | 36 ++++++++++++++++++++++
 .../lib/webhob/bldviewer/templates/package.html    | 10 ------
 bitbake/lib/webhob/bldviewer/views.py              |  9 +++++-
 4 files changed, 61 insertions(+), 11 deletions(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/basebuildpage.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/bpackage.html

diff --git a/bitbake/lib/webhob/bldviewer/templates/basebuildpage.html b/bitbake/lib/webhob/bldviewer/templates/basebuildpage.html
new file mode 100644
index 0000000..478019b
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/basebuildpage.html
@@ -0,0 +1,17 @@
+{% extends "basetable.html" %}
+
+{% block pagename %}
+<ul class="nav nav-tabs" style="display: inline-block">
+  <li><a>Build {{build.target_set.all|join:"&nbsp;"}} at {{build.started_on}} : </a></li>
+  <li><a href="/simple/build/{{build.id}}/task/"> Tasks </a></li>
+  <li><a href="/simple/build/{{build.id}}/packages/"> Build Packages </a></li>
+    {% for t in build.target_set.all %}
+        {% if t.is_image %}
+  <li><a href="/simple/build/{{build.id}}/target/{{t.pk}}/packages/"> Packages for {{t.target}} </a> </li>
+        {% endif %}
+    {% endfor %}
+  <li><a href="/simple/build/{{build.id}}/configuration/"> Configuration </a> </li>
+</ul>
+     <h1>Toaster - Build {% block pagetitle %} {% endblock %}</h1>
+{% endblock %}
+
diff --git a/bitbake/lib/webhob/bldviewer/templates/bpackage.html b/bitbake/lib/webhob/bldviewer/templates/bpackage.html
new file mode 100644
index 0000000..4e6cb00
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/bpackage.html
@@ -0,0 +1,36 @@
+{% extends "basebuildpage.html" %}
+
+{% block pagetable %}
+    {% if not packages %}
+        <p>No packages were recorded for this target!</p>
+    {% else %}
+
+            <tr>
+            <th>Name</th>
+            <th>Version</th>
+            <th>Revision</th>
+            <th>Recipe</th>
+            <th>Summary</th>
+            <th>Section</th>
+            <th>Description</th>
+            <th>License</th>
+            </tr>
+
+            {% for package in packages %}
+
+            <tr class="data">
+                <td><a name="#{{package.name}}">{{package.name}}</a></td>
+                <td>{{package.version}}</td>
+                <td>{{package.revision}}</td>
+                <td><a href="/simple/layerversions/{{package.recipe.layer_version_id}}/recipes/#{{package.recipe.name}}">{{package.recipe.name}}</a>{{package.package_name}}</a></td>
+                
+            <td>{{package.summary}}</td>
+            <td>{{package.section}}</td>
+            <td>{{package.description}}</td>
+            <td>{{package.license}}</td>
+
+            {% endfor %}
+
+    {% endif %}
+
+{% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/package.html b/bitbake/lib/webhob/bldviewer/templates/package.html
index cba21e1..4ce25ae 100644
--- a/bitbake/lib/webhob/bldviewer/templates/package.html
+++ b/bitbake/lib/webhob/bldviewer/templates/package.html
@@ -1,15 +1,5 @@
 {% extends "basebuildpage.html" %}
 
-{% block pagename %}
-<ul class="nav nav-tabs" style="display: inline-block">
-  <li><a>Build {{build}} : </a></li>
-  <li><a href="/simple/build/{{build}}/task/"> Tasks </a></li>
-  <li><a href="/simple/build/{{build}}/package/"> Package </a> </li>
-  <li><a href="/simple/build/{{build}}/configuration/"> Configuration </a> </li>
-</ul>
-     <h1>Toaster - Target Install Packages</h1>
-{% endblock %}
-
 {% block pagetable %}
     {% if not packages %}
         <p>No packages were recorded for this target!</p>
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index cfc2a89..a1da8b3 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -3,7 +3,7 @@ import operator
 from django.db.models import Q
 from django.shortcuts import render
 from orm.models import Build, Task, Layer, Layer_Version, Recipe, Target_Package, LogMessage, Variable
-from orm.models import Task_Dependency, Target_Package_Dependency
+from orm.models import Task_Dependency, Target_Package_Dependency, Build_Package
 from django.views.decorators.cache import cache_control
 
 @cache_control(no_store=True)
@@ -36,6 +36,13 @@ def configuration(request, build_id):
     context = {'build': Build.objects.filter(pk=build_id)[0], 'configuration' : variables}
     return render(request, template, context)
 
+
+def bpackage(request, build_id):
+    template = 'bpackage.html'
+    packages = Build_Package.objects.filter(build = build_id)
+    context = {'build': Build.objects.filter(pk=build_id)[0], 'packages' : packages}
+    return render(request, template, context)
+
 def tpackage(request, build_id, target_id):
     template = 'package.html'
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 77/94] bitbake: webhob: store file size information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (74 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 76/94] bitbake: webhob: Simple visualisation for the build-time packages Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 78/94] bitbake: webhob: simple visualisation for package files Alex DAMIAN
                   ` (18 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding code to store file size information for each
of the build package. Reassembling information from
a pair of events for each package - first contains
file and size information, and the second contains
package metadata.

Cleanup of the database models to hold file size info.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 24 +++++++++++++++++++++---
 bitbake/lib/bb/ui/dsi.py             |  2 ++
 bitbake/lib/webhob/orm/models.py     | 12 ++++++------
 3 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 06ca703..8b0e720 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -8,7 +8,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 
 import webhob.whbmain.settings as whb_django_settings
 from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage
-from webhob.orm.models import Target_Package, Build_Package, Variable
+from webhob.orm.models import Target_Package, Build_Package, Variable, Build_File
 from webhob.orm.models import Task_Dependency, Build_Package_Dependency, Target_Package_Dependency
 from bb.msg import BBLogFormatter as format
 
@@ -157,7 +157,8 @@ class ORMWrapper(object):
         return log_object.save()
 
 
-    def save_build_package_information(self, build_obj, package_info, recipes):
+    def save_build_package_information(self, build_obj, package_info, recipes, files):
+        # create and save the object
         bp_object = Build_Package.objects.create( build = build_obj,
                                        recipe = recipes[package_info['PN']],
                                        name = package_info['PKG'],
@@ -168,6 +169,14 @@ class ORMWrapper(object):
                                        section = package_info['SECTION'],
                                        license = package_info['LICENSE'],
                                        )
+        # save any attached file information
+        if bp_object.name in files.keys():
+            for path, size in files[bp_object.name]:
+                fo = Build_File.objects.create( bpackage = bp_object,
+                                        path = path,
+                                        size = size )
+            del files[bp_object.name]
+
         return bp_object
 
     def save_build_variables(self, build_obj, vardump):
@@ -575,8 +584,17 @@ class BuildInfoHelper(object):
     def store_build_package_information(self, event):
         package_info = event.data
         self.orm_wrapper.save_build_package_information(self.internal_state['build'],
-                            package_info, self.internal_state['recipes'])
+                            package_info,
+                            self.internal_state['recipes'],
+                            self.internal_state['package_files'])
+
+
+    def store_package_file_information(self, event):
+        if not 'package_files' in self.internal_state.keys():
+            self.internal_state['package_files'] = {}
 
+        data = event._data
+        self.internal_state['package_files'][data['PKG']] = data['FILES']
 
     def store_log_information(self, level, text):
         log_information = {}
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 643e98e..e5447ec 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -492,6 +492,8 @@ def main(server, eventHandler, params, tf = TerminalFilter):
             if isinstance(event, bb.event.MetadataEvent):
                 if event.type == "SinglePackageInfo":
                     buildinfohelper.store_build_package_information(event)
+                elif event.type == "PackageFileSize":
+                    buildinfohelper.store_package_file_information(event)
                 continue
 
             # ignore
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 7e57c1d..aa368fe 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -157,15 +157,15 @@ class Target_Package_Dependency(models.Model):
     dep_type = models.IntegerField(choices=DEPENDS_TYPE)
 
 
-class Build_Filelist(models.Model):
+class Build_File(models.Model):
     bpackage = models.ForeignKey(Build_Package, related_name='filelist_bpackage')
-    complete_file_path = models.FilePathField(max_length=255, blank=True)
-    file_size = models.IntegerField()
+    path = models.FilePathField(max_length=255, blank=True)
+    size = models.IntegerField()
 
-class Target_Filelist(models.Model):
+class Target_File(models.Model):
     tpackage = models.ForeignKey(Target_Package, related_name='filelist_tpackage')
-    complete_file_path = models.FilePathField(max_length=255, blank=True)
-    file_size = models.IntegerField()
+    path = models.FilePathField(max_length=255, blank=True)
+    size = models.IntegerField()
 
 
 class Recipe(models.Model):
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 78/94] bitbake: webhob: simple visualisation for package files
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (75 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 77/94] bitbake: webhob: store file size information Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 79/94] bitbake: dsi: record recipe dependency Alex DAMIAN
                   ` (17 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding a simple table visualisation for data collected
for package content - file names and file sizes.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/bfile.html  | 24 ++++++++++++++++++++++
 .../lib/webhob/bldviewer/templates/bpackage.html   |  7 +++----
 bitbake/lib/webhob/bldviewer/urls.py               |  1 +
 bitbake/lib/webhob/bldviewer/views.py              |  9 ++++++--
 4 files changed, 35 insertions(+), 6 deletions(-)
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/bfile.html

diff --git a/bitbake/lib/webhob/bldviewer/templates/bfile.html b/bitbake/lib/webhob/bldviewer/templates/bfile.html
new file mode 100644
index 0000000..d4943c0
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/templates/bfile.html
@@ -0,0 +1,24 @@
+{% extends "basebuildpage.html" %}
+
+{% block pagetitle %}Files for package {{files.0.bpackage.name}} {% endblock %}
+{% block pagetable %}
+    {% if not files %}
+        <p>No files were recorded for this package!</p>
+    {% else %}
+
+            <tr>
+            <th>Name</th>
+            <th>Size</th>
+            </tr>
+
+            {% for file in files %}
+
+            <tr class="data">
+            <td>{{file.path}}</td>
+            <td>{{file.size}}</td>
+
+            {% endfor %}
+
+    {% endif %}
+
+{% endblock %}
diff --git a/bitbake/lib/webhob/bldviewer/templates/bpackage.html b/bitbake/lib/webhob/bldviewer/templates/bpackage.html
index 4e6cb00..91e5f6f 100644
--- a/bitbake/lib/webhob/bldviewer/templates/bpackage.html
+++ b/bitbake/lib/webhob/bldviewer/templates/bpackage.html
@@ -1,5 +1,6 @@
 {% extends "basebuildpage.html" %}
 
+{% block pagetitle %}Packages{% endblock %}
 {% block pagetable %}
     {% if not packages %}
         <p>No packages were recorded for this target!</p>
@@ -8,7 +9,6 @@
             <tr>
             <th>Name</th>
             <th>Version</th>
-            <th>Revision</th>
             <th>Recipe</th>
             <th>Summary</th>
             <th>Section</th>
@@ -19,9 +19,8 @@
             {% for package in packages %}
 
             <tr class="data">
-                <td><a name="#{{package.name}}">{{package.name}}</a></td>
-                <td>{{package.version}}</td>
-                <td>{{package.revision}}</td>
+                <td><a name="#{{package.name}}" href="/simple/build/{{build.pk}}/package/{{package.pk}}/files/">{{package.name}} ({{package.filelist_bpackage.count}} files)</a></td>
+                <td>{{package.version}}-{{package.revision}}</td>
                 <td><a href="/simple/layerversions/{{package.recipe.layer_version_id}}/recipes/#{{package.recipe.name}}">{{package.recipe.name}}</a>{{package.package_name}}</a></td>
                 
             <td>{{package.summary}}</td>
diff --git a/bitbake/lib/webhob/bldviewer/urls.py b/bitbake/lib/webhob/bldviewer/urls.py
index d49f8a5..d6dd280 100644
--- a/bitbake/lib/webhob/bldviewer/urls.py
+++ b/bitbake/lib/webhob/bldviewer/urls.py
@@ -5,6 +5,7 @@ urlpatterns = patterns('bldviewer.views',
         url(r'^builds/$', 'build', name='build'),
         url(r'^build/(?P<build_id>\d+)/task/$', 'task', name='task'),
         url(r'^build/(?P<build_id>\d+)/packages/$', 'bpackage', name='bpackage'),
+        url(r'^build/(?P<build_id>\d+)/package/(?P<package_id>\d+)/files/$', 'bfile', name='bfile'),
         url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)/packages/$', 'tpackage', name='tpackage'),
         url(r'^build/(?P<build_id>\d+)/configuration/$', 'configuration', name='configuration'),
         url(r'^layers/$', 'layer', name='layer'),
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index a1da8b3..4ff23d3 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -3,7 +3,7 @@ import operator
 from django.db.models import Q
 from django.shortcuts import render
 from orm.models import Build, Task, Layer, Layer_Version, Recipe, Target_Package, LogMessage, Variable
-from orm.models import Task_Dependency, Target_Package_Dependency, Build_Package
+from orm.models import Task_Dependency, Target_Package_Dependency, Build_Package, Build_File
 from django.views.decorators.cache import cache_control
 
 @cache_control(no_store=True)
@@ -36,13 +36,18 @@ def configuration(request, build_id):
     context = {'build': Build.objects.filter(pk=build_id)[0], 'configuration' : variables}
     return render(request, template, context)
 
-
 def bpackage(request, build_id):
     template = 'bpackage.html'
     packages = Build_Package.objects.filter(build = build_id)
     context = {'build': Build.objects.filter(pk=build_id)[0], 'packages' : packages}
     return render(request, template, context)
 
+def bfile(request, build_id, package_id):
+    template = 'bfile.html'
+    files = Build_File.objects.filter(bpackage = package_id)
+    context = {'build': Build.objects.filter(pk=build_id)[0], 'files' : files}
+    return render(request, template, context)
+
 def tpackage(request, build_id, target_id):
     template = 'package.html'
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 79/94] bitbake: dsi: record recipe dependency
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (76 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 78/94] bitbake: webhob: simple visualisation for package files Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 80/94] bitbake: dsi: add exception dumping code Alex DAMIAN
                   ` (16 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Add code to record recipe dependencies, for DEPENDS
and RDEPENDS relationships.

Caveat emptor: relationships with ASSUME_PROVIDED
PNs will not be recorded, as they don't have real
recipe objects.

Adds code to the Simple interface to allow visualisation
of the recorded data.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py               | 23 +++++++++++++++++++++-
 bitbake/lib/webhob/bldviewer/templates/recipe.html |  9 +++++++++
 bitbake/lib/webhob/orm/models.py                   | 11 +++++++++--
 3 files changed, 40 insertions(+), 3 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 8b0e720..cf947da 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -9,7 +9,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 import webhob.whbmain.settings as whb_django_settings
 from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage
 from webhob.orm.models import Target_Package, Build_Package, Variable, Build_File
-from webhob.orm.models import Task_Dependency, Build_Package_Dependency, Target_Package_Dependency
+from webhob.orm.models import Task_Dependency, Build_Package_Dependency, Target_Package_Dependency, Recipe_Dependency
 from bb.msg import BBLogFormatter as format
 
 class ORMWrapper(object):
@@ -562,6 +562,27 @@ class BuildInfoHelper(object):
                         t.save()
             self.internal_state['recipes'][pn] = recipe
 
+        # save recipe dependency
+        try:
+            # buildtime
+            for recipe in event._depgraph['depends']:
+                target = self.internal_state['recipes'][recipe]
+                for dep in event._depgraph['depends'][recipe]:
+                    dependency = self.internal_state['recipes'][dep]
+                    Recipe_Dependency.objects.get_or_create( recipe = target,
+                            depends_on = dependency, dep_type = Recipe_Dependency.TYPE_DEPENDS)
+
+            # runtime
+            for recipe in event._depgraph['rdepends-pn']:
+                target = self.internal_state['recipes'][recipe]
+                for dep in event._depgraph['rdepends-pn'][recipe]:
+                    dependency = self.internal_state['recipes'][dep]
+                    Recipe_Dependency.objects.get_or_create( recipe = target,
+                            depends_on = dependency, dep_type = Recipe_Dependency.TYPE_RDEPENDS)
+
+        except KeyError:    # we'll not get recipes for key w/ values listed in ASSUME_PROVIDED
+            pass
+
         # save all task information
         def _save_a_task(taskdesc):
             spec = re.split(r'\.', taskdesc);
diff --git a/bitbake/lib/webhob/bldviewer/templates/recipe.html b/bitbake/lib/webhob/bldviewer/templates/recipe.html
index 32fcdb8..e81883f 100644
--- a/bitbake/lib/webhob/bldviewer/templates/recipe.html
+++ b/bitbake/lib/webhob/bldviewer/templates/recipe.html
@@ -22,6 +22,8 @@
             <th>Bugtracker</th>
             <th>Author</th>
             <th>File_path</th>
+            <th style="width: 30em">Recipe Dependency</th>
+
 
         {% for recipe in recipes %}
 
@@ -36,6 +38,13 @@
             <td>{{recipe.bugtracker}}</td>
             <td>{{recipe.author}}</td>
             <td>{{recipe.file_path}}</td>
+            <td>
+        <div style="height: 5em; overflow:auto">
+            {% for rr in recipe.r_dependencies_recipe.all %}
+                <a href="#{{rr.depends_on.name}}">{{rr.depends_on.name}}</a><br/>
+            {% endfor %}
+        </div>
+            </td>
         </tr>
 
         {% endfor %}
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index aa368fe..7ce0d1f 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -184,9 +184,16 @@ class Recipe(models.Model):
 
 
 class Recipe_Dependency(models.Model):
-    recipe = models.ForeignKey(Recipe, related_name='recipe_dependencies_recipe')
-    depends_on = models.ForeignKey(Recipe, related_name='recipe_dependencies_depends')
+    TYPE_DEPENDS = 0
+    TYPE_RDEPENDS = 1
 
+    DEPENDS_TYPE = (
+        (TYPE_DEPENDS, "depends"),
+        (TYPE_RDEPENDS, "rdepends"),
+    )
+    recipe = models.ForeignKey(Recipe, related_name='r_dependencies_recipe')
+    depends_on = models.ForeignKey(Recipe, related_name='r_dependencies_depends')
+    dep_type = models.IntegerField(choices=DEPENDS_TYPE)
 
 class Layer(models.Model):
     name = models.CharField(max_length=100)
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 80/94] bitbake: dsi: add exception dumping code
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (77 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 79/94] bitbake: dsi: record recipe dependency Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 81/94] bitbake: webhob: better help message in startup script Alex DAMIAN
                   ` (15 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Dump detailed information about unexpected
exceptions during a run. This has effect only
when running DSI from command line, since in
background mode everything is dumped to /dev/null

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/dsi.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index e5447ec..22cff5d 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -540,6 +540,8 @@ def main(server, eventHandler, params, tf = TerminalFilter):
             pass
         except Exception as e:
             print(e)
+            import traceback
+            traceback.print_exc()
             pass
 
     summary = ""
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 00/94] Webhob patches for DSI and Web modules
@ 2013-09-24 16:52 Alex DAMIAN
  2013-09-24 16:51 ` [PATCH 01/94] webhob: create main WEBHOB project Alex DAMIAN
                   ` (94 more replies)
  0 siblings, 95 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Patches for the DSI and Web modules of Webhob, in the order of development.

DSI is the DataStore Interface, the component that records information
derived from a Bitbake build into the data backend.

Web module consists of the Simple UI interface, and the API interface available
to application for data query.

External components jQuery and Bootstrap are included in the sources,
and respective licenses documented (APACHE-2.0 and MIT).


The following changes since commit 0fc8317c6385eb1ed69ca4522ee6424c456dbb92:

  yocto-bsp: add 3.10/remove 3.8 kernel from templates (2013-09-24 12:01:47 +0100)

are available in the git repository at:

  git://git.yoctoproject.org/poky-contrib webhob-poky/webhob-master-1_5_upstream_2509
  http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=webhob-poky/webhob-master-1_5_upstream_2509

Alexandru DAMIAN (72):
  webhob: add system start/stop script
  bitbake: webhob: set sane default settings
  bitbake: webhob: adds dsi support for observer mode
  bitbake: dsi: Translate runQueue events into Task data
  bitbake: webhob: disable synchronous sqlite connection
  bitbake: buildinfohelper: clean-up the imports
  bitbake: buildinfohelper: clearing up task order
  bitbake: dsi: saving prebuild task detailed information
  bitbake: dsi: clear up the state on build completion
  bitbake: dsi: event data change
  bitbake: webhob: fix and cleanup start script
  bitbake: dsi: refactor the BuildInfoHelper code
  bitbake: webhob: record dependency info
  bitbake: webhob: use defined constants for models
  bitbake: dsi: fix the reading of task event information
  bitbake: webhob: orm change to remove Target
  bitbake: webhob: clear up ORM relations
  bitbake: dsi: save detailed recipe information
  bitbake: webhob: determine if a build is an image
  bitbake: webhob: clean up URL structure
  bitbake: webhob: add simple pages for Recipes
  bitbake: webhob: enhance Simple browser navigation
  bitbake: webhob: improve startup script
  bitbake: webhob: extend search for multiple terms
  bitbake: webhob: force bitbake server stop
  bitbake: webhob: simple interface dependency list
  bitbake: dsi: add feature to store package information
  bitbake: webhob: add simple viewer for package information
  bitbake: dsi: fix build stats data gathering
  bitbake: dsi: update build object on command end
  bitbake: dsi: fix sstate task information gathering
  bitbake: webhob: clean up starting script
  bitbake: dsi: store log information
  bitbake: webhob: add simple visualisation for build errors
  bitbake: webhob: store logfile and message for Tasks
  bitbake: webhob: display proper information in Simple
  bitbake: dsi: clear up global variables between builds
  bitbake: dsi: small bugfixes in data collection
  bitbake: bldviewer: add jquery and bootstrap frameworks
  bitbake: webhob: add toggle column functionality to build page
  bitbake: webhob: add search functionality to the Simple interface
  bitbake: webhob: refactor column hiding code
  bitbake: webhob: improve search functionality
  bitbake: webhob: refactor Simple web interface
  bitbake: webhob: simple interface CSS
  bitbake: dsi: use get vars command to store configuration
  bitbake: webhob: refactor CSS display in Simple
  bitbake: webhob: add Configuration visualisation in Simple
  bitbake: webhob: add navigation links Simple interface
  bitbake: webhob: startup script fixing
  bitbake: webhob: change database models and related
  bitbake: webhob: navigation in the Simple interface
  bitbake: dsi: save build-time package information
  bitbake: webhob: Simple visualisation for the build-time packages
  bitbake: webhob: store file size information
  bitbake: webhob: simple visualisation for package files
  bitbake: dsi: record recipe dependency
  bitbake: dsi: add exception dumping code
  bitbake: webhob: better help message in startup script
  bitbake: dsi: add build package dependency recording
  bitbake: webhob: clean up Machine table
  bitbake: webhob: update API endpoints
  bitbake: webhob: record task hash information
  bitbake: cooker: add the inherits attribute to the dependency tree
  bitbake: webhob: Clean up links in Simple UI
  bitbake: dsi: read correct recipe description field
  bitbake: cooker: send layer information with dependency tree
  bitbake: webhob: store and display layer priorities
  bitbake: webhob: collect recipe licensing info
  bitbake: webhob: Simple UI interface CSS fix
  bitbake: dsi: fix typos
  bitbake: webhob: do not set timezones

Calin Dragomir (22):
  webhob: create main WEBHOB project
  webhob: create Django models for webhob
  bitbake: create Data Store Interface (DSI) file
  bitbake: webhob: updates to Django models
  bitbake: webhob: make DSI store build information
  bitbake: webhob: make DSI store task information
  bitbake: webhob: view a table with all builds
  bitbake: webhob: view a table with all the tasks of a build
  bitbake: webhob: use buildcompleted event as the end of a build
    operation
  bitbake: webhob: changes to build information table
  bitbake: webhob: gather buildstats for each executed task
  bitbake: webhob: add layer information to the database
  bitbake: webhob: mark private methods inside DSI
  bitbake: webhob: create basic structure for static files
  bitbake: webhob: Setup API for build model
  bitbake: webhob: add ordering capabilities to build api
  bitbake: webhob: validate inputs for build api
  bitbake: webhob: generic view for multiple models
  bitbake: webhob: improve validation code flow
  bitbake: webhob: add more models to webhob API
  bitbake: webhob: add search for build model
  bitbake: webhob: fix ordering issue

 bitbake/bin/webhob                                 |  145 +
 bitbake/lib/bb/cooker.py                           |    2 +
 bitbake/lib/bb/ui/buildinfohelper.py               |  678 ++
 bitbake/lib/bb/ui/dsi.py                           |  569 ++
 bitbake/lib/webhob/__init__.py                     |    0
 bitbake/lib/webhob/bldviewer/__init__.py           |    0
 bitbake/lib/webhob/bldviewer/api.py                |   19 +
 .../lib/webhob/bldviewer/static/css/bootstrap.css  | 4797 +++++++++++
 .../lib/webhob/bldviewer/static/js/bootstrap.js    | 1982 +++++
 .../lib/webhob/bldviewer/static/js/jquery-2.0.3.js | 8829 ++++++++++++++++++++
 bitbake/lib/webhob/bldviewer/templates/base.html   |   30 +
 .../webhob/bldviewer/templates/basebuildpage.html  |   17 +
 .../lib/webhob/bldviewer/templates/basetable.html  |   46 +
 bitbake/lib/webhob/bldviewer/templates/bfile.html  |   24 +
 .../lib/webhob/bldviewer/templates/bpackage.html   |   44 +
 bitbake/lib/webhob/bldviewer/templates/build.html  |   45 +
 .../webhob/bldviewer/templates/configuration.html  |   20 +
 bitbake/lib/webhob/bldviewer/templates/layer.html  |   34 +
 .../lib/webhob/bldviewer/templates/package.html    |   36 +
 bitbake/lib/webhob/bldviewer/templates/recipe.html |   54 +
 bitbake/lib/webhob/bldviewer/templates/task.html   |   63 +
 .../lib/webhob/bldviewer/templatetags/__init__.py  |    0
 .../webhob/bldviewer/templatetags/projecttags.py   |    8 +
 bitbake/lib/webhob/bldviewer/urls.py               |   14 +
 bitbake/lib/webhob/bldviewer/views.py              |  220 +
 bitbake/lib/webhob/manage.py                       |   10 +
 bitbake/lib/webhob/orm/__init__.py                 |    0
 bitbake/lib/webhob/orm/models.py                   |  242 +
 bitbake/lib/webhob/orm/tests.py                    |   16 +
 bitbake/lib/webhob/orm/views.py                    |    1 +
 bitbake/lib/webhob/whbgui/__init__.py              |    0
 bitbake/lib/webhob/whbgui/static/images/yocto.jpg  |  Bin 0 -> 6582 bytes
 bitbake/lib/webhob/whbgui/templates/index.html     |   13 +
 bitbake/lib/webhob/whbgui/tests.py                 |   16 +
 bitbake/lib/webhob/whbgui/urls.py                  |    9 +
 bitbake/lib/webhob/whbgui/views.py                 |    8 +
 bitbake/lib/webhob/whbmain/__init__.py             |    0
 bitbake/lib/webhob/whbmain/settings.py             |  173 +
 bitbake/lib/webhob/whbmain/urls.py                 |   22 +
 bitbake/lib/webhob/whbmain/wsgi.py                 |   32 +
 40 files changed, 18218 insertions(+)
 create mode 100755 bitbake/bin/webhob
 create mode 100644 bitbake/lib/bb/ui/buildinfohelper.py
 create mode 100644 bitbake/lib/bb/ui/dsi.py
 create mode 100644 bitbake/lib/webhob/__init__.py
 create mode 100644 bitbake/lib/webhob/bldviewer/__init__.py
 create mode 100644 bitbake/lib/webhob/bldviewer/api.py
 create mode 100644 bitbake/lib/webhob/bldviewer/static/css/bootstrap.css
 create mode 100644 bitbake/lib/webhob/bldviewer/static/js/bootstrap.js
 create mode 100644 bitbake/lib/webhob/bldviewer/static/js/jquery-2.0.3.js
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/base.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/basebuildpage.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/basetable.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/bfile.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/bpackage.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/build.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/configuration.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/layer.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/package.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/recipe.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templates/task.html
 create mode 100644 bitbake/lib/webhob/bldviewer/templatetags/__init__.py
 create mode 100644 bitbake/lib/webhob/bldviewer/templatetags/projecttags.py
 create mode 100644 bitbake/lib/webhob/bldviewer/urls.py
 create mode 100644 bitbake/lib/webhob/bldviewer/views.py
 create mode 100755 bitbake/lib/webhob/manage.py
 create mode 100644 bitbake/lib/webhob/orm/__init__.py
 create mode 100644 bitbake/lib/webhob/orm/models.py
 create mode 100644 bitbake/lib/webhob/orm/tests.py
 create mode 100644 bitbake/lib/webhob/orm/views.py
 create mode 100644 bitbake/lib/webhob/whbgui/__init__.py
 create mode 100644 bitbake/lib/webhob/whbgui/static/images/yocto.jpg
 create mode 100644 bitbake/lib/webhob/whbgui/templates/index.html
 create mode 100644 bitbake/lib/webhob/whbgui/tests.py
 create mode 100644 bitbake/lib/webhob/whbgui/urls.py
 create mode 100644 bitbake/lib/webhob/whbgui/views.py
 create mode 100644 bitbake/lib/webhob/whbmain/__init__.py
 create mode 100644 bitbake/lib/webhob/whbmain/settings.py
 create mode 100644 bitbake/lib/webhob/whbmain/urls.py
 create mode 100644 bitbake/lib/webhob/whbmain/wsgi.py

-- 
1.8.1.2



^ permalink raw reply	[flat|nested] 96+ messages in thread

* [PATCH 81/94] bitbake: webhob: better help message in startup script
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (78 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 80/94] bitbake: dsi: add exception dumping code Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 82/94] bitbake: dsi: add build package dependency recording Alex DAMIAN
                   ` (14 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Improved the error during stop message in the
startup script as the previous message was confusing
to the users.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/bin/webhob | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/bitbake/bin/webhob b/bitbake/bin/webhob
index 0c4d9ea..7bc9759 100755
--- a/bitbake/bin/webhob
+++ b/bitbake/bin/webhob
@@ -104,7 +104,14 @@ if [ ${CMD} == "start" ] && ( [ $lock -eq 0 ] || [ -e $BUILDDIR/whbmain.pid ] );
     return 3
 elif [ ${CMD} == "stop" ] && ( [ $lock -eq 1 ] || ! [ -e $BUILDDIR/whbmain.pid ] ) ; then
     echo "Error: bitbake lock state error. Trying to stop a stopped system ?
-manually stop system with bitbake -m / webserverKill" 2>&1
+If you think the system is hanged up, you can try to manually stop system with the commands
+
+# BBSERVER=localhost:8200 bitbake -m
+
+and
+
+# webserverKillAll
+" 2>&1
     return 3
 fi
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 82/94] bitbake: dsi: add build package dependency recording
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (79 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 81/94] bitbake: webhob: better help message in startup script Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 83/94] bitbake: webhob: clean up Machine table Alex DAMIAN
                   ` (13 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding code to store all types of package dependencies
that a build time package can have.

Added code to visualise the dependency listing in
the Simple interface.

Addded code to record content size for the
build time packages.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py               | 33 ++++++++++++++++++++++
 .../lib/webhob/bldviewer/templates/bpackage.html   | 13 +++++++--
 bitbake/lib/webhob/orm/models.py                   | 16 ++++++++---
 3 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index cf947da..30aa098 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -166,6 +166,7 @@ class ORMWrapper(object):
                                        revision = package_info['PKGR'],
                                        summary = package_info['SUMMARY'],
                                        description = package_info['DESCRIPTION'],
+                                       size = package_info['PKGSIZE'],
                                        section = package_info['SECTION'],
                                        license = package_info['LICENSE'],
                                        )
@@ -177,6 +178,38 @@ class ORMWrapper(object):
                                         size = size )
             del files[bp_object.name]
 
+        # save soft dependency information
+        if package_info['RDEPENDS'] is not None:
+            for p in package_info['RDEPENDS'].split(" "):
+                if not p.startswith("("):
+                    Build_Package_Dependency.objects.get_or_create( package = bp_object,
+                        depends_on = p, dep_type = Build_Package_Dependency.TYPE_RDEPENDS)
+        if package_info['RDEPENDS'] is not None:
+            for p in package_info['RPROVIDES'].split(" "):
+                if not p.startswith("("):
+                    Build_Package_Dependency.objects.get_or_create( package = bp_object,
+                        depends_on = p, dep_type = Build_Package_Dependency.TYPE_RPROVIDES)
+        if package_info['RDEPENDS'] is not None:
+            for p in package_info['RRECOMMENDS'].split(" "):
+                if not p.startswith("("):
+                    Build_Package_Dependency.objects.get_or_create( package = bp_object,
+                        depends_on = p, dep_type = Build_Package_Dependency.TYPE_RRECOMMENDS)
+        if package_info['RDEPENDS'] is not None:
+            for p in package_info['RSUGGESTS'].split(" "):
+                if not p.startswith("("):
+                    Build_Package_Dependency.objects.get_or_create( package = bp_object,
+                        depends_on = p, dep_type = Build_Package_Dependency.TYPE_RSUGGESTS)
+        if package_info['RDEPENDS'] is not None:
+            for p in package_info['RREPLACES'].split(" "):
+                if not p.startswith("("):
+                    Build_Package_Dependency.objects.get_or_create( package = bp_object,
+                        depends_on = p, dep_type = Build_Package_Dependency.TYPE_RREPLACES)
+        if package_info['RDEPENDS'] is not None:
+            for p in package_info['RCONFLICTS'].split(" "):
+                if not p.startswith("("):
+                    Build_Package_Dependency.objects.get_or_create( package = bp_object,
+                        depends_on = p, dep_type = Build_Package_Dependency.TYPE_RCONFLICTS)
+
         return bp_object
 
     def save_build_variables(self, build_obj, vardump):
diff --git a/bitbake/lib/webhob/bldviewer/templates/bpackage.html b/bitbake/lib/webhob/bldviewer/templates/bpackage.html
index 91e5f6f..46a256c 100644
--- a/bitbake/lib/webhob/bldviewer/templates/bpackage.html
+++ b/bitbake/lib/webhob/bldviewer/templates/bpackage.html
@@ -13,7 +13,9 @@
             <th>Summary</th>
             <th>Section</th>
             <th>Description</th>
+            <th>Size</th>
             <th>License</th>
+            <th>Dependencies List (all)</th>
             </tr>
 
             {% for package in packages %}
@@ -22,12 +24,19 @@
                 <td><a name="#{{package.name}}" href="/simple/build/{{build.pk}}/package/{{package.pk}}/files/">{{package.name}} ({{package.filelist_bpackage.count}} files)</a></td>
                 <td>{{package.version}}-{{package.revision}}</td>
                 <td><a href="/simple/layerversions/{{package.recipe.layer_version_id}}/recipes/#{{package.recipe.name}}">{{package.recipe.name}}</a>{{package.package_name}}</a></td>
-                
+
             <td>{{package.summary}}</td>
             <td>{{package.section}}</td>
             <td>{{package.description}}</td>
+            <td>{{package.size}}</td>
             <td>{{package.license}}</td>
-
+            <td>
+        <div style="height: 3em">
+            {% for bpd in package.bpackage_dependencies_package.all %}
+                {{bpd.dep_type}}: {{bpd.depends_on}} <br/>
+            {% endfor %}
+        </div>
+            </td>
             {% endfor %}
 
     {% endif %}
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 7ce0d1f..2018020 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -123,14 +123,22 @@ class Build_Package(models.Model):
     license = models.CharField(max_length=80, null=True)
 
 class Build_Package_Dependency(models.Model):
-    TYPE_DEPENDS = 0
-    TYPE_RDEPENDS = 1
+    TYPE_RDEPENDS = 0
+    TYPE_RPROVIDES = 1
+    TYPE_RRECOMMENDS = 2
+    TYPE_RSUGGESTS = 3
+    TYPE_RREPLACES = 4
+    TYPE_RCONFLICTS = 5
     DEPENDS_TYPE = (
-        (TYPE_DEPENDS, "depends"),
         (TYPE_RDEPENDS, "rdepends"),
+        (TYPE_RPROVIDES, "rprovides"),
+        (TYPE_RRECOMMENDS, "rrecommends"),
+        (TYPE_RSUGGESTS, "rsuggests"),
+        (TYPE_RREPLACES, "rreplaces"),
+        (TYPE_RCONFLICTS, "rconflicts"),
     )
     package = models.ForeignKey(Build_Package, related_name='bpackage_dependencies_package')
-    depends_on = models.ForeignKey(Build_Package, related_name='bpackage_dependencies_depends')
+    depends_on = models.CharField(max_length=100)   # soft dependency
     dep_type = models.IntegerField(choices=DEPENDS_TYPE)
 
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 83/94] bitbake: webhob: clean up Machine table
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (80 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 82/94] bitbake: dsi: add build package dependency recording Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 84/94] bitbake: webhob: update API endpoints Alex DAMIAN
                   ` (12 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Removing the Machine table in the favour of a column
in the Build table since it complicated the queries without
any benefit in reduced data structure.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py              | 26 +++++------------------
 bitbake/lib/webhob/bldviewer/templates/build.html |  2 +-
 bitbake/lib/webhob/orm/models.py                  |  9 ++------
 3 files changed, 8 insertions(+), 29 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 30aa098..b5c2dc0 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -7,7 +7,7 @@ import re
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webhob.whbmain.settings")
 
 import webhob.whbmain.settings as whb_django_settings
-from webhob.orm.models import Machine, Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage
+from webhob.orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage
 from webhob.orm.models import Target_Package, Build_Package, Variable, Build_File
 from webhob.orm.models import Task_Dependency, Build_Package_Dependency, Target_Package_Dependency, Recipe_Dependency
 from bb.msg import BBLogFormatter as format
@@ -21,12 +21,6 @@ class ORMWrapper(object):
     def __init__(self):
         pass
 
-    def create_machine_object(self, machine_information):
-
-        machine = Machine.objects.get_or_create(name=machine_information['name'],
-                                                description=machine_information['description'])
-
-        return machine[0]
 
     def create_build_object(self, build_info):
 
@@ -244,13 +238,6 @@ class BuildInfoHelper(object):
     ###################
     ## methods to convert event/external info into objects that the ORM layer uses
 
-    def _get_machine_information(self):
-        machine_info = {}
-        machine_info['name'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
-        machine_info['description'] = 'Not Available'
-        self.internal_state['machine'] = machine_info['name']
-        return machine_info
-
     def _get_layer_dict(self, layer_path):
 
         layer_info = {}
@@ -313,11 +300,11 @@ class BuildInfoHelper(object):
         return "<unknown>"
 
 
-    def _get_build_information(self, machine_obj):
+    def _get_build_information(self):
         build_info = {}
         # Generate an identifier for each new build
 
-        build_info['machine'] = machine_obj
+        build_info['machine'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
         build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
         build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
         build_info['started_on'] = datetime.datetime.now()
@@ -375,7 +362,7 @@ class BuildInfoHelper(object):
 
         for t in self.internal_state['targets']:
             target = t.target
-            machine = self.internal_state['build'].machine.name
+            machine = self.internal_state['build'].machine
             buildname = self.internal_state['build'].build_name
             package = task_object.recipe.name + "-" + task_object.recipe.version.strip(":")
 
@@ -428,10 +415,7 @@ class BuildInfoHelper(object):
 
     def store_started_build(self, event):
 
-        machine_information = self._get_machine_information()
-        machine_obj = self.orm_wrapper.create_machine_object(machine_information)
-
-        build_information = self._get_build_information(machine_obj)
+        build_information = self._get_build_information()
 
         build_obj = self.orm_wrapper.create_build_object(build_information)
         self.internal_state['build'] = build_obj
diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index eed81a3..dd0e5c0 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -29,7 +29,7 @@
             <td>{{build.completed_on}}</td>
             <td>{% for t in build.target_set.all %}<a href="/simple/build/{{build.id}}/target/{{t.id}}">{{t.target}}</a><br/>{% endfor %}</td>
             <td>{% if build.is_image %} <a href="/simple/build/{{build.id}}/package/">{{build.is_image}}</a>{% else %} {{build.is_image}} {% endif %}</td>
-            <td>{{build.machine.name}}</td>
+            <td>{{build.machine}}</td>
             <td>{% time_difference build.started_on build.completed_on %}</td>
             <td>{{build.errors_no}}:{% if  build.errors_no %}{% for error in logs %}{% if error.build == build %}{% if error.level == 2 %}<p>{{error.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
             <td>{{build.warnings_no}}:{% if  build.warnings_no %}{% for warning in logs %}{% if warning.build == build %}{% if warning.level == 1 %}<p>{{warning.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
diff --git a/bitbake/lib/webhob/orm/models.py b/bitbake/lib/webhob/orm/models.py
index 2018020..66cbdc8 100644
--- a/bitbake/lib/webhob/orm/models.py
+++ b/bitbake/lib/webhob/orm/models.py
@@ -13,10 +13,10 @@ class Build(models.Model):
         (IN_PROGRESS, 'In Progress'),
     )
 
-    search_allowed_fields = ['machine__name',
+    search_allowed_fields = ['machine',
                              'cooker_log_path']
 
-    machine = models.ForeignKey('Machine', related_name='build_machine')
+    machine = models.CharField(max_length=100)
     distro = models.CharField(max_length=100)
     distro_version = models.CharField(max_length=100)
     started_on = models.DateTimeField()
@@ -226,11 +226,6 @@ class Variable(models.Model):
     description = models.TextField(null=True)
 
 
-class Machine(models.Model):
-    name = models.CharField(max_length=100)
-    description = models.TextField()
-
-
 class LogMessage(models.Model):
     INFO = 0
     WARNING = 1
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 84/94] bitbake: webhob: update API endpoints
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (81 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 83/94] bitbake: webhob: clean up Machine table Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 85/94] bitbake: webhob: record task hash information Alex DAMIAN
                   ` (11 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Updating the API endpoints as described by the documentation.

The documentation updated here:
https://wiki.yoctoproject.org/wiki/REST_API_Contracts

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/api.py   | 20 ++++++++++++++------
 bitbake/lib/webhob/bldviewer/views.py | 15 ++++++++++++---
 2 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/api.py b/bitbake/lib/webhob/bldviewer/api.py
index 9fa701c..2e58313 100644
--- a/bitbake/lib/webhob/bldviewer/api.py
+++ b/bitbake/lib/webhob/bldviewer/api.py
@@ -2,10 +2,18 @@ from django.conf.urls import patterns, include, url
 
 
 urlpatterns = patterns('bldviewer.views',
-        url(r'^builds/$', 'model_explorer',  {'model_name':'build'}, name='builds'),
-        url(r'^tasks/$', 'model_explorer', {'model_name':'task'}, name='task'),
-        url(r'^packages/$', 'model_explorer', {'model_name':'package'}, name='package'),
-        url(r'^layers/$', 'model_explorer', {'model_name':'layer'}, name='layer'),
-        url(r'^recipes/$', 'model_explorer', {'model_name':'recipe'}, name='recipe'),
-        url(r'^layersversions/$', 'model_explorer', {'model_name':'layerversion'}, name='layerversion'),
+        url(r'^builds$', 'model_explorer',  {'model_name':'build'}, name='builds'),
+        url(r'^targets$', 'model_explorer',  {'model_name':'target'}, name='targets'),
+        url(r'^tasks$', 'model_explorer', {'model_name':'task'}, name='task'),
+        url(r'^task_dependencies$', 'model_explorer',  {'model_name':'task_dependency'}, name='task_dependencies'),
+        url(r'^packages$', 'model_explorer',  {'model_name':'build_package'}, name='build_packages'),
+        url(r'^package_dependencies$', 'model_explorer',  {'model_name':'build_package_dependency'}, name='build_package_dependencies'),
+        url(r'^target_packages$', 'model_explorer',  {'model_name':'target_package'}, name='target_packages'),
+        url(r'^package_files$', 'model_explorer',  {'model_name':'build_file'}, name='build_files'),
+        url(r'^layers$', 'model_explorer', {'model_name':'layer'}, name='layer'),
+        url(r'^layerversions$', 'model_explorer', {'model_name':'layerversion'}, name='layerversion'),
+        url(r'^recipes$', 'model_explorer', {'model_name':'recipe'}, name='recipe'),
+        url(r'^recipe_dependencies$', 'model_explorer',  {'model_name':'recipe_dependency'}, name='recipe_dependencies'),
+        url(r'^variables$', 'model_explorer',  {'model_name':'variable'}, name='variables'),
+        url(r'^logmessages$', 'model_explorer',  {'model_name':'logmessage'}, name='logmessages'),
 )
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 4ff23d3..98099ee 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -2,8 +2,8 @@ import operator
 
 from django.db.models import Q
 from django.shortcuts import render
-from orm.models import Build, Task, Layer, Layer_Version, Recipe, Target_Package, LogMessage, Variable
-from orm.models import Task_Dependency, Target_Package_Dependency, Build_Package, Build_File
+from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, Target_Package, LogMessage, Variable
+from orm.models import Task_Dependency, Recipe_Dependency, Build_Package, Build_File, Build_Package_Dependency
 from django.views.decorators.cache import cache_control
 
 @cache_control(no_store=True)
@@ -95,11 +95,20 @@ def model_explorer(request, model_name):
     response_data = {}
     model_mapping = {
         'build': Build,
+        'target': Target,
+        'target_package': Target_Package,
         'task': Task,
-        'package': Target_Package,
+        'task_dependency': Task_Dependency,
+        'package': Build_Package,
         'layer': Layer,
         'layerversion': Layer_Version,
         'recipe': Recipe,
+        'recipe_dependency': Recipe_Dependency,
+        'build_package': Build_Package,
+        'build_package_dependency': Build_Package_Dependency,
+        'build_file': Build_File,
+        'variable': Variable,
+        'logmessage': LogMessage,
         }
 
     if model_name not in model_mapping.keys():
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 85/94] bitbake: webhob: record task hash information
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (82 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 84/94] bitbake: webhob: update API endpoints Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 86/94] bitbake: cooker: add the inherits attribute to the dependency tree Alex DAMIAN
                   ` (10 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We record the task hash for all queue tasks.

Displaying the task hash in the Simple interface.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py             | 5 +++++
 bitbake/lib/webhob/bldviewer/templates/task.html | 2 ++
 2 files changed, 7 insertions(+)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index b5c2dc0..4ffa0d9 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -324,6 +324,11 @@ class BuildInfoHelper(object):
         task_information['outcome'] = Task.OUTCOME_NA
         task_information['recipe'] = recipe
         task_information['task_name'] = event.taskname
+        try:
+            # some tasks don't come with a hash. and that's ok
+            task_information['sstate_checksum'] = event.taskhash
+        except AttributeError:
+            pass
         return task_information
 
     def _get_layer_version_for_path(self, path):
diff --git a/bitbake/lib/webhob/bldviewer/templates/task.html b/bitbake/lib/webhob/bldviewer/templates/task.html
index 65d75cb..25715fe 100644
--- a/bitbake/lib/webhob/bldviewer/templates/task.html
+++ b/bitbake/lib/webhob/bldviewer/templates/task.html
@@ -11,6 +11,7 @@
             <th>Task</th>
             <th>Recipe Version</th>
             <th>Task Type</th>
+            <th>Checksum</th>
             <th>Outcome</th>
             <th>Message</th>
             <th>Logfile</th>
@@ -36,6 +37,7 @@
                 <td>Prebuilt</td>
                 {% endif %}
 
+                <td>{{task.sstate_checksum}}</td>
                 <td>{{task.get_outcome_display}}</td>
                 <td><p>{{task.message}}</td>
                 <td><a target="_fileview" href="file:///{{task.logfile}}">{{task.logfile}}</a></td>
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 86/94] bitbake: cooker: add the inherits attribute to the dependency tree
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (83 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 85/94] bitbake: webhob: record task hash information Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 87/94] bitbake: webhob: Clean up links in Simple UI Alex DAMIAN
                   ` (9 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

The built dependency tree has to have the inherits attribute
so that the UIs can use and save this information.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/cooker.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py
index ff2af69..9ed22c7 100644
--- a/bitbake/lib/bb/cooker.py
+++ b/bitbake/lib/bb/cooker.py
@@ -481,6 +481,7 @@ class BBCooker:
                 depend_tree["pn"][pn] = {}
                 depend_tree["pn"][pn]["filename"] = fn
                 depend_tree["pn"][pn]["version"] = version
+                depend_tree["pn"][pn]["inherits"] = self.recipecache.inherits.get(fn, None)
 
                 # if we have extra caches, list all attributes they bring in
                 extra_info = []
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 87/94] bitbake: webhob: Clean up links in Simple UI
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (84 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 86/94] bitbake: cooker: add the inherits attribute to the dependency tree Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 88/94] bitbake: dsi: read correct recipe description field Alex DAMIAN
                   ` (8 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch fixes 404s and 500 errors coming up on
a spidering of the UI simple interface.

Test used:  wget -r -l 6 http://localhost:8000/

For log, look up build/whbmain.log file.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/build.html | 2 +-
 bitbake/lib/webhob/bldviewer/views.py             | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/build.html b/bitbake/lib/webhob/bldviewer/templates/build.html
index dd0e5c0..bd7e0f3 100644
--- a/bitbake/lib/webhob/bldviewer/templates/build.html
+++ b/bitbake/lib/webhob/bldviewer/templates/build.html
@@ -27,7 +27,7 @@
             <td><a href="/simple/build/{{build.id}}/configuration/">{{build.get_outcome_display}}</a></td>
             <td>{{build.started_on}}</td>
             <td>{{build.completed_on}}</td>
-            <td>{% for t in build.target_set.all %}<a href="/simple/build/{{build.id}}/target/{{t.id}}">{{t.target}}</a><br/>{% endfor %}</td>
+            <td>{% for t in build.target_set.all %}<a href="/simple/build/{{build.id}}/target/{{t.id}}/packages/">{{t.target}}</a><br/>{% endfor %}</td>
             <td>{% if build.is_image %} <a href="/simple/build/{{build.id}}/package/">{{build.is_image}}</a>{% else %} {{build.is_image}} {% endif %}</td>
             <td>{{build.machine}}</td>
             <td>{% time_difference build.started_on build.completed_on %}</td>
diff --git a/bitbake/lib/webhob/bldviewer/views.py b/bitbake/lib/webhob/bldviewer/views.py
index 98099ee..b7bac73 100644
--- a/bitbake/lib/webhob/bldviewer/views.py
+++ b/bitbake/lib/webhob/bldviewer/views.py
@@ -52,7 +52,6 @@ def tpackage(request, build_id, target_id):
     template = 'package.html'
 
     packages = Target_Package.objects.filter(target=target_id)
-    package_depends = Target_Package_Dependency.objects.filter(package__in=packages)
 
     context = {'build' : Build.objects.filter(pk=build_id)[0],'packages': packages}
 
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 88/94] bitbake: dsi: read correct recipe description field
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (85 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 87/94] bitbake: webhob: Clean up links in Simple UI Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 89/94] bitbake: cooker: send layer information with dependency tree Alex DAMIAN
                   ` (7 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Fixing reading the recipe description field.

[YOCTO #5010]

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 4ffa0d9..7f0086e 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -568,7 +568,7 @@ class BuildInfoHelper(object):
             recipe_info['layer_version'] = layer_version_obj
             recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
             recipe_info['license'] = event._depgraph['pn'][pn]['license']
-            recipe_info['description'] = event._depgraph['pn'][pn]['summary']
+            recipe_info['description'] = event._depgraph['pn'][pn]['description']
             recipe_info['section'] = event._depgraph['pn'][pn]['section']
             recipe_info['licensing_info'] = 'Not Available'
             recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 89/94] bitbake: cooker: send layer information with dependency tree
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (86 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 88/94] bitbake: dsi: read correct recipe description field Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:52 ` [PATCH 90/94] bitbake: webhob: store and display layer priorities Alex DAMIAN
                   ` (6 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding layer priorities information to the dependency tree.
This is needed because the layer priorities are not computed
until runtime, and priority information is needed for recording
correct recipe information.

[YOCTO #5218]

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/cooker.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py
index 9ed22c7..a978f92 100644
--- a/bitbake/lib/bb/cooker.py
+++ b/bitbake/lib/bb/cooker.py
@@ -470,6 +470,7 @@ class BBCooker:
         depend_tree["packages"] = {}
         depend_tree["rdepends-pkg"] = {}
         depend_tree["rrecs-pkg"] = {}
+        depend_tree["layer-priorities"] = self.recipecache.bbfile_config_priorities
 
         for task in xrange(len(rq.rqdata.runq_fnid)):
             taskname = rq.rqdata.runq_task[task]
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 90/94] bitbake: webhob: store and display layer priorities
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (87 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 89/94] bitbake: cooker: send layer information with dependency tree Alex DAMIAN
@ 2013-09-24 16:52 ` Alex DAMIAN
  2013-09-24 16:53 ` [PATCH 91/94] bitbake: webhob: collect recipe licensing info Alex DAMIAN
                   ` (5 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:52 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Recording layer version priorities based on the information
sent with the dependency tree. The layer versions are already
created, just need to update the layer priority info.

Modifying the Simple interface to display the layer priorities.

[YOCTO #5218]

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py               | 7 +++++++
 bitbake/lib/webhob/bldviewer/templates/layer.html  | 2 +-
 bitbake/lib/webhob/bldviewer/templates/recipe.html | 2 +-
 3 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 7f0086e..c8820a5 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -547,6 +547,13 @@ class BuildInfoHelper(object):
 
 
     def store_dependency_information(self, event):
+        # save layer version priorities
+        for lv in event._depgraph['layer-priorities']:
+            (name, path, regexp, priority) = lv
+            layer_version_obj = self._get_layer_version_for_path(path[1:]) # paths start with a ^
+            assert layer_version_obj is not None
+            layer_version_obj.priority = priority
+            layer_version_obj.save()
 
         # save build time package information
         self.internal_state['bldpkgs'] = {}
diff --git a/bitbake/lib/webhob/bldviewer/templates/layer.html b/bitbake/lib/webhob/bldviewer/templates/layer.html
index 508f254..374a0f9 100644
--- a/bitbake/lib/webhob/bldviewer/templates/layer.html
+++ b/bitbake/lib/webhob/bldviewer/templates/layer.html
@@ -23,7 +23,7 @@
             <td><table>
             {% for lv in layer.versions %}
                 <tr><td>
-        <a href="/simple/layerversions/{{lv.id}}/recipes">{{lv.branch}}:{{lv.commit}} ({{lv.count}} recipes)</a>
+        <a href="/simple/layerversions/{{lv.id}}/recipes">({{lv.priority}}){{lv.branch}}:{{lv.commit}} ({{lv.count}} recipes)</a>
                 </td></tr>
             {% endfor %}
             </table></td>
diff --git a/bitbake/lib/webhob/bldviewer/templates/recipe.html b/bitbake/lib/webhob/bldviewer/templates/recipe.html
index e81883f..ec8a46d 100644
--- a/bitbake/lib/webhob/bldviewer/templates/recipe.html
+++ b/bitbake/lib/webhob/bldviewer/templates/recipe.html
@@ -2,7 +2,7 @@
 
 {% block pagename %}
 <ul class="nav nav-tabs" style="display: inline-block">
-  <li><a>Layer  {{layer_version.layer.name}}&nbsp;:&nbsp;{{layer_version.branch}}&nbsp;:&nbsp;{{layer_version.commit}}</a></li>
+  <li><a>Layer  {{layer_version.layer.name}}&nbsp;:&nbsp;{{layer_version.branch}}&nbsp;:&nbsp;{{layer_version.commit}}&nbsp;:&nbsp;{{layer_version.priority}}</a></li>
 </ul>
     <h1>Toaster - Recipes for a Layer</h1>
 {% endblock %}
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 91/94] bitbake: webhob: collect recipe licensing info
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (88 preceding siblings ...)
  2013-09-24 16:52 ` [PATCH 90/94] bitbake: webhob: store and display layer priorities Alex DAMIAN
@ 2013-09-24 16:53 ` Alex DAMIAN
  2013-09-24 16:53 ` [PATCH 92/94] bitbake: webhob: Simple UI interface CSS fix Alex DAMIAN
                   ` (4 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:53 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Collecting licensing info from log data.
The logs are generated in insane.bbclass.

Modified Simple interface to display licensing file.

[YOCTO #5194]

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py               | 7 +++++++
 bitbake/lib/bb/ui/dsi.py                           | 2 +-
 bitbake/lib/webhob/bldviewer/templates/recipe.html | 2 ++
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index c8820a5..a3a50a3 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -654,6 +654,13 @@ class BuildInfoHelper(object):
         self.orm_wrapper.create_logmessage(log_information)
 
     def store_log_event(self, event):
+        # look up license files info from insane.bbclass
+        m = re.match("([^:]*): md5 checksum matched for ([^;]*)", event.msg)
+        if m:
+            (pn, fn) = m.groups()
+            self.internal_state['recipes'][pn].licensing_info = fn
+            self.internal_state['recipes'][pn].save()
+
         if event.levelno < format.WARNING:
             return
         if not 'build' in self.internal_state:
diff --git a/bitbake/lib/bb/ui/dsi.py b/bitbake/lib/bb/ui/dsi.py
index 22cff5d..434a6dd 100644
--- a/bitbake/lib/bb/ui/dsi.py
+++ b/bitbake/lib/bb/ui/dsi.py
@@ -322,6 +322,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 continue
 
             if isinstance(event, logging.LogRecord):
+                buildinfohelper.store_log_event(event)
                 if event.levelno >= format.ERROR:
                     errors = errors + 1
                     return_value = 1
@@ -333,7 +334,6 @@ def main(server, eventHandler, params, tf = TerminalFilter):
                 if event.taskpid != 0 and event.levelno <= format.NOTE:
                     continue
 
-                buildinfohelper.store_log_event(event)
                 logger.handle(event)
                 continue
 
diff --git a/bitbake/lib/webhob/bldviewer/templates/recipe.html b/bitbake/lib/webhob/bldviewer/templates/recipe.html
index ec8a46d..a624370 100644
--- a/bitbake/lib/webhob/bldviewer/templates/recipe.html
+++ b/bitbake/lib/webhob/bldviewer/templates/recipe.html
@@ -18,6 +18,7 @@
             <th>Description</th>
             <th>Section</th>
             <th>License</th>
+            <th>License file</th>
             <th>Homepage</th>
             <th>Bugtracker</th>
             <th>Author</th>
@@ -34,6 +35,7 @@
             <td>{{recipe.description}}</td>
             <td>{{recipe.section}}</td>
             <td>{{recipe.license}}</td>
+            <td>{{recipe.licensing_info}}</td>
             <td>{{recipe.homepage}}</td>
             <td>{{recipe.bugtracker}}</td>
             <td>{{recipe.author}}</td>
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 92/94] bitbake: webhob: Simple UI interface CSS fix
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (89 preceding siblings ...)
  2013-09-24 16:53 ` [PATCH 91/94] bitbake: webhob: collect recipe licensing info Alex DAMIAN
@ 2013-09-24 16:53 ` Alex DAMIAN
  2013-09-24 16:53 ` [PATCH 93/94] bitbake: dsi: fix typos Alex DAMIAN
                   ` (3 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:53 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

In the build packages page of the Simple interface,
setting overflow:auto to be able to scroll the
dependency information area.

[YOCTO #5196]

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/bldviewer/templates/bpackage.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bitbake/lib/webhob/bldviewer/templates/bpackage.html b/bitbake/lib/webhob/bldviewer/templates/bpackage.html
index 46a256c..39190d8 100644
--- a/bitbake/lib/webhob/bldviewer/templates/bpackage.html
+++ b/bitbake/lib/webhob/bldviewer/templates/bpackage.html
@@ -31,7 +31,7 @@
             <td>{{package.size}}</td>
             <td>{{package.license}}</td>
             <td>
-        <div style="height: 3em">
+        <div style="height: 3em; overflow:auto">
             {% for bpd in package.bpackage_dependencies_package.all %}
                 {{bpd.dep_type}}: {{bpd.depends_on}} <br/>
             {% endfor %}
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 93/94] bitbake: dsi: fix typos
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (90 preceding siblings ...)
  2013-09-24 16:53 ` [PATCH 92/94] bitbake: webhob: Simple UI interface CSS fix Alex DAMIAN
@ 2013-09-24 16:53 ` Alex DAMIAN
  2013-09-24 16:53 ` [PATCH 94/94] bitbake: webhob: do not set timezones Alex DAMIAN
                   ` (2 subsequent siblings)
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:53 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Some copy-pasted code didn't get pasted right,
so we fix some of the typos which crepped out.

This fixes some bugs which aren't logged
in Bugzilla, which caused data transfer to fail
silently.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index a3a50a3..8ead476 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -178,27 +178,27 @@ class ORMWrapper(object):
                 if not p.startswith("("):
                     Build_Package_Dependency.objects.get_or_create( package = bp_object,
                         depends_on = p, dep_type = Build_Package_Dependency.TYPE_RDEPENDS)
-        if package_info['RDEPENDS'] is not None:
+        if package_info['RPROVIDES'] is not None:
             for p in package_info['RPROVIDES'].split(" "):
                 if not p.startswith("("):
                     Build_Package_Dependency.objects.get_or_create( package = bp_object,
                         depends_on = p, dep_type = Build_Package_Dependency.TYPE_RPROVIDES)
-        if package_info['RDEPENDS'] is not None:
+        if package_info['RRECOMMENDS'] is not None:
             for p in package_info['RRECOMMENDS'].split(" "):
                 if not p.startswith("("):
                     Build_Package_Dependency.objects.get_or_create( package = bp_object,
                         depends_on = p, dep_type = Build_Package_Dependency.TYPE_RRECOMMENDS)
-        if package_info['RDEPENDS'] is not None:
+        if package_info['RSUGGESTS'] is not None:
             for p in package_info['RSUGGESTS'].split(" "):
                 if not p.startswith("("):
                     Build_Package_Dependency.objects.get_or_create( package = bp_object,
                         depends_on = p, dep_type = Build_Package_Dependency.TYPE_RSUGGESTS)
-        if package_info['RDEPENDS'] is not None:
+        if package_info['RREPLACES'] is not None:
             for p in package_info['RREPLACES'].split(" "):
                 if not p.startswith("("):
                     Build_Package_Dependency.objects.get_or_create( package = bp_object,
                         depends_on = p, dep_type = Build_Package_Dependency.TYPE_RREPLACES)
-        if package_info['RDEPENDS'] is not None:
+        if package_info['RCONFLICTS'] is not None:
             for p in package_info['RCONFLICTS'].split(" "):
                 if not p.startswith("("):
                     Build_Package_Dependency.objects.get_or_create( package = bp_object,
@@ -643,7 +643,7 @@ class BuildInfoHelper(object):
         if not 'package_files' in self.internal_state.keys():
             self.internal_state['package_files'] = {}
 
-        data = event._data
+        data = event.data
         self.internal_state['package_files'][data['PKG']] = data['FILES']
 
     def store_log_information(self, level, text):
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 94/94] bitbake: webhob: do not set timezones
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (91 preceding siblings ...)
  2013-09-24 16:53 ` [PATCH 93/94] bitbake: dsi: fix typos Alex DAMIAN
@ 2013-09-24 16:53 ` Alex DAMIAN
  2013-09-24 16:53 ` [PATCH 61/94] bitbake: bldviewer: add jquery and bootstrap frameworks Alex DAMIAN
  2013-09-24 17:14 ` [PATCH 00/94] Webhob patches for DSI and Web modules Paul Eggleton
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:53 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Preventing webhob setting a default timezone.
As such, it will always use the current time
for the local computer.

[YOCTO #5186]

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bitbake/lib/webhob/whbmain/settings.py | 26 +++++++++++++++-----------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/bitbake/lib/webhob/whbmain/settings.py b/bitbake/lib/webhob/whbmain/settings.py
index d470328..c5222e5 100644
--- a/bitbake/lib/webhob/whbmain/settings.py
+++ b/bitbake/lib/webhob/whbmain/settings.py
@@ -20,16 +20,6 @@ DATABASES = {
     }
 }
 
-# If we're using sqlite, we need to tweak the performance a bit
-from django.db.backends.signals import connection_created
-def activate_synchronous_off(sender, connection, **kwargs):
-    if connection.vendor == 'sqlite':
-        cursor = connection.cursor()
-        cursor.execute('PRAGMA synchronous = 0;')
-connection_created.connect(activate_synchronous_off)
-#
-
-
 # Hosts/domain names that are valid for this site; required if DEBUG is False
 # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
 ALLOWED_HOSTS = []
@@ -38,7 +28,9 @@ ALLOWED_HOSTS = []
 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
 # although not all choices may be available on all operating systems.
 # In a Windows environment this must be set to your system time zone.
-TIME_ZONE = 'America/Chicago'
+
+# Always use local computer's time zone
+TIME_ZONE = ''
 
 # Language code for this installation. All choices can be found here:
 # http://www.i18nguy.com/unicode/language-identifiers.html
@@ -54,6 +46,7 @@ USE_I18N = True
 # calendars according to the current locale.
 USE_L10N = True
 
+
 # If you set this to False, Django will not use timezone-aware datetimes.
 USE_TZ = True
 
@@ -167,3 +160,14 @@ LOGGING = {
         },
     }
 }
+
+# If we're using sqlite, we need to tweak the performance a bit
+from django.db.backends.signals import connection_created
+def activate_synchronous_off(sender, connection, **kwargs):
+    if connection.vendor == 'sqlite':
+        cursor = connection.cursor()
+        cursor.execute('PRAGMA synchronous = 0;')
+connection_created.connect(activate_synchronous_off)
+#
+
+
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* [PATCH 61/94] bitbake: bldviewer: add jquery and bootstrap frameworks
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (92 preceding siblings ...)
  2013-09-24 16:53 ` [PATCH 94/94] bitbake: webhob: do not set timezones Alex DAMIAN
@ 2013-09-24 16:53 ` Alex DAMIAN
  2013-09-24 17:14 ` [PATCH 00/94] Webhob patches for DSI and Web modules Paul Eggleton
  94 siblings, 0 replies; 96+ messages in thread
From: Alex DAMIAN @ 2013-09-24 16:53 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We are adding the jQuery and Bootstrap libraries, so
we can add some advanced features to the Simple web interface.

jQuery license is MIT.
Bootstrap license is Apache v2.0

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 .../lib/webhob/bldviewer/static/css/bootstrap.css  | 4797 +++++++++++
 .../lib/webhob/bldviewer/static/js/bootstrap.js    | 1982 +++++
 .../lib/webhob/bldviewer/static/js/jquery-2.0.3.js | 8829 ++++++++++++++++++++
 3 files changed, 15608 insertions(+)
 create mode 100644 bitbake/lib/webhob/bldviewer/static/css/bootstrap.css
 create mode 100644 bitbake/lib/webhob/bldviewer/static/js/bootstrap.js
 create mode 100644 bitbake/lib/webhob/bldviewer/static/js/jquery-2.0.3.js

diff --git a/bitbake/lib/webhob/bldviewer/static/css/bootstrap.css b/bitbake/lib/webhob/bldviewer/static/css/bootstrap.css
new file mode 100644
index 0000000..cb62a54
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/static/css/bootstrap.css
@@ -0,0 +1,4797 @@
+/*!
+ * Bootstrap v3.0.0
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+/*! normalize.css v2.1.0 | MIT License | git.io/normalize */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+  display: block;
+}
+
+audio,
+canvas,
+video {
+  display: inline-block;
+}
+
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+
+[hidden] {
+  display: none;
+}
+
+html {
+  font-family: sans-serif;
+  -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+}
+
+body {
+  margin: 0;
+}
+
+a:focus {
+  outline: thin dotted;
+}
+
+a:active,
+a:hover {
+  outline: 0;
+}
+
+h1 {
+  margin: 0.67em 0;
+  font-size: 2em;
+}
+
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+
+b,
+strong {
+  font-weight: bold;
+}
+
+dfn {
+  font-style: italic;
+}
+
+hr {
+  height: 0;
+  -moz-box-sizing: content-box;
+       box-sizing: content-box;
+}
+
+mark {
+  color: #000;
+  background: #ff0;
+}
+
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, serif;
+  font-size: 1em;
+}
+
+pre {
+  white-space: pre-wrap;
+}
+
+q {
+  quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+small {
+  font-size: 80%;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+img {
+  border: 0;
+}
+
+svg:not(:root) {
+  overflow: hidden;
+}
+
+figure {
+  margin: 0;
+}
+
+fieldset {
+  padding: 0.35em 0.625em 0.75em;
+  margin: 0 2px;
+  border: 1px solid #c0c0c0;
+}
+
+legend {
+  padding: 0;
+  border: 0;
+}
+
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-family: inherit;
+  font-size: 100%;
+}
+
+button,
+input {
+  line-height: normal;
+}
+
+button,
+select {
+  text-transform: none;
+}
+
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  cursor: pointer;
+  -webkit-appearance: button;
+}
+
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+  padding: 0;
+  box-sizing: border-box;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+@media print {
+  * {
+    color: #000 !important;
+    text-shadow: none !important;
+    background: transparent !important;
+    box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  .ir a:after,
+  a[href^="javascript:"]:after,
+  a[href^="#"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  @page  {
+    margin: 2cm .5cm;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+  .navbar {
+    display: none;
+  }
+  .table td,
+  .table th {
+    background-color: #fff !important;
+  }
+  .btn > .caret,
+  .dropup > .btn > .caret {
+    border-top-color: #000 !important;
+  }
+  .label {
+    border: 1px solid #000;
+  }
+  .table {
+    border-collapse: collapse !important;
+  }
+  .table-bordered th,
+  .table-bordered td {
+    border: 1px solid #ddd !important;
+  }
+}
+
+* {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+html {
+  font-size: 62.5%;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #333333;
+  background-color: #ffffff;
+}
+
+input,
+button,
+select,
+textarea {
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+a {
+  color: #428bca;
+  text-decoration: none;
+}
+
+a:hover,
+a:focus {
+  color: #2a6496;
+  text-decoration: underline;
+}
+
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+img {
+  vertical-align: middle;
+}
+
+.img-responsive {
+  display: inline-block;
+  height: auto;
+  max-width: 100%;
+}
+
+.img-rounded {
+  border-radius: 6px;
+}
+
+.img-circle {
+  border-radius: 500px;
+}
+
+hr {
+  margin-top: 20px;
+  margin-bottom: 20px;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+}
+
+p {
+  margin: 0 0 10px;
+}
+
+.lead {
+  margin-bottom: 20px;
+  font-size: 16.099999999999998px;
+  font-weight: 200;
+  line-height: 1.4;
+}
+
+@media (min-width: 768px) {
+  .lead {
+    font-size: 21px;
+  }
+}
+
+small {
+  font-size: 85%;
+}
+
+cite {
+  font-style: normal;
+}
+
+.text-muted {
+  color: #999999;
+}
+
+.text-primary {
+  color: #428bca;
+}
+
+.text-warning {
+  color: #c09853;
+}
+
+.text-danger {
+  color: #b94a48;
+}
+
+.text-success {
+  color: #468847;
+}
+
+.text-info {
+  color: #3a87ad;
+}
+
+.text-left {
+  text-align: left;
+}
+
+.text-right {
+  text-align: right;
+}
+
+.text-center {
+  text-align: center;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-weight: 500;
+  line-height: 1.1;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small {
+  font-weight: normal;
+  line-height: 1;
+  color: #999999;
+}
+
+h1,
+h2,
+h3 {
+  margin-top: 20px;
+  margin-bottom: 10px;
+}
+
+h4,
+h5,
+h6 {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+h1,
+.h1 {
+  font-size: 38px;
+}
+
+h2,
+.h2 {
+  font-size: 32px;
+}
+
+h3,
+.h3 {
+  font-size: 24px;
+}
+
+h4,
+.h4 {
+  font-size: 18px;
+}
+
+h5,
+.h5 {
+  font-size: 14px;
+}
+
+h6,
+.h6 {
+  font-size: 12px;
+}
+
+h1 small,
+.h1 small {
+  font-size: 24px;
+}
+
+h2 small,
+.h2 small {
+  font-size: 18px;
+}
+
+h3 small,
+.h3 small,
+h4 small,
+.h4 small {
+  font-size: 14px;
+}
+
+.page-header {
+  padding-bottom: 9px;
+  margin: 40px 0 20px;
+  border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+  margin-top: 0;
+  margin-bottom: 10px;
+}
+
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+  margin-bottom: 0;
+}
+
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline > li {
+  display: inline-block;
+  padding-right: 5px;
+  padding-left: 5px;
+}
+
+dl {
+  margin-bottom: 20px;
+}
+
+dt,
+dd {
+  line-height: 1.428571429;
+}
+
+dt {
+  font-weight: bold;
+}
+
+dd {
+  margin-left: 0;
+}
+
+.dl-horizontal dt {
+  float: left;
+  width: 160px;
+  overflow: hidden;
+  clear: left;
+  text-align: right;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.dl-horizontal dd {
+  margin-left: 180px;
+}
+
+.dl-horizontal dd:before,
+.dl-horizontal dd:after {
+  display: table;
+  content: " ";
+}
+
+.dl-horizontal dd:after {
+  clear: both;
+}
+
+.dl-horizontal dd:before,
+.dl-horizontal dd:after {
+  display: table;
+  content: " ";
+}
+
+.dl-horizontal dd:after {
+  clear: both;
+}
+
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+
+blockquote {
+  padding: 10px 20px;
+  margin: 0 0 20px;
+  border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+  font-size: 17.5px;
+  font-weight: 300;
+  line-height: 1.25;
+}
+
+blockquote p:last-child {
+  margin-bottom: 0;
+}
+
+blockquote small {
+  display: block;
+  line-height: 1.428571429;
+  color: #999999;
+}
+
+blockquote small:before {
+  content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+  float: right;
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid #eeeeee;
+  border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small {
+  text-align: right;
+}
+
+blockquote.pull-right small:before {
+  content: '';
+}
+
+blockquote.pull-right small:after {
+  content: '\00A0 \2014';
+}
+
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+  content: "";
+}
+
+address {
+  display: block;
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 1.428571429;
+}
+
+code,
+pre {
+  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+}
+
+code {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: #c7254e;
+  white-space: nowrap;
+  background-color: #f9f2f4;
+  border-radius: 4px;
+}
+
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 1.428571429;
+  color: #333333;
+  word-break: break-all;
+  word-wrap: break-word;
+  background-color: #f5f5f5;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+}
+
+pre.prettyprint {
+  margin-bottom: 20px;
+}
+
+pre code {
+  padding: 0;
+  color: inherit;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border: 0;
+}
+
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+
+.container {
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  content: " ";
+}
+
+.container:after {
+  clear: both;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  content: " ";
+}
+
+.container:after {
+  clear: both;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  content: " ";
+}
+
+.row:after {
+  clear: both;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  content: " ";
+}
+
+.row:after {
+  clear: both;
+}
+
+@media (min-width: 768px) {
+  .row {
+    margin-right: -15px;
+    margin-left: -15px;
+  }
+}
+
+.row .row {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+.col-1,
+.col-2,
+.col-3,
+.col-4,
+.col-5,
+.col-6,
+.col-7,
+.col-8,
+.col-9,
+.col-10,
+.col-11,
+.col-12,
+.col-sm-1,
+.col-sm-2,
+.col-sm-3,
+.col-sm-4,
+.col-sm-5,
+.col-sm-6,
+.col-sm-7,
+.col-sm-8,
+.col-sm-9,
+.col-sm-10,
+.col-sm-11,
+.col-sm-12,
+.col-lg-1,
+.col-lg-2,
+.col-lg-3,
+.col-lg-4,
+.col-lg-5,
+.col-lg-6,
+.col-lg-7,
+.col-lg-8,
+.col-lg-9,
+.col-lg-10,
+.col-lg-11,
+.col-lg-12 {
+  position: relative;
+  min-height: 1px;
+  padding-right: 15px;
+  padding-left: 15px;
+}
+
+.col-1,
+.col-2,
+.col-3,
+.col-4,
+.col-5,
+.col-6,
+.col-7,
+.col-8,
+.col-9,
+.col-10,
+.col-11,
+.col-12 {
+  float: left;
+}
+
+.col-1 {
+  width: 8.333333333333332%;
+}
+
+.col-2 {
+  width: 16.666666666666664%;
+}
+
+.col-3 {
+  width: 25%;
+}
+
+.col-4 {
+  width: 33.33333333333333%;
+}
+
+.col-5 {
+  width: 41.66666666666667%;
+}
+
+.col-6 {
+  width: 50%;
+}
+
+.col-7 {
+  width: 58.333333333333336%;
+}
+
+.col-8 {
+  width: 66.66666666666666%;
+}
+
+.col-9 {
+  width: 75%;
+}
+
+.col-10 {
+  width: 83.33333333333334%;
+}
+
+.col-11 {
+  width: 91.66666666666666%;
+}
+
+.col-12 {
+  width: 100%;
+}
+
+@media (min-width: 768px) {
+  .container {
+    max-width: 728px;
+  }
+  .col-sm-1,
+  .col-sm-2,
+  .col-sm-3,
+  .col-sm-4,
+  .col-sm-5,
+  .col-sm-6,
+  .col-sm-7,
+  .col-sm-8,
+  .col-sm-9,
+  .col-sm-10,
+  .col-sm-11,
+  .col-sm-12 {
+    float: left;
+  }
+  .col-sm-1 {
+    width: 8.333333333333332%;
+  }
+  .col-sm-2 {
+    width: 16.666666666666664%;
+  }
+  .col-sm-3 {
+    width: 25%;
+  }
+  .col-sm-4 {
+    width: 33.33333333333333%;
+  }
+  .col-sm-5 {
+    width: 41.66666666666667%;
+  }
+  .col-sm-6 {
+    width: 50%;
+  }
+  .col-sm-7 {
+    width: 58.333333333333336%;
+  }
+  .col-sm-8 {
+    width: 66.66666666666666%;
+  }
+  .col-sm-9 {
+    width: 75%;
+  }
+  .col-sm-10 {
+    width: 83.33333333333334%;
+  }
+  .col-sm-11 {
+    width: 91.66666666666666%;
+  }
+  .col-sm-12 {
+    width: 100%;
+  }
+  .col-sm-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-sm-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-sm-push-3 {
+    left: 25%;
+  }
+  .col-sm-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-sm-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-sm-push-6 {
+    left: 50%;
+  }
+  .col-sm-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-sm-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-sm-push-9 {
+    left: 75%;
+  }
+  .col-sm-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-sm-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-sm-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-sm-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-sm-pull-3 {
+    right: 25%;
+  }
+  .col-sm-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-sm-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-sm-pull-6 {
+    right: 50%;
+  }
+  .col-sm-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-sm-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-sm-pull-9 {
+    right: 75%;
+  }
+  .col-sm-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-sm-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-sm-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-sm-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-sm-offset-3 {
+    margin-left: 25%;
+  }
+  .col-sm-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-sm-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-sm-offset-6 {
+    margin-left: 50%;
+  }
+  .col-sm-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-sm-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-sm-offset-9 {
+    margin-left: 75%;
+  }
+  .col-sm-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-sm-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+}
+
+@media (min-width: 992px) {
+  .container {
+    max-width: 940px;
+  }
+  .col-lg-1,
+  .col-lg-2,
+  .col-lg-3,
+  .col-lg-4,
+  .col-lg-5,
+  .col-lg-6,
+  .col-lg-7,
+  .col-lg-8,
+  .col-lg-9,
+  .col-lg-10,
+  .col-lg-11,
+  .col-lg-12 {
+    float: left;
+  }
+  .col-lg-1 {
+    width: 8.333333333333332%;
+  }
+  .col-lg-2 {
+    width: 16.666666666666664%;
+  }
+  .col-lg-3 {
+    width: 25%;
+  }
+  .col-lg-4 {
+    width: 33.33333333333333%;
+  }
+  .col-lg-5 {
+    width: 41.66666666666667%;
+  }
+  .col-lg-6 {
+    width: 50%;
+  }
+  .col-lg-7 {
+    width: 58.333333333333336%;
+  }
+  .col-lg-8 {
+    width: 66.66666666666666%;
+  }
+  .col-lg-9 {
+    width: 75%;
+  }
+  .col-lg-10 {
+    width: 83.33333333333334%;
+  }
+  .col-lg-11 {
+    width: 91.66666666666666%;
+  }
+  .col-lg-12 {
+    width: 100%;
+  }
+  .col-lg-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-lg-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-lg-push-3 {
+    left: 25%;
+  }
+  .col-lg-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-lg-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-lg-push-6 {
+    left: 50%;
+  }
+  .col-lg-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-lg-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-lg-push-9 {
+    left: 75%;
+  }
+  .col-lg-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-lg-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-lg-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-lg-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-lg-pull-3 {
+    right: 25%;
+  }
+  .col-lg-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-lg-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-lg-pull-6 {
+    right: 50%;
+  }
+  .col-lg-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-lg-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-lg-pull-9 {
+    right: 75%;
+  }
+  .col-lg-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-lg-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-lg-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+  .col-lg-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-lg-offset-3 {
+    margin-left: 25%;
+  }
+  .col-lg-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-lg-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-lg-offset-6 {
+    margin-left: 50%;
+  }
+  .col-lg-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-lg-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-lg-offset-9 {
+    margin-left: 75%;
+  }
+  .col-lg-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-lg-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+}
+
+@media (min-width: 1200px) {
+  .container {
+    max-width: 1170px;
+  }
+}
+
+table {
+  max-width: 100%;
+  background-color: transparent;
+}
+
+th {
+  text-align: left;
+}
+
+.table {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.table thead > tr > th,
+.table tbody > tr > th,
+.table tfoot > tr > th,
+.table thead > tr > td,
+.table tbody > tr > td,
+.table tfoot > tr > td {
+  padding: 8px;
+  line-height: 1.428571429;
+  vertical-align: top;
+  border-top: 1px solid #dddddd;
+}
+
+.table thead > tr > th {
+  vertical-align: bottom;
+}
+
+.table caption + thead tr:first-child th,
+.table colgroup + thead tr:first-child th,
+.table thead:first-child tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child td {
+  border-top: 0;
+}
+
+.table tbody + tbody {
+  border-top: 2px solid #dddddd;
+}
+
+.table .table {
+  background-color: #ffffff;
+}
+
+.table-condensed thead > tr > th,
+.table-condensed tbody > tr > th,
+.table-condensed tfoot > tr > th,
+.table-condensed thead > tr > td,
+.table-condensed tbody > tr > td,
+.table-condensed tfoot > tr > td {
+  padding: 5px;
+}
+
+.table-bordered {
+  border: 1px solid #dddddd;
+}
+
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+  border: 1px solid #dddddd;
+}
+
+.table-striped > tbody > tr:nth-child(odd) > td,
+.table-striped > tbody > tr:nth-child(odd) > th {
+  background-color: #f9f9f9;
+}
+
+.table-hover > tbody > tr:hover > td,
+.table-hover > tbody > tr:hover > th {
+  background-color: #f5f5f5;
+}
+
+table col[class^="col-"] {
+  display: table-column;
+  float: none;
+}
+
+table td[class^="col-"],
+table th[class^="col-"] {
+  display: table-cell;
+  float: none;
+}
+
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+  background-color: #f5f5f5;
+}
+
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+  background-color: #fcf8e3;
+  border-color: #fbeed5;
+}
+
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td {
+  background-color: #d0e9c6;
+  border-color: #c9e2b3;
+}
+
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td {
+  background-color: #ebcccc;
+  border-color: #e6c1c7;
+}
+
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td {
+  background-color: #faf2cc;
+  border-color: #f8e5be;
+}
+
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: inherit;
+  color: #333333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+label {
+  display: inline-block;
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9;
+  /* IE8-9 */
+
+  line-height: normal;
+}
+
+input[type="file"] {
+  display: block;
+}
+
+select[multiple],
+select[size] {
+  height: auto;
+}
+
+select optgroup {
+  font-family: inherit;
+  font-size: inherit;
+  font-style: inherit;
+}
+
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+  height: auto;
+}
+
+.form-control:-moz-placeholder {
+  color: #999999;
+}
+
+.form-control::-moz-placeholder {
+  color: #999999;
+}
+
+.form-control:-ms-input-placeholder {
+  color: #999999;
+}
+
+.form-control::-webkit-input-placeholder {
+  color: #999999;
+}
+
+.form-control {
+  display: block;
+  width: 100%;
+  height: 34px;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #555555;
+  vertical-align: middle;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+          transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+}
+
+.form-control:focus {
+  border-color: #66afe9;
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+  cursor: not-allowed;
+  background-color: #eeeeee;
+}
+
+textarea.form-control {
+  height: auto;
+}
+
+.form-group {
+  margin-bottom: 15px;
+}
+
+.radio,
+.checkbox {
+  display: block;
+  min-height: 20px;
+  padding-left: 20px;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  vertical-align: middle;
+}
+
+.radio label,
+.checkbox label {
+  display: inline;
+  margin-bottom: 0;
+  font-weight: normal;
+  cursor: pointer;
+}
+
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+  float: left;
+  margin-left: -20px;
+}
+
+.radio + .radio,
+.checkbox + .checkbox {
+  margin-top: -5px;
+}
+
+.radio-inline,
+.checkbox-inline {
+  display: inline-block;
+  padding-left: 20px;
+  margin-bottom: 0;
+  font-weight: normal;
+  vertical-align: middle;
+  cursor: pointer;
+}
+
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+  margin-top: 0;
+  margin-left: 10px;
+}
+
+.input-lg {
+  height: 45px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+.input-sm {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+select.input-lg {
+  height: 45px;
+  line-height: 45px;
+}
+
+select.input-sm {
+  height: 30px;
+  line-height: 30px;
+}
+
+textarea.input-lg,
+textarea.input-sm {
+  height: auto;
+}
+
+.has-warning .help-block,
+.has-warning .control-label {
+  color: #c09853;
+}
+
+.has-warning .form-control {
+  border-color: #c09853;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-warning .form-control:focus {
+  border-color: #a47e3c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.has-warning .input-group-addon {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #c09853;
+}
+
+.has-error .help-block,
+.has-error .control-label {
+  color: #b94a48;
+}
+
+.has-error .form-control {
+  border-color: #b94a48;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-error .form-control:focus {
+  border-color: #953b39;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.has-error .input-group-addon {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #b94a48;
+}
+
+.has-success .help-block,
+.has-success .control-label {
+  color: #468847;
+}
+
+.has-success .form-control {
+  border-color: #468847;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-success .form-control:focus {
+  border-color: #356635;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.has-success .input-group-addon {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #468847;
+}
+
+.help-block {
+  display: block;
+  margin-top: 5px;
+  margin-bottom: 10px;
+  color: #737373;
+}
+
+.form-inline .form-control,
+.form-inline .radio,
+.form-inline .checkbox {
+  display: inline-block;
+}
+
+.form-inline .radio,
+.form-inline .checkbox {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.form-horizontal .control-label,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+  padding-top: 9px;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  display: table;
+  content: " ";
+}
+
+.form-horizontal .form-group:after {
+  clear: both;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  display: table;
+  content: " ";
+}
+
+.form-horizontal .form-group:after {
+  clear: both;
+}
+
+@media (min-width: 768px) {
+  .form-horizontal .form-group {
+    margin-right: -15px;
+    margin-left: -15px;
+  }
+}
+
+.form-horizontal .form-group .row {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+@media (min-width: 768px) {
+  .form-horizontal .control-label {
+    text-align: right;
+  }
+}
+
+.btn {
+  display: inline-block;
+  padding: 6px 12px;
+  margin-bottom: 0;
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 1.428571429;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  cursor: pointer;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+       -o-user-select: none;
+          user-select: none;
+}
+
+.btn:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+.btn:hover,
+.btn:focus {
+  color: #ffffff;
+  text-decoration: none;
+}
+
+.btn:active,
+.btn.active {
+  outline: 0;
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+  pointer-events: none;
+  cursor: default;
+  opacity: 0.65;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-default {
+  color: #ffffff;
+  background-color: #474949;
+  border-color: #474949;
+}
+
+.btn-default:hover,
+.btn-default:focus,
+.btn-default:active,
+.btn-default.active {
+  background-color: #3a3c3c;
+  border-color: #2e2f2f;
+}
+
+.btn-default.disabled,
+.btn-default[disabled],
+fieldset[disabled] .btn-default,
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled:active,
+.btn-default[disabled]:active,
+fieldset[disabled] .btn-default:active,
+.btn-default.disabled.active,
+.btn-default[disabled].active,
+fieldset[disabled] .btn-default.active {
+  background-color: #474949;
+  border-color: #474949;
+}
+
+.btn-primary {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active {
+  background-color: #357ebd;
+  border-color: #3071a9;
+}
+
+.btn-primary.disabled,
+.btn-primary[disabled],
+fieldset[disabled] .btn-primary,
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled:active,
+.btn-primary[disabled]:active,
+fieldset[disabled] .btn-primary:active,
+.btn-primary.disabled.active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary.active {
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.btn-warning {
+  color: #ffffff;
+  background-color: #f0ad4e;
+  border-color: #f0ad4e;
+}
+
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active {
+  background-color: #eea236;
+  border-color: #ec971f;
+}
+
+.btn-warning.disabled,
+.btn-warning[disabled],
+fieldset[disabled] .btn-warning,
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled:active,
+.btn-warning[disabled]:active,
+fieldset[disabled] .btn-warning:active,
+.btn-warning.disabled.active,
+.btn-warning[disabled].active,
+fieldset[disabled] .btn-warning.active {
+  background-color: #f0ad4e;
+  border-color: #f0ad4e;
+}
+
+.btn-danger {
+  color: #ffffff;
+  background-color: #d9534f;
+  border-color: #d9534f;
+}
+
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active {
+  background-color: #d43f3a;
+  border-color: #c9302c;
+}
+
+.btn-danger.disabled,
+.btn-danger[disabled],
+fieldset[disabled] .btn-danger,
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled:active,
+.btn-danger[disabled]:active,
+fieldset[disabled] .btn-danger:active,
+.btn-danger.disabled.active,
+.btn-danger[disabled].active,
+fieldset[disabled] .btn-danger.active {
+  background-color: #d9534f;
+  border-color: #d9534f;
+}
+
+.btn-success {
+  color: #ffffff;
+  background-color: #5cb85c;
+  border-color: #5cb85c;
+}
+
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active {
+  background-color: #4cae4c;
+  border-color: #449d44;
+}
+
+.btn-success.disabled,
+.btn-success[disabled],
+fieldset[disabled] .btn-success,
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled:active,
+.btn-success[disabled]:active,
+fieldset[disabled] .btn-success:active,
+.btn-success.disabled.active,
+.btn-success[disabled].active,
+fieldset[disabled] .btn-success.active {
+  background-color: #5cb85c;
+  border-color: #5cb85c;
+}
+
+.btn-info {
+  color: #ffffff;
+  background-color: #5bc0de;
+  border-color: #5bc0de;
+}
+
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active {
+  background-color: #46b8da;
+  border-color: #31b0d5;
+}
+
+.btn-info.disabled,
+.btn-info[disabled],
+fieldset[disabled] .btn-info,
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled:active,
+.btn-info[disabled]:active,
+fieldset[disabled] .btn-info:active,
+.btn-info.disabled.active,
+.btn-info[disabled].active,
+fieldset[disabled] .btn-info.active {
+  background-color: #5bc0de;
+  border-color: #5bc0de;
+}
+
+.btn-link {
+  font-weight: normal;
+  color: #428bca;
+  cursor: pointer;
+  border-radius: 0;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+  background-color: transparent;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+  border-color: transparent;
+}
+
+.btn-link:hover,
+.btn-link:focus {
+  color: #2a6496;
+  text-decoration: underline;
+  background-color: transparent;
+}
+
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+  color: #333333;
+  text-decoration: none;
+}
+
+.btn-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+.btn-sm,
+.btn-xs {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-xs {
+  padding: 3px 5px;
+}
+
+.btn-block {
+  display: block;
+  width: 100%;
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity 0.15s linear;
+          transition: opacity 0.15s linear;
+}
+
+.fade.in {
+  opacity: 1;
+}
+
+.collapse {
+  display: none;
+}
+
+.collapse.in {
+  display: block;
+}
+
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition: height 0.35s ease;
+          transition: height 0.35s ease;
+}
+
+.input-group {
+  position: relative;
+  display: table;
+  border-collapse: separate;
+}
+
+.input-group.col {
+  float: none;
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.input-group .form-control {
+  width: 100%;
+  margin-bottom: 0;
+}
+
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+  display: table-cell;
+}
+
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+
+.input-group-addon,
+.input-group-btn {
+  width: 1%;
+  white-space: nowrap;
+  vertical-align: middle;
+}
+
+.input-group-addon {
+  padding: 6px 12px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.428571429;
+  text-align: center;
+  background-color: #eeeeee;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.input-group-addon.input-sm {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.input-group-addon.input-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+  margin-top: 0;
+}
+
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.input-group-addon:first-child {
+  border-right: 0;
+}
+
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child) {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.input-group-addon:last-child {
+  border-left: 0;
+}
+
+.input-group-btn {
+  position: relative;
+  white-space: nowrap;
+}
+
+.input-group-btn > .btn {
+  position: relative;
+}
+
+.input-group-btn > .btn + .btn {
+  margin-left: -4px;
+}
+
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:active {
+  z-index: 2;
+}
+
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  margin-left: 2px;
+  vertical-align: middle;
+  border-top: 4px solid #000000;
+  border-right: 4px solid transparent;
+  border-left: 4px solid transparent;
+  content: "";
+}
+
+.dropdown {
+  position: relative;
+}
+
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0;
+  list-style: none;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  border-radius: 4px;
+  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+          box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+  background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.dropdown-menu .divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+
+.dropdown-menu > li > a {
+  display: block;
+  padding: 3px 20px;
+  clear: both;
+  font-weight: normal;
+  line-height: 1.428571429;
+  color: #333333;
+  white-space: nowrap;
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #357ebd;
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
+  background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+}
+
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #357ebd;
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
+  background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+  background-repeat: repeat-x;
+  outline: 0;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+}
+
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  color: #999999;
+}
+
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+  background-image: none;
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.open > .dropdown-menu {
+  display: block;
+}
+
+.open > a {
+  outline: 0;
+}
+
+.dropdown-header {
+  display: block;
+  padding: 3px 20px;
+  font-size: 12px;
+  line-height: 1.428571429;
+  color: #999999;
+}
+
+.dropdown-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 990;
+}
+
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  border-top: 0;
+  border-bottom: 4px solid #000000;
+  content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 1px;
+}
+
+.list-group {
+  padding-left: 0;
+  margin-bottom: 20px;
+}
+
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 10px 30px 10px 15px;
+  margin-bottom: -1px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+}
+
+.list-group-item:first-child {
+  border-top-right-radius: 4px;
+  border-top-left-radius: 4px;
+}
+
+.list-group-item:last-child {
+  margin-bottom: 0;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+
+.list-group-item > .badge {
+  float: right;
+  margin-right: -15px;
+}
+
+.list-group-item-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+
+.list-group-item-text {
+  margin-bottom: 0;
+  line-height: 1.3;
+}
+
+a.list-group-item .list-group-item-heading {
+  color: #333333;
+}
+
+a.list-group-item .list-group-item-text {
+  color: #555555;
+}
+
+a.list-group-item:hover,
+a.list-group-item:focus {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+
+a.list-group-item.active {
+  z-index: 2;
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+a.list-group-item.active .list-group-item-heading {
+  color: inherit;
+}
+
+a.list-group-item.active .list-group-item-text {
+  color: #e1edf7;
+}
+
+.panel {
+  padding: 15px;
+  margin-bottom: 20px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.panel .list-group {
+  margin: 15px -15px -15px;
+}
+
+.panel .list-group .list-group-item {
+  border-width: 1px 0;
+}
+
+.panel .list-group .list-group-item:first-child {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.panel .list-group .list-group-item:last-child {
+  border-bottom: 0;
+}
+
+.panel-heading {
+  padding: 10px 15px;
+  margin: -15px -15px 15px;
+  background-color: #f5f5f5;
+  border-bottom: 1px solid #dddddd;
+  border-top-right-radius: 3px;
+  border-top-left-radius: 3px;
+}
+
+.panel-title {
+  margin-top: 0;
+  margin-bottom: 0;
+  font-size: 17.5px;
+  font-weight: 500;
+}
+
+.panel-title > a {
+  color: inherit;
+}
+
+.panel-footer {
+  padding: 10px 15px;
+  margin: 15px -15px -15px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #dddddd;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+
+.panel-primary {
+  border-color: #428bca;
+}
+
+.panel-primary .panel-heading {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.panel-success {
+  border-color: #d6e9c6;
+}
+
+.panel-success .panel-heading {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.panel-warning {
+  border-color: #fbeed5;
+}
+
+.panel-warning .panel-heading {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #fbeed5;
+}
+
+.panel-danger {
+  border-color: #eed3d7;
+}
+
+.panel-danger .panel-heading {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+
+.panel-info {
+  border-color: #bce8f1;
+}
+
+.panel-info .panel-heading {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-lg {
+  padding: 24px;
+  border-radius: 6px;
+}
+
+.well-sm {
+  padding: 9px;
+  border-radius: 3px;
+}
+
+.close {
+  float: right;
+  font-size: 21px;
+  font-weight: bold;
+  line-height: 1;
+  color: #000000;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.2;
+  filter: alpha(opacity=20);
+}
+
+.close:hover,
+.close:focus {
+  color: #000000;
+  text-decoration: none;
+  cursor: pointer;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+button.close {
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+  -webkit-appearance: none;
+}
+
+.nav {
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+
+.nav:before,
+.nav:after {
+  display: table;
+  content: " ";
+}
+
+.nav:after {
+  clear: both;
+}
+
+.nav:before,
+.nav:after {
+  display: table;
+  content: " ";
+}
+
+.nav:after {
+  clear: both;
+}
+
+.nav > li {
+  position: relative;
+  display: block;
+}
+
+.nav > li > a {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+}
+
+.nav > li > a:hover,
+.nav > li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+
+.nav > li.disabled > a {
+  color: #999999;
+}
+
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+  color: #999999;
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+}
+
+.nav.open > a,
+.nav.open > a:hover,
+.nav.open > a:focus {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.nav.open > a .caret,
+.nav.open > a:hover .caret,
+.nav.open > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.nav > .pull-right {
+  float: right;
+}
+
+.nav .nav-divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+
+.nav-tabs {
+  border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs > li {
+  float: left;
+  margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+  margin-right: 2px;
+  line-height: 1.428571429;
+  border: 1px solid transparent;
+  border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover {
+  border-color: #eeeeee;
+}
+
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+  color: #555555;
+  cursor: default;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-bottom-color: transparent;
+}
+
+.nav-tabs.nav-justified {
+  width: 100%;
+  border-bottom: 0;
+}
+
+.nav-tabs.nav-justified > li {
+  display: table-cell;
+  float: none;
+  width: 1%;
+}
+
+.nav-tabs.nav-justified > li > a {
+  text-align: center;
+}
+
+.nav-tabs.nav-justified > li > a {
+  margin-right: 0;
+  border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs.nav-justified > .active > a {
+  border-bottom-color: #ffffff;
+}
+
+.nav-pills > li {
+  float: left;
+}
+
+.nav-pills > li > a {
+  border-radius: 5px;
+}
+
+.nav-pills > li + li {
+  margin-left: 2px;
+}
+
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+  color: #ffffff;
+  background-color: #428bca;
+}
+
+.nav-stacked > li {
+  float: none;
+}
+
+.nav-stacked > li + li {
+  margin-top: 2px;
+  margin-left: 0;
+}
+
+.nav-justified {
+  width: 100%;
+}
+
+.nav-justified > li {
+  display: table-cell;
+  float: none;
+  width: 1%;
+}
+
+.nav-justified > li > a {
+  text-align: center;
+}
+
+.nav-tabs-justified {
+  border-bottom: 0;
+}
+
+.nav-tabs-justified > li > a {
+  margin-right: 0;
+  border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs-justified > .active > a {
+  border-bottom-color: #ffffff;
+}
+
+.tabbable:before,
+.tabbable:after {
+  display: table;
+  content: " ";
+}
+
+.tabbable:after {
+  clear: both;
+}
+
+.tabbable:before,
+.tabbable:after {
+  display: table;
+  content: " ";
+}
+
+.tabbable:after {
+  clear: both;
+}
+
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+  display: none;
+}
+
+.tab-content > .active,
+.pill-content > .active {
+  display: block;
+}
+
+.nav .caret {
+  border-top-color: #428bca;
+  border-bottom-color: #428bca;
+}
+
+.nav a:hover .caret {
+  border-top-color: #2a6496;
+  border-bottom-color: #2a6496;
+}
+
+.nav-tabs .dropdown-menu {
+  margin-top: -1px;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.navbar {
+  position: relative;
+  min-height: 50px;
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-bottom: 20px;
+  background-color: #eeeeee;
+  border-radius: 4px;
+}
+
+.navbar:before,
+.navbar:after {
+  display: table;
+  content: " ";
+}
+
+.navbar:after {
+  clear: both;
+}
+
+.navbar:before,
+.navbar:after {
+  display: table;
+  content: " ";
+}
+
+.navbar:after {
+  clear: both;
+}
+
+.navbar-nav {
+  margin-top: 10px;
+  margin-bottom: 15px;
+}
+
+.navbar-nav > li > a {
+  padding-top: 15px;
+  padding-bottom: 15px;
+  line-height: 20px;
+  color: #777777;
+  border-radius: 4px;
+}
+
+.navbar-nav > li > a:hover,
+.navbar-nav > li > a:focus {
+  color: #333333;
+  background-color: transparent;
+}
+
+.navbar-nav > .active > a,
+.navbar-nav > .active > a:hover,
+.navbar-nav > .active > a:focus {
+  color: #555555;
+  background-color: #d5d5d5;
+}
+
+.navbar-nav > .disabled > a,
+.navbar-nav > .disabled > a:hover,
+.navbar-nav > .disabled > a:focus {
+  color: #cccccc;
+  background-color: transparent;
+}
+
+.navbar-nav.pull-right {
+  width: 100%;
+}
+
+.navbar-static-top {
+  border-radius: 0;
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+  border-radius: 0;
+}
+
+.navbar-fixed-top {
+  top: 0;
+}
+
+.navbar-fixed-bottom {
+  bottom: 0;
+  margin-bottom: 0;
+}
+
+.navbar-brand {
+  display: block;
+  max-width: 200px;
+  padding: 15px 15px;
+  margin-right: auto;
+  margin-left: auto;
+  font-size: 18px;
+  font-weight: 500;
+  line-height: 20px;
+  color: #777777;
+  text-align: center;
+}
+
+.navbar-brand:hover,
+.navbar-brand:focus {
+  color: #5e5e5e;
+  text-decoration: none;
+  background-color: transparent;
+}
+
+.navbar-toggle {
+  position: relative;
+  float: right;
+  width: 48px;
+  height: 34px;
+  padding: 6px 12px;
+  margin-top: 8px;
+  margin-bottom: 8px;
+  background-color: transparent;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+}
+
+.navbar-toggle:hover,
+.navbar-toggle:focus {
+  background-color: #dddddd;
+}
+
+.navbar-toggle .icon-bar {
+  display: block;
+  width: 22px;
+  height: 2px;
+  background-color: #cccccc;
+  border-radius: 1px;
+}
+
+.navbar-toggle .icon-bar + .icon-bar {
+  margin-top: 4px;
+}
+
+.navbar-form {
+  margin-top: 8px;
+  margin-bottom: 8px;
+}
+
+.navbar-form .form-control,
+.navbar-form .radio,
+.navbar-form .checkbox {
+  display: inline-block;
+}
+
+.navbar-form .radio,
+.navbar-form .checkbox {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.navbar-nav > li > .dropdown-menu {
+  margin-top: 0;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.navbar-nav > .dropdown > a:hover .caret,
+.navbar-nav > .dropdown > a:focus .caret {
+  border-top-color: #333333;
+  border-bottom-color: #333333;
+}
+
+.navbar-nav > .open > a,
+.navbar-nav > .open > a:hover,
+.navbar-nav > .open > a:focus {
+  color: #555555;
+  background-color: #d5d5d5;
+}
+
+.navbar-nav > .open > a .caret,
+.navbar-nav > .open > a:hover .caret,
+.navbar-nav > .open > a:focus .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+
+.navbar-nav > .dropdown > a .caret {
+  border-top-color: #777777;
+  border-bottom-color: #777777;
+}
+
+.navbar-nav.pull-right > li > .dropdown-menu,
+.navbar-nav > li > .dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.navbar-inverse {
+  background-color: #222222;
+}
+
+.navbar-inverse .navbar-brand {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-text {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+  color: #ffffff;
+  background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+  color: #444444;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-toggle {
+  border-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+  background-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle .icon-bar {
+  background-color: #ffffff;
+}
+
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+  color: #ffffff;
+  background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a:hover .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a .caret {
+  border-top-color: #999999;
+  border-bottom-color: #999999;
+}
+
+.navbar-inverse .navbar-nav > .open > a .caret,
+.navbar-inverse .navbar-nav > .open > a:hover .caret,
+.navbar-inverse .navbar-nav > .open > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+@media screen and (min-width: 768px) {
+  .navbar-brand {
+    float: left;
+    max-width: none;
+    margin-right: 5px;
+    margin-left: -15px;
+  }
+  .navbar-nav {
+    float: left;
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+  .navbar-nav > li {
+    float: left;
+  }
+  .navbar-nav > li > a {
+    border-radius: 0;
+  }
+  .navbar-nav.pull-right {
+    float: right;
+    width: auto;
+  }
+  .navbar-toggle {
+    position: relative;
+    top: auto;
+    left: auto;
+    display: none;
+  }
+  .nav-collapse.collapse {
+    display: block !important;
+    height: auto !important;
+    overflow: visible !important;
+  }
+}
+
+.navbar-btn {
+  margin-top: 8px;
+}
+
+.navbar-text {
+  float: left;
+  padding: 0 15px;
+  margin-top: 15px;
+  margin-bottom: 15px;
+}
+
+.navbar-link {
+  color: #777777;
+}
+
+.navbar-link:hover {
+  color: #333333;
+}
+
+.navbar-inverse .navbar-link {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover {
+  color: #ffffff;
+}
+
+.btn .caret {
+  border-top-color: #ffffff;
+}
+
+.dropup .btn .caret {
+  border-bottom-color: #ffffff;
+}
+
+.btn-group,
+.btn-group-vertical {
+  position: relative;
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+  position: relative;
+  float: left;
+}
+
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus,
+.btn-group > .btn:active,
+.btn-group-vertical > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn.active {
+  z-index: 2;
+}
+
+.btn-group .btn + .btn {
+  margin-left: -1px;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+  display: table;
+  content: " ";
+}
+
+.btn-toolbar:after {
+  clear: both;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+  display: table;
+  content: " ";
+}
+
+.btn-toolbar:after {
+  clear: both;
+}
+
+.btn-toolbar .btn-group {
+  float: left;
+}
+
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn-group + .btn,
+.btn-toolbar > .btn + .btn-group,
+.btn-toolbar > .btn-group + .btn-group {
+  margin-left: 5px;
+}
+
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+  border-radius: 0;
+}
+
+.btn-group > .btn:first-child {
+  margin-left: 0;
+}
+
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group > .btn-group {
+  float: left;
+}
+
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+
+.btn-group > .btn-group:first-child > .btn:last-child,
+.btn-group > .btn-group:first-child > .dropdown-toggle {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn-group:last-child > .btn:first-child {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+  padding-right: 8px;
+  padding-left: 8px;
+}
+
+.btn-group > .btn-lg + .dropdown-toggle {
+  padding-right: 12px;
+  padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn .caret {
+  margin-left: 0;
+}
+
+.btn-lg .caret {
+  border-width: 5px;
+}
+
+.dropup .btn-lg .caret {
+  border-bottom-width: 5px;
+}
+
+.btn-group-vertical > .btn {
+  display: block;
+  float: none;
+  width: 100%;
+  max-width: 100%;
+}
+
+.btn-group-vertical > .btn + .btn {
+  margin-top: -1px;
+}
+
+.btn-group-vertical .btn:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+
+.btn-group-vertical .btn:first-child:not(:last-child) {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical .btn:last-child:not(:first-child) {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group-justified {
+  display: table;
+  width: 100%;
+  table-layout: fixed;
+}
+
+.btn-group-justified .btn {
+  display: table-cell;
+  float: none;
+  width: 1%;
+}
+
+.btn-group[data-toggle="buttons"] > .btn > input[type="radio"],
+.btn-group[data-toggle="buttons"] > .btn > input[type="checkbox"] {
+  display: none;
+}
+
+.breadcrumb {
+  padding: 8px 15px;
+  margin-bottom: 20px;
+  list-style: none;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+}
+
+.breadcrumb > li {
+  display: inline-block;
+}
+
+.breadcrumb > li + li:before {
+  padding: 0 5px;
+  color: #cccccc;
+  content: "/\00a0";
+}
+
+.breadcrumb > .active {
+  color: #999999;
+}
+
+.pagination {
+  display: inline-block;
+  padding-left: 0;
+  margin: 20px 0;
+  border-radius: 4px;
+}
+
+.pagination > li {
+  display: inline;
+}
+
+.pagination > li > a,
+.pagination > li > span {
+  float: left;
+  padding: 6px 12px;
+  line-height: 1.428571429;
+  text-decoration: none;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-left-width: 0;
+}
+
+.pagination > li:first-child > a,
+.pagination > li:first-child > span {
+  border-left-width: 1px;
+  border-bottom-left-radius: 4px;
+  border-top-left-radius: 4px;
+}
+
+.pagination > li:last-child > a,
+.pagination > li:last-child > span {
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 4px;
+}
+
+.pagination > li > a:hover,
+.pagination > li > a:focus,
+.pagination > .active > a,
+.pagination > .active > span {
+  background-color: #f5f5f5;
+}
+
+.pagination > .active > a,
+.pagination > .active > span {
+  color: #999999;
+  cursor: default;
+}
+
+.pagination > .disabled > span,
+.pagination > .disabled > a,
+.pagination > .disabled > a:hover,
+.pagination > .disabled > a:focus {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #ffffff;
+}
+
+.pagination-lg > li > a,
+.pagination-lg > li > span {
+  padding: 10px 16px;
+  font-size: 18px;
+}
+
+.pagination-lg > li:first-child > a,
+.pagination-lg > li:first-child > span {
+  border-bottom-left-radius: 6px;
+  border-top-left-radius: 6px;
+}
+
+.pagination-lg > li:last-child > a,
+.pagination-lg > li:last-child > span {
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+
+.pagination-sm > li > a,
+.pagination-sm > li > span {
+  padding: 5px 10px;
+  font-size: 12px;
+}
+
+.pagination-sm > li:first-child > a,
+.pagination-sm > li:first-child > span {
+  border-bottom-left-radius: 3px;
+  border-top-left-radius: 3px;
+}
+
+.pagination-sm > li:last-child > a,
+.pagination-sm > li:last-child > span {
+  border-top-right-radius: 3px;
+  border-bottom-right-radius: 3px;
+}
+
+.pager {
+  padding-left: 0;
+  margin: 20px 0;
+  text-align: center;
+  list-style: none;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  content: " ";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  content: " ";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager li {
+  display: inline;
+}
+
+.pager li > a,
+.pager li > span {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 15px;
+}
+
+.pager li > a:hover,
+.pager li > a:focus {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+
+.pager .next > a,
+.pager .next > span {
+  float: right;
+}
+
+.pager .previous > a,
+.pager .previous > span {
+  float: left;
+}
+
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #ffffff;
+}
+
+.modal-open {
+  overflow: hidden;
+}
+
+.modal {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  display: none;
+  overflow: auto;
+  overflow-y: scroll;
+}
+
+.modal.fade .modal-dialog {
+  -webkit-transform: translate(0, -25%);
+      -ms-transform: translate(0, -25%);
+          transform: translate(0, -25%);
+  -webkit-transition: -webkit-transform 0.3s ease-out;
+     -moz-transition: -moz-transform 0.3s ease-out;
+       -o-transition: -o-transform 0.3s ease-out;
+          transition: transform 0.3s ease-out;
+}
+
+.modal.in .modal-dialog {
+  -webkit-transform: translate(0, 0);
+      -ms-transform: translate(0, 0);
+          transform: translate(0, 0);
+}
+
+.modal-dialog {
+  z-index: 1050;
+  width: auto;
+  padding: 10px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.modal-content {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #999999;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  outline: none;
+  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+          box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+  background-clip: padding-box;
+}
+
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1030;
+  background-color: #000000;
+}
+
+.modal-backdrop.fade {
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+
+.modal-backdrop.in {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.modal-header {
+  min-height: 16.428571429px;
+  padding: 15px;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+.modal-header .close {
+  margin-top: -2px;
+}
+
+.modal-title {
+  margin: 0;
+  line-height: 1.428571429;
+}
+
+.modal-body {
+  position: relative;
+  padding: 20px;
+}
+
+.modal-footer {
+  padding: 19px 20px 20px;
+  margin-top: 15px;
+  text-align: right;
+  border-top: 1px solid #e5e5e5;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer .btn + .btn {
+  margin-bottom: 0;
+  margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+
+@media screen and (min-width: 768px) {
+  .modal-dialog {
+    right: auto;
+    left: 50%;
+    width: 600px;
+    padding-top: 30px;
+    padding-bottom: 30px;
+  }
+  .modal-content {
+    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+  }
+}
+
+.tooltip {
+  position: absolute;
+  z-index: 1030;
+  display: block;
+  font-size: 12px;
+  line-height: 1.4;
+  opacity: 0;
+  filter: alpha(opacity=0);
+  visibility: visible;
+}
+
+.tooltip.in {
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+
+.tooltip.top {
+  padding: 5px 0;
+  margin-top: -3px;
+}
+
+.tooltip.right {
+  padding: 0 5px;
+  margin-left: 3px;
+}
+
+.tooltip.bottom {
+  padding: 5px 0;
+  margin-top: 3px;
+}
+
+.tooltip.left {
+  padding: 0 5px;
+  margin-left: -3px;
+}
+
+.tooltip-inner {
+  max-width: 200px;
+  padding: 3px 8px;
+  color: #ffffff;
+  text-align: center;
+  text-decoration: none;
+  background-color: rgba(0, 0, 0, 0.9);
+  border-radius: 4px;
+}
+
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-top-color: rgba(0, 0, 0, 0.9);
+  border-width: 5px 5px 0;
+}
+
+.tooltip.top-left .tooltip-arrow {
+  bottom: 0;
+  left: 5px;
+  border-top-color: rgba(0, 0, 0, 0.9);
+  border-width: 5px 5px 0;
+}
+
+.tooltip.top-right .tooltip-arrow {
+  right: 5px;
+  bottom: 0;
+  border-top-color: rgba(0, 0, 0, 0.9);
+  border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-right-color: rgba(0, 0, 0, 0.9);
+  border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-left-color: rgba(0, 0, 0, 0.9);
+  border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-bottom-color: rgba(0, 0, 0, 0.9);
+  border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-left .tooltip-arrow {
+  top: 0;
+  left: 5px;
+  border-bottom-color: rgba(0, 0, 0, 0.9);
+  border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-right .tooltip-arrow {
+  top: 0;
+  right: 5px;
+  border-bottom-color: rgba(0, 0, 0, 0.9);
+  border-width: 0 5px 5px;
+}
+
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1010;
+  display: none;
+  max-width: 276px;
+  padding: 1px;
+  text-align: left;
+  white-space: normal;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  background-clip: padding-box;
+  -webkit-bg-clip: padding-box;
+     -moz-bg-clip: padding;
+}
+
+.popover.top {
+  margin-top: -10px;
+}
+
+.popover.right {
+  margin-left: 10px;
+}
+
+.popover.bottom {
+  margin-top: 10px;
+}
+
+.popover.left {
+  margin-left: -10px;
+}
+
+.popover-title {
+  padding: 8px 14px;
+  margin: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 18px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+  padding: 9px 14px;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.popover .arrow {
+  border-width: 11px;
+}
+
+.popover .arrow:after {
+  border-width: 10px;
+  content: "";
+}
+
+.popover.top .arrow {
+  bottom: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-color: #999999;
+  border-top-color: rgba(0, 0, 0, 0.25);
+  border-bottom-width: 0;
+}
+
+.popover.top .arrow:after {
+  bottom: 1px;
+  margin-left: -10px;
+  border-top-color: #ffffff;
+  border-bottom-width: 0;
+  content: " ";
+}
+
+.popover.right .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-right-color: #999999;
+  border-right-color: rgba(0, 0, 0, 0.25);
+  border-left-width: 0;
+}
+
+.popover.right .arrow:after {
+  bottom: -10px;
+  left: 1px;
+  border-right-color: #ffffff;
+  border-left-width: 0;
+  content: " ";
+}
+
+.popover.bottom .arrow {
+  top: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-bottom-color: #999999;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+  border-top-width: 0;
+}
+
+.popover.bottom .arrow:after {
+  top: 1px;
+  margin-left: -10px;
+  border-bottom-color: #ffffff;
+  border-top-width: 0;
+  content: " ";
+}
+
+.popover.left .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-left-color: #999999;
+  border-left-color: rgba(0, 0, 0, 0.25);
+  border-right-width: 0;
+}
+
+.popover.left .arrow:after {
+  right: 1px;
+  bottom: -10px;
+  border-left-color: #ffffff;
+  border-right-width: 0;
+  content: " ";
+}
+
+.alert {
+  padding: 15px 35px 15px 15px;
+  margin-bottom: 20px;
+  color: #c09853;
+  background-color: #fcf8e3;
+  border: 1px solid #fbeed5;
+  border-radius: 4px;
+}
+
+.alert h4 {
+  margin-top: 0;
+  color: inherit;
+}
+
+.alert hr {
+  border-top-color: #f8e5be;
+}
+
+.alert .alert-link {
+  font-weight: bold;
+  color: #a47e3c;
+}
+
+.alert .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  color: inherit;
+}
+
+.alert > p,
+.alert > ul {
+  margin-bottom: 0;
+}
+
+.alert > p + p {
+  margin-top: 5px;
+}
+
+.alert-success {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.alert-success hr {
+  border-top-color: #c9e2b3;
+}
+
+.alert-success .alert-link {
+  color: #356635;
+}
+
+.alert-danger {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+
+.alert-danger hr {
+  border-top-color: #e6c1c7;
+}
+
+.alert-danger .alert-link {
+  color: #953b39;
+}
+
+.alert-info {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.alert-info hr {
+  border-top-color: #a6e1ec;
+}
+
+.alert-info .alert-link {
+  color: #2d6987;
+}
+
+.thumbnail,
+.img-thumbnail {
+  padding: 4px;
+  line-height: 1.428571429;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-transition: all 0.2s ease-in-out;
+          transition: all 0.2s ease-in-out;
+}
+
+.thumbnail {
+  display: block;
+}
+
+.thumbnail > img,
+.img-thumbnail {
+  display: inline-block;
+  height: auto;
+  max-width: 100%;
+}
+
+a.thumbnail:hover,
+a.thumbnail:focus {
+  border-color: #428bca;
+}
+
+.thumbnail > img {
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.thumbnail .caption {
+  padding: 9px;
+  color: #333333;
+}
+
+.media,
+.media-body {
+  overflow: hidden;
+  zoom: 1;
+}
+
+.media,
+.media .media {
+  margin-top: 15px;
+}
+
+.media:first-child {
+  margin-top: 0;
+}
+
+.media-object {
+  display: block;
+}
+
+.media-heading {
+  margin: 0 0 5px;
+}
+
+.media > .pull-left {
+  margin-right: 10px;
+}
+
+.media > .pull-right {
+  margin-left: 10px;
+}
+
+.media-list {
+  padding-left: 0;
+  list-style: none;
+}
+
+.label {
+  display: inline;
+  padding: .25em .6em;
+  font-size: 75%;
+  font-weight: 500;
+  line-height: 1;
+  color: #ffffff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  background-color: #999999;
+  border-radius: .25em;
+}
+
+.label[href]:hover,
+.label[href]:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+  background-color: #808080;
+}
+
+.label-danger {
+  background-color: #d9534f;
+}
+
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+  background-color: #c9302c;
+}
+
+.label-success {
+  background-color: #5cb85c;
+}
+
+.label-success[href]:hover,
+.label-success[href]:focus {
+  background-color: #449d44;
+}
+
+.label-warning {
+  background-color: #f0ad4e;
+}
+
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+  background-color: #ec971f;
+}
+
+.label-info {
+  background-color: #5bc0de;
+}
+
+.label-info[href]:hover,
+.label-info[href]:focus {
+  background-color: #31b0d5;
+}
+
+.badge {
+  display: inline-block;
+  min-width: 10px;
+  padding: 3px 7px;
+  font-size: 12px;
+  font-weight: bold;
+  line-height: 1;
+  color: #ffffff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  background-color: #999999;
+  border-radius: 10px;
+}
+
+.badge:empty {
+  display: none;
+}
+
+a.badge:hover,
+a.badge:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+
+a.list-group-item.active > .badge,
+.nav-pills > .active > a > .badge {
+  color: #428bca;
+  background-color: #ffffff;
+}
+
+.nav-pills > li > a > .badge {
+  margin-left: 3px;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-moz-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-o-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+.progress {
+  height: 20px;
+  margin-bottom: 20px;
+  overflow: hidden;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress-bar {
+  float: left;
+  width: 0;
+  height: 100%;
+  font-size: 12px;
+  color: #ffffff;
+  text-align: center;
+  background-color: #428bca;
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-transition: width 0.6s ease;
+          transition: width 0.6s ease;
+}
+
+.progress-striped .progress-bar {
+  background-color: #428bca;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-size: 40px 40px;
+}
+
+.progress.active .progress-bar {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+     -moz-animation: progress-bar-stripes 2s linear infinite;
+      -ms-animation: progress-bar-stripes 2s linear infinite;
+       -o-animation: progress-bar-stripes 2s linear infinite;
+          animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-bar-danger {
+  background-color: #d9534f;
+}
+
+.progress-striped .progress-bar-danger {
+  background-color: #d9534f;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-success {
+  background-color: #5cb85c;
+}
+
+.progress-striped .progress-bar-success {
+  background-color: #5cb85c;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-warning {
+  background-color: #f0ad4e;
+}
+
+.progress-striped .progress-bar-warning {
+  background-color: #f0ad4e;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-info {
+  background-color: #5bc0de;
+}
+
+.progress-striped .progress-bar-info {
+  background-color: #5bc0de;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.accordion {
+  margin-bottom: 20px;
+}
+
+.accordion-group {
+  margin-bottom: 2px;
+  border: 1px solid #e5e5e5;
+  border-radius: 4px;
+}
+
+.accordion-heading {
+  border-bottom: 0;
+}
+
+.accordion-heading .accordion-toggle {
+  display: block;
+  padding: 8px 15px;
+  cursor: pointer;
+}
+
+.accordion-inner {
+  padding: 9px 15px;
+  border-top: 1px solid #e5e5e5;
+}
+
+.carousel {
+  position: relative;
+}
+
+.carousel-inner {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+
+.carousel-inner > .item {
+  position: relative;
+  display: none;
+  -webkit-transition: 0.6s ease-in-out left;
+          transition: 0.6s ease-in-out left;
+}
+
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  display: inline-block;
+  height: auto;
+  max-width: 100%;
+  line-height: 1;
+}
+
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  display: block;
+}
+
+.carousel-inner > .active {
+  left: 0;
+}
+
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+
+.carousel-inner > .next {
+  left: 100%;
+}
+
+.carousel-inner > .prev {
+  left: -100%;
+}
+
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+  left: 0;
+}
+
+.carousel-inner > .active.left {
+  left: -100%;
+}
+
+.carousel-inner > .active.right {
+  left: 100%;
+}
+
+.carousel-control {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 15%;
+  font-size: 20px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.carousel-control.left {
+  background-color: rgba(0, 0, 0, 0.0001);
+  background-color: transparent;
+  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001)));
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%));
+  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+}
+
+.carousel-control.right {
+  right: 0;
+  left: auto;
+  background-color: rgba(0, 0, 0, 0.5);
+  background-color: transparent;
+  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5)));
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%));
+  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+}
+
+.carousel-control:hover,
+.carousel-control:focus {
+  color: #ffffff;
+  text-decoration: none;
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+
+.carousel-control .glyphicon,
+.carousel-control .icon-prev,
+.carousel-control .icon-next {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  z-index: 5;
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  margin-top: -10px;
+  margin-left: -10px;
+  font-family: serif;
+}
+
+.carousel-control .icon-prev:before {
+  content: '\2039';
+}
+
+.carousel-control .icon-next:before {
+  content: '\203a';
+}
+
+.carousel-indicators {
+  position: absolute;
+  bottom: 10px;
+  left: 50%;
+  z-index: 15;
+  width: 120px;
+  padding-left: 0;
+  margin-left: -60px;
+  text-align: center;
+  list-style: none;
+}
+
+.carousel-indicators li {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  margin: 1px;
+  text-indent: -999px;
+  cursor: pointer;
+  border: 1px solid #ffffff;
+  border-radius: 10px;
+}
+
+.carousel-indicators .active {
+  width: 12px;
+  height: 12px;
+  margin: 0;
+  background-color: #ffffff;
+}
+
+.carousel-caption {
+  position: absolute;
+  right: 15%;
+  bottom: 20px;
+  left: 15%;
+  z-index: 10;
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+}
+
+.carousel-caption .btn {
+  text-shadow: none;
+}
+
+@media screen and (min-width: 768px) {
+  .carousel-control .glyphicon,
+  .carousel-control .icon-prev,
+  .carousel-control .icon-next {
+    width: 30px;
+    height: 30px;
+    margin-top: -15px;
+    margin-left: -15px;
+    font-size: 30px;
+  }
+  .carousel-caption {
+    right: 20%;
+    left: 20%;
+    padding-bottom: 30px;
+  }
+  .carousel-indicators {
+    bottom: 20px;
+  }
+}
+
+.jumbotron {
+  padding: 30px;
+  margin-bottom: 30px;
+  font-size: 21px;
+  font-weight: 200;
+  line-height: 2.1428571435;
+  color: inherit;
+  background-color: #eeeeee;
+}
+
+.jumbotron h1 {
+  line-height: 1;
+  color: inherit;
+}
+
+.jumbotron p {
+  line-height: 1.4;
+}
+
+@media screen and (min-width: 768px) {
+  .jumbotron {
+    padding: 50px 60px;
+    border-radius: 6px;
+  }
+  .jumbotron h1 {
+    font-size: 63px;
+  }
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: " ";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.pull-right {
+  float: right;
+}
+
+.pull-left {
+  float: left;
+}
+
+.hide {
+  display: none !important;
+}
+
+.show {
+  display: block !important;
+}
+
+.invisible {
+  visibility: hidden;
+}
+
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.affix {
+  position: fixed;
+}
+
+@-ms-viewport {
+  width: device-width;
+}
+
+@media screen and (max-width: 400px) {
+  @-ms-viewport {
+    width: 320px;
+  }
+}
+
+.hidden {
+  display: none !important;
+  visibility: hidden !important;
+}
+
+.visible-sm {
+  display: block !important;
+}
+
+tr.visible-sm {
+  display: table-row !important;
+}
+
+th.visible-sm,
+td.visible-sm {
+  display: table-cell !important;
+}
+
+.visible-md {
+  display: none !important;
+}
+
+tr.visible-md {
+  display: none !important;
+}
+
+th.visible-md,
+td.visible-md {
+  display: none !important;
+}
+
+.visible-lg {
+  display: none !important;
+}
+
+tr.visible-lg {
+  display: none !important;
+}
+
+th.visible-lg,
+td.visible-lg {
+  display: none !important;
+}
+
+.hidden-sm {
+  display: none !important;
+}
+
+tr.hidden-sm {
+  display: none !important;
+}
+
+th.hidden-sm,
+td.hidden-sm {
+  display: none !important;
+}
+
+.hidden-md {
+  display: block !important;
+}
+
+tr.hidden-md {
+  display: table-row !important;
+}
+
+th.hidden-md,
+td.hidden-md {
+  display: table-cell !important;
+}
+
+.hidden-lg {
+  display: block !important;
+}
+
+tr.hidden-lg {
+  display: table-row !important;
+}
+
+th.hidden-lg,
+td.hidden-lg {
+  display: table-cell !important;
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm {
+    display: none !important;
+  }
+  tr.visible-sm {
+    display: none !important;
+  }
+  th.visible-sm,
+  td.visible-sm {
+    display: none !important;
+  }
+  .visible-md {
+    display: block !important;
+  }
+  tr.visible-md {
+    display: table-row !important;
+  }
+  th.visible-md,
+  td.visible-md {
+    display: table-cell !important;
+  }
+  .visible-lg {
+    display: none !important;
+  }
+  tr.visible-lg {
+    display: none !important;
+  }
+  th.visible-lg,
+  td.visible-lg {
+    display: none !important;
+  }
+  .hidden-sm {
+    display: block !important;
+  }
+  tr.hidden-sm {
+    display: table-row !important;
+  }
+  th.hidden-sm,
+  td.hidden-sm {
+    display: table-cell !important;
+  }
+  .hidden-md {
+    display: none !important;
+  }
+  tr.hidden-md {
+    display: none !important;
+  }
+  th.hidden-md,
+  td.hidden-md {
+    display: none !important;
+  }
+  .hidden-lg {
+    display: block !important;
+  }
+  tr.hidden-lg {
+    display: table-row !important;
+  }
+  th.hidden-lg,
+  td.hidden-lg {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .visible-sm {
+    display: none !important;
+  }
+  tr.visible-sm {
+    display: none !important;
+  }
+  th.visible-sm,
+  td.visible-sm {
+    display: none !important;
+  }
+  .visible-md {
+    display: none !important;
+  }
+  tr.visible-md {
+    display: none !important;
+  }
+  th.visible-md,
+  td.visible-md {
+    display: none !important;
+  }
+  .visible-lg {
+    display: block !important;
+  }
+  tr.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-lg,
+  td.visible-lg {
+    display: table-cell !important;
+  }
+  .hidden-sm {
+    display: block !important;
+  }
+  tr.hidden-sm {
+    display: table-row !important;
+  }
+  th.hidden-sm,
+  td.hidden-sm {
+    display: table-cell !important;
+  }
+  .hidden-md {
+    display: block !important;
+  }
+  tr.hidden-md {
+    display: table-row !important;
+  }
+  th.hidden-md,
+  td.hidden-md {
+    display: table-cell !important;
+  }
+  .hidden-lg {
+    display: none !important;
+  }
+  tr.hidden-lg {
+    display: none !important;
+  }
+  th.hidden-lg,
+  td.hidden-lg {
+    display: none !important;
+  }
+}
+
+.visible-print {
+  display: none !important;
+}
+
+tr.visible-print {
+  display: none !important;
+}
+
+th.visible-print,
+td.visible-print {
+  display: none !important;
+}
+
+@media print {
+  .visible-print {
+    display: block !important;
+  }
+  tr.visible-print {
+    display: table-row !important;
+  }
+  th.visible-print,
+  td.visible-print {
+    display: table-cell !important;
+  }
+  .hidden-print {
+    display: none !important;
+  }
+  tr.hidden-print {
+    display: none !important;
+  }
+  th.hidden-print,
+  td.hidden-print {
+    display: none !important;
+  }
+}
\ No newline at end of file
diff --git a/bitbake/lib/webhob/bldviewer/static/js/bootstrap.js b/bitbake/lib/webhob/bldviewer/static/js/bootstrap.js
new file mode 100644
index 0000000..29f4eea
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/static/js/bootstrap.js
@@ -0,0 +1,1982 @@
+/**
+* bootstrap.js v3.0.0 by @fat and @mdo
+* Copyright 2013 Twitter Inc.
+* http://www.apache.org/licenses/LICENSE-2.0
+*/
+if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#transitions
+ * ========================================================================
+ * Copyright 2013 Twitter, 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 ($) { "use strict";
+
+  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+  // ============================================================
+
+  function transitionEnd() {
+    var el = document.createElement('bootstrap')
+
+    var transEndEventNames = {
+      'WebkitTransition' : 'webkitTransitionEnd'
+    , 'MozTransition'    : 'transitionend'
+    , 'OTransition'      : 'oTransitionEnd otransitionend'
+    , 'transition'       : 'transitionend'
+    }
+
+    for (var name in transEndEventNames) {
+      if (el.style[name] !== undefined) {
+        return { end: transEndEventNames[name] }
+      }
+    }
+  }
+
+  // http://blog.alexmaccaw.com/css-transitions
+  $.fn.emulateTransitionEnd = function (duration) {
+    var called = false, $el    = this
+    $(this).one($.support.transition.end, function () { called = true })
+    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+    setTimeout(callback, duration)
+    return this
+  }
+
+  $(function () {
+    $.support.transition = transitionEnd()
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#alerts
+ * ========================================================================
+ * Copyright 2013 Twitter, 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 ($) { "use strict";
+
+  // ALERT CLASS DEFINITION
+  // ======================
+
+  var dismiss = '[data-dismiss="alert"]'
+  var Alert   = function (el) {
+    $(el).on('click', dismiss, this.close)
+  }
+
+  Alert.prototype.close = function (e) {
+    var $this    = $(this)
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = $(selector)
+
+    if (e) e.preventDefault()
+
+    if (!$parent.length) {
+      $parent = $this.hasClass('alert') ? $this : $this.parent()
+    }
+
+    $parent.trigger(e = $.Event('close.bs.alert'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      $parent.trigger('closed.bs.alert').remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent
+        .one($.support.transition.end, removeElement)
+        .emulateTransitionEnd(150) :
+      removeElement()
+  }
+
+
+  // ALERT PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.alert
+
+  $.fn.alert = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.alert')
+
+      if (!data) $this.data('bs.alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.alert.Constructor = Alert
+
+
+  // ALERT NO CONFLICT
+  // =================
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+  // ALERT DATA-API
+  // ==============
+
+  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#buttons
+ * ========================================================================
+ * Copyright 2013 Twitter, 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 ($) { "use strict";
+
+  // BUTTON PUBLIC CLASS DEFINITION
+  // ==============================
+
+  var Button = function (element, options) {
+    this.$element = $(element)
+    this.options  = $.extend({}, Button.DEFAULTS, options)
+  }
+
+  Button.DEFAULTS = {
+    loadingText: 'loading...'
+  }
+
+  Button.prototype.setState = function (state) {
+    var d    = 'disabled'
+    var $el  = this.$element
+    var val  = $el.is('input') ? 'val' : 'html'
+    var data = $el.data()
+
+    state = state + 'Text'
+
+    if (!data.resetText) $el.data('resetText', $el[val]())
+
+    $el[val](data[state] || this.options[state])
+
+    // push to event loop to allow forms to submit
+    setTimeout(function () {
+      state == 'loadingText' ?
+        $el.addClass(d).attr(d, d) :
+        $el.removeClass(d).removeAttr(d);
+    }, 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+    if ($parent.length) {
+      var $input = this.$element.find('input')
+        .prop('checked', !this.$element.hasClass('active'))
+        .trigger('change')
+      if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active')
+    }
+
+    this.$element.toggleClass('active')
+  }
+
+
+  // BUTTON PLUGIN DEFINITION
+  // ========================
+
+  var old = $.fn.button
+
+  $.fn.button = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.button')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  $.fn.button.Constructor = Button
+
+
+  // BUTTON NO CONFLICT
+  // ==================
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+  // BUTTON DATA-API
+  // ===============
+
+  $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {
+    var $btn = $(e.target)
+    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+    $btn.button('toggle')
+    e.preventDefault()
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#carousel
+ * ========================================================================
+ * Copyright 2012 Twitter, 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 ($) { "use strict";
+
+  // CAROUSEL CLASS DEFINITION
+  // =========================
+
+  var Carousel = function (element, options) {
+    this.$element    = $(element)
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options     = options
+    this.paused      =
+    this.sliding     =
+    this.interval    =
+    this.$active     =
+    this.$items      = null
+
+    this.options.pause == 'hover' && this.$element
+      .on('mouseenter', $.proxy(this.pause, this))
+      .on('mouseleave', $.proxy(this.cycle, this))
+  }
+
+  Carousel.DEFAULTS = {
+    interval: 5000
+  , pause: 'hover'
+  }
+
+  Carousel.prototype.cycle =  function (e) {
+    e || (this.paused = false)
+
+    this.interval && clearInterval(this.interval)
+
+    this.options.interval
+      && !this.paused
+      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+    return this
+  }
+
+  Carousel.prototype.getActiveIndex = function () {
+    this.$active = this.$element.find('.item.active')
+    this.$items  = this.$active.parent().children()
+
+    return this.$items.index(this.$active)
+  }
+
+  Carousel.prototype.to = function (pos) {
+    var that        = this
+    var activeIndex = this.getActiveIndex()
+
+    if (pos > (this.$items.length - 1) || pos < 0) return
+
+    if (this.sliding)       return this.$element.one('slid', function () { that.to(pos) })
+    if (activeIndex == pos) return this.pause().cycle()
+
+    return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+  }
+
+  Carousel.prototype.pause = function (e) {
+    e || (this.paused = true)
+
+    if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+      this.$element.trigger($.support.transition.end)
+      this.cycle(true)
+    }
+
+    this.interval = clearInterval(this.interval)
+
+    return this
+  }
+
+  Carousel.prototype.next = function () {
+    if (this.sliding) return
+    return this.slide('next')
+  }
+
+  Carousel.prototype.prev = function () {
+    if (this.sliding) return
+    return this.slide('prev')
+  }
+
+  Carousel.prototype.slide = function (type, next) {
+    var $active   = this.$element.find('.item.active')
+    var $next     = next || $active[type]()
+    var isCycling = this.interval
+    var direction = type == 'next' ? 'left' : 'right'
+    var fallback  = type == 'next' ? 'first' : 'last'
+    var that      = this
+
+    this.sliding = true
+
+    isCycling && this.pause()
+
+    $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+    var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })
+
+    if ($next.hasClass('active')) return
+
+    if (this.$indicators.length) {
+      this.$indicators.find('.active').removeClass('active')
+      this.$element.one('slid', function () {
+        var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
+        $nextIndicator && $nextIndicator.addClass('active')
+      })
+    }
+
+    if ($.support.transition && this.$element.hasClass('slide')) {
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+      $next.addClass(type)
+      $next[0].offsetWidth // force reflow
+      $active.addClass(direction)
+      $next.addClass(direction)
+      $active
+        .one($.support.transition.end, function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () { that.$element.trigger('slid') }, 0)
+        })
+        .emulateTransitionEnd(600)
+    } else {
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+      $active.removeClass('active')
+      $next.addClass('active')
+      this.sliding = false
+      this.$element.trigger('slid')
+    }
+
+    isCycling && this.cycle()
+
+    return this
+  }
+
+
+  // CAROUSEL PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.carousel
+
+  $.fn.carousel = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.carousel')
+      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+      var action  = typeof option == 'string' ? option : options.slide
+
+      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  $.fn.carousel.Constructor = Carousel
+
+
+  // CAROUSEL NO CONFLICT
+  // ====================
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+
+  // CAROUSEL DATA-API
+  // =================
+
+  $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+    var $this   = $(this), href
+    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+    var options = $.extend({}, $target.data(), $this.data())
+    var slideIndex = $this.attr('data-slide-to')
+    if (slideIndex) options.interval = false
+
+    $target.carousel(options)
+
+    if (slideIndex = $this.attr('data-slide-to')) {
+      $target.data('bs.carousel').to(slideIndex)
+    }
+
+    e.preventDefault()
+  })
+
+  $(window).on('load', function () {
+    $('[data-ride="carousel"]').each(function () {
+      var $carousel = $(this)
+      $carousel.carousel($carousel.data())
+    })
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#collapse
+ * ========================================================================
+ * Copyright 2012 Twitter, 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 ($) { "use strict";
+
+  // COLLAPSE PUBLIC CLASS DEFINITION
+  // ================================
+
+  var Collapse = function (element, options) {
+    this.$element      = $(element)
+    this.options       = $.extend({}, Collapse.DEFAULTS, options)
+    this.transitioning = null
+
+    if (this.options.parent) this.$parent = $(this.options.parent)
+    if (this.options.toggle) this.toggle()
+  }
+
+  Collapse.DEFAULTS = {
+    toggle: true
+  }
+
+  Collapse.prototype.dimension = function () {
+    var hasWidth = this.$element.hasClass('width')
+    return hasWidth ? 'width' : 'height'
+  }
+
+  Collapse.prototype.show = function () {
+    if (this.transitioning || this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('show.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+    if (actives && actives.length) {
+      var hasData = actives.data('bs.collapse')
+      if (hasData && hasData.transitioning) return
+      actives.collapse('hide')
+      hasData || actives.data('bs.collapse', null)
+    }
+
+    var dimension = this.dimension()
+
+    this.$element
+      .removeClass('collapse')
+      .addClass('collapsing')
+      [dimension](0)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.$element
+        .removeClass('collapsing')
+        .addClass('in')
+        [dimension]('auto')
+      this.transitioning = 0
+      this.$element.trigger('shown.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+    this.$element
+      .one($.support.transition.end, $.proxy(complete, this))
+      .emulateTransitionEnd(350)
+      [dimension](this.$element[0][scrollSize])
+  }
+
+  Collapse.prototype.hide = function () {
+    if (this.transitioning || !this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('hide.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var dimension = this.dimension()
+
+    this.$element
+      [dimension](this.$element[dimension]())
+      [0].offsetHeight
+
+    this.$element
+      .addClass('collapsing')
+      .removeClass('collapse')
+      .removeClass('in')
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.transitioning = 0
+      this.$element
+        .trigger('hidden.bs.collapse')
+        .removeClass('collapsing')
+        .addClass('collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    this.$element
+      [dimension](0)
+      .one($.support.transition.end, $.proxy(complete, this))
+      .emulateTransitionEnd(350)
+  }
+
+  Collapse.prototype.toggle = function () {
+    this[this.$element.hasClass('in') ? 'hide' : 'show']()
+  }
+
+
+  // COLLAPSE PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.collapse
+
+  $.fn.collapse = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.collapse')
+      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.collapse.Constructor = Collapse
+
+
+  // COLLAPSE NO CONFLICT
+  // ====================
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+  // COLLAPSE DATA-API
+  // =================
+
+  $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) {
+    var $this   = $(this), href
+    var target  = $this.attr('data-target')
+        || e.preventDefault()
+        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+    var $target = $(target)
+    var data    = $target.data('bs.collapse')
+    var option  = data ? 'toggle' : $this.data()
+    var parent  = $this.attr('data-parent')
+    var $parent = parent && $(parent)
+
+    if (!data || !data.transitioning) {
+      if ($parent) $parent.find('[data-toggle=collapse][data-parent=' + parent + ']').not($this).addClass('collapsed')
+      $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+    }
+
+    $target.collapse(option)
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#dropdowns
+ * ========================================================================
+ * Copyright 2012 Twitter, 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 ($) { "use strict";
+
+  // DROPDOWN CLASS DEFINITION
+  // =========================
+
+  var backdrop = '.dropdown-backdrop'
+  var toggle   = '[data-toggle=dropdown]'
+  var Dropdown = function (element) {
+    var $el = $(element).on('click.bs.dropdown', this.toggle)
+  }
+
+  Dropdown.prototype.toggle = function (e) {
+    var $this = $(this)
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    clearMenus()
+
+    if (!isActive) {
+      if ('ontouchstart' in document.documentElement) {
+        // if mobile we we use a backdrop because click events don't delegate
+        $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
+      }
+
+      $parent.trigger(e = $.Event('show.bs.dropdown'))
+
+      if (e.isDefaultPrevented()) return
+
+      $parent
+        .toggleClass('open')
+        .trigger('shown.bs.dropdown')
+    }
+
+    $this.focus()
+
+    return false
+  }
+
+  Dropdown.prototype.keydown = function (e) {
+    if (!/(38|40|27)/.test(e.keyCode)) return
+
+    var $this = $(this)
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    if (!isActive || (isActive && e.keyCode == 27)) {
+      if (e.which == 27) $parent.find(toggle).focus()
+      return $this.click()
+    }
+
+    var $items = $('[role=menu] li:not(.divider):visible a', $parent)
+
+    if (!$items.length) return
+
+    var index = $items.index($items.filter(':focus'))
+
+    if (e.keyCode == 38 && index > 0)                 index--                        // up
+    if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
+    if (!~index)                                      index=0
+
+    $items.eq(index).focus()
+  }
+
+  function clearMenus() {
+    $(backdrop).remove()
+    $(toggle).each(function (e) {
+      var $parent = getParent($(this))
+      if (!$parent.hasClass('open')) return
+      $parent.trigger(e = $.Event('hide.bs.dropdown'))
+      if (e.isDefaultPrevented()) return
+      $parent.removeClass('open').trigger('hidden.bs.dropdown')
+    })
+  }
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    var $parent = selector && $(selector)
+
+    return $parent && $parent.length ? $parent : $this.parent()
+  }
+
+
+  // DROPDOWN PLUGIN DEFINITION
+  // ==========================
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('dropdown')
+
+      if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  // DROPDOWN NO CONFLICT
+  // ====================
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  // APPLY TO STANDARD DROPDOWN ELEMENTS
+  // ===================================
+
+  $(document)
+    .on('click.bs.dropdown.data-api', clearMenus)
+    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.bs.dropdown.data-api'  , toggle, Dropdown.prototype.toggle)
+    .on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#modals
+ * ========================================================================
+ * Copyright 2012 Twitter, 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 ($) { "use strict";
+
+  // MODAL CLASS DEFINITION
+  // ======================
+
+  var Modal = function (element, options) {
+    this.options   = options
+    this.$element  = $(element).on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+    this.$backdrop =
+    this.isShown   = null
+
+    if (this.options.remote) this.$element.find('.modal-body').load(this.options.remote)
+  }
+
+  Modal.DEFAULTS = {
+      backdrop: true
+    , keyboard: true
+    , show: true
+  }
+
+  Modal.prototype.toggle = function () {
+    return this[!this.isShown ? 'show' : 'hide']()
+  }
+
+  Modal.prototype.show = function () {
+    var that = this
+    var e    = $.Event('show.bs.modal')
+
+    this.$element.trigger(e)
+
+    if (this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = true
+
+    this.escape()
+
+    this.backdrop(function () {
+      var transition = $.support.transition && that.$element.hasClass('fade')
+
+      if (!that.$element.parent().length) {
+        that.$element.appendTo(document.body) // don't move modals dom position
+      }
+
+      that.$element.show()
+
+      if (transition) {
+        that.$element[0].offsetWidth // force reflow
+      }
+
+      that.$element
+        .addClass('in')
+        .attr('aria-hidden', false)
+
+      that.enforceFocus()
+
+      transition ?
+        that.$element
+          .one($.support.transition.end, function () {
+            that.$element.focus().trigger('shown.bs.modal')
+          })
+          .emulateTransitionEnd(300) :
+        that.$element.focus().trigger('shown.bs.modal')
+    })
+  }
+
+  Modal.prototype.hide = function (e) {
+    if (e) e.preventDefault()
+
+    e = $.Event('hide.bs.modal')
+
+    this.$element.trigger(e)
+
+    if (!this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = false
+
+    this.escape()
+
+    $(document).off('focusin.bs.modal')
+
+    this.$element
+      .removeClass('in')
+      .attr('aria-hidden', true)
+
+    $.support.transition && this.$element.hasClass('fade') ?
+      this.$element
+        .one($.support.transition.end, $.proxy(this.hideModal, this))
+        .emulateTransitionEnd(300) :
+      this.hideModal()
+  }
+
+  Modal.prototype.enforceFocus = function () {
+    $(document)
+      .off('focusin.bs.modal') // guard against infinite focus loop
+      .on('focusin.bs.modal', $.proxy(function (e) {
+        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+          this.$element.focus()
+        }
+      }, this))
+  }
+
+  Modal.prototype.escape = function () {
+    if (this.isShown && this.options.keyboard) {
+      this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
+        e.which == 27 && this.hide()
+      }, this))
+    } else if (!this.isShown) {
+      this.$element.off('keyup.dismiss.bs.modal')
+    }
+  }
+
+  Modal.prototype.hideModal = function () {
+    var that = this
+    this.$element.hide()
+    this.backdrop(function () {
+      that.removeBackdrop()
+      that.$element.trigger('hidden.bs.modal')
+    })
+  }
+
+  Modal.prototype.removeBackdrop = function () {
+    this.$backdrop && this.$backdrop.remove()
+    this.$backdrop = null
+  }
+
+  Modal.prototype.backdrop = function (callback) {
+    var that    = this
+    var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+    if (this.isShown && this.options.backdrop) {
+      var doAnimate = $.support.transition && animate
+
+      this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+        .appendTo(document.body)
+
+      this.$element.on('click', $.proxy(function (e) {
+        if (e.target !== e.currentTarget) return
+        this.options.backdrop == 'static'
+          ? this.$element[0].focus.call(this.$element[0])
+          : this.hide.call(this)
+      }, this))
+
+      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+      this.$backdrop.addClass('in')
+
+      if (!callback) return
+
+      doAnimate ?
+        this.$backdrop
+          .one($.support.transition.end, callback)
+          .emulateTransitionEnd(150) :
+        callback()
+
+    } else if (!this.isShown && this.$backdrop) {
+      this.$backdrop.removeClass('in')
+
+      $.support.transition && this.$element.hasClass('fade')?
+        this.$backdrop
+          .one($.support.transition.end, callback)
+          .emulateTransitionEnd(150) :
+        callback()
+
+    } else if (callback) {
+      callback()
+    }
+  }
+
+
+  // MODAL PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.modal
+
+  $.fn.modal = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.modal')
+      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option]()
+      else if (options.show) data.show()
+    })
+  }
+
+  $.fn.modal.Constructor = Modal
+
+
+  // MODAL NO CONFLICT
+  // =================
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+  // MODAL DATA-API
+  // ==============
+
+  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this   = $(this)
+    var href    = $this.attr('href')
+    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+    var option  = $target.data('modal') ? 'toggle' : $.extend({ remote:!/#/.test(href) && href }, $target.data(), $this.data())
+
+    e.preventDefault()
+
+    $target
+      .modal(option)
+      .one('hide', function () {
+        $this.is(':visible') && $this.focus()
+      })
+  })
+
+  $(function () {
+    var $body = $(document.body)
+      .on('shown.bs.modal',  '.modal', function () { $body.addClass('modal-open') })
+      .on('hidden.bs.modal', '.modal', function () { $body.removeClass('modal-open') })
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#affix
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2012 Twitter, 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 ($) { "use strict";
+
+  // TOOLTIP PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Tooltip = function (element, options) {
+    this.type       =
+    this.options    =
+    this.enabled    =
+    this.timeout    =
+    this.hoverState =
+    this.$element   = null
+
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.DEFAULTS = {
+    animation: true
+  , placement: 'top'
+  , selector: false
+  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+  , trigger: 'hover focus'
+  , title: ''
+  , delay: 0
+  , html: false
+  , container: false
+  }
+
+  Tooltip.prototype.init = function (type, element, options) {
+    this.enabled  = true
+    this.type     = type
+    this.$element = $(element)
+    this.options  = this.getOptions(options)
+
+    var triggers = this.options.trigger.split(' ')
+
+    for (var i = triggers.length; i--;) {
+      var trigger = triggers[i]
+
+      if (trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (trigger != 'manual') {
+        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focus'
+        var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
+
+        this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+    }
+
+    this.options.selector ?
+      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+      this.fixTitle()
+  }
+
+  Tooltip.prototype.getDefaults = function () {
+    return Tooltip.DEFAULTS
+  }
+
+  Tooltip.prototype.getOptions = function (options) {
+    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+    if (options.delay && typeof options.delay == 'number') {
+      options.delay = {
+        show: options.delay
+      , hide: options.delay
+      }
+    }
+
+    return options
+  }
+
+  Tooltip.prototype.enter = function (obj) {
+    var defaults = this.getDefaults()
+    var options  = {}
+
+    this._options && $.each(this._options, function (key, value) {
+      if (defaults[key] != value) options[key] = value
+    })
+
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget)[this.type](options).data('bs.' + this.type)
+
+    clearTimeout(self.timeout)
+
+    if (!self.options.delay || !self.options.delay.show) return self.show()
+
+    self.hoverState = 'in'
+    self.timeout    = setTimeout(function () {
+      if (self.hoverState == 'in') self.show()
+    }, self.options.delay.show)
+  }
+
+  Tooltip.prototype.leave = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget)[this.type](this._options).data('bs.' + this.type)
+
+    clearTimeout(self.timeout)
+
+    if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+    self.hoverState = 'out'
+    self.timeout    = setTimeout(function () {
+      if (self.hoverState == 'out') self.hide()
+    }, self.options.delay.hide)
+  }
+
+  Tooltip.prototype.show = function () {
+    var e = $.Event('show.bs.'+ this.type)
+
+    if (this.hasContent() && this.enabled) {
+      this.$element.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      var $tip = this.tip()
+
+      this.setContent()
+
+      if (this.options.animation) $tip.addClass('fade')
+
+      var placement = typeof this.options.placement == 'function' ?
+        this.options.placement.call(this, $tip[0], this.$element[0]) :
+        this.options.placement
+
+      var autoToken = /\s?auto?\s?/i
+      var autoPlace = autoToken.test(placement)
+      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+      $tip
+        .detach()
+        .css({ top: 0, left: 0, display: 'block' })
+        .addClass(placement)
+
+      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+      var pos          = this.getPosition()
+      var actualWidth  = $tip[0].offsetWidth
+      var actualHeight = $tip[0].offsetHeight
+
+      if (autoPlace) {
+        var $parent = this.$element.parent()
+
+        var orgPlacement = placement
+        var docScroll    = document.documentElement.scrollTop || document.body.scrollTop
+        var parentWidth  = this.options.container == 'body' ? window.innerWidth  : $parent.outerWidth()
+        var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
+        var parentLeft   = this.options.container == 'body' ? 0 : $parent.offset().left
+
+        placement = placement == 'bottom' && pos.top   + pos.height  + actualHeight - docScroll > parentHeight  ? 'top'    :
+                    placement == 'top'    && pos.top   - docScroll   - actualHeight < 0                         ? 'bottom' :
+                    placement == 'right'  && pos.right + actualWidth > parentWidth                              ? 'left'   :
+                    placement == 'left'   && pos.left  - actualWidth < parentLeft                               ? 'right'  :
+                    placement
+
+        $tip
+          .removeClass(orgPlacement)
+          .addClass(placement)
+      }
+
+      var calculatedOffset = this.getCalcuatedOffset(placement, pos, actualWidth, actualHeight)
+
+      this.applyPlacement(calculatedOffset, placement)
+      this.$element.trigger('shown.bs.' + this.type)
+    }
+  }
+
+  Tooltip.prototype.applyPlacement = function(offset, placement) {
+    var replace
+    var $tip   = this.tip()
+    var width  = $tip[0].offsetWidth
+    var height = $tip[0].offsetHeight
+
+    // manually read margins because getBoundingClientRect includes difference
+    var marginTop = parseInt($tip.css('margin-top'), 10)
+    var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+    // we must check for NaN for ie 8/9
+    if (isNaN(marginTop))  marginTop  = 0
+    if (isNaN(marginLeft)) marginLeft = 0
+
+    offset.top  = offset.top  + marginTop
+    offset.left = offset.left + marginLeft
+
+    $tip
+      .offset(offset)
+      .addClass('in')
+
+    // check to see if placing tip in new offset caused the tip to resize itself
+    var actualWidth  = $tip[0].offsetWidth
+    var actualHeight = $tip[0].offsetHeight
+
+    if (placement == 'top' && actualHeight != height) {
+      replace = true
+      offset.top = offset.top + height - actualHeight
+    }
+
+    if (/bottom|top/.test(placement)) {
+      var delta = 0
+
+      if (offset.left < 0) {
+        delta       = offset.left * -2
+        offset.left = 0
+
+        $tip.offset(offset)
+
+        actualWidth  = $tip[0].offsetWidth
+        actualHeight = $tip[0].offsetHeight
+      }
+
+      this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
+    } else {
+      this.replaceArrow(actualHeight - height, actualHeight, 'top')
+    }
+
+    if (replace) $tip.offset(offset)
+  }
+
+  Tooltip.prototype.replaceArrow = function(delta, dimension, position) {
+    this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
+  }
+
+  Tooltip.prototype.setContent = function () {
+    var $tip  = this.tip()
+    var title = this.getTitle()
+
+    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+    $tip.removeClass('fade in top bottom left right')
+  }
+
+  Tooltip.prototype.hide = function () {
+    var that = this
+    var $tip = this.tip()
+    var e    = $.Event('hide.bs.' + this.type)
+
+    function complete() { $tip.detach() }
+
+    this.$element.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    $tip.removeClass('in')
+
+    $.support.transition && this.$tip.hasClass('fade') ?
+      $tip
+        .one($.support.transition.end, complete)
+        .emulateTransitionEnd(150) :
+      complete()
+
+    this.$element.trigger('hidden.bs.' + this.type)
+
+    return this
+  }
+
+  Tooltip.prototype.fixTitle = function () {
+    var $e = this.$element
+    if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+    }
+  }
+
+  Tooltip.prototype.hasContent = function () {
+    return this.getTitle()
+  }
+
+  Tooltip.prototype.getPosition = function () {
+    var el = this.$element[0]
+    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
+      width: el.offsetWidth
+    , height: el.offsetHeight
+    }, this.$element.offset())
+  }
+
+  Tooltip.prototype.getCalcuatedOffset = function (placement, pos, actualWidth, actualHeight) {
+    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }
+  }
+
+  Tooltip.prototype.getTitle = function () {
+    var title
+    var $e = this.$element
+    var o  = this.options
+
+    title = $e.attr('data-original-title')
+      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+    return title
+  }
+
+  Tooltip.prototype.tip = function () {
+    return this.$tip = this.$tip || $(this.options.template)
+  }
+
+  Tooltip.prototype.arrow = function () {
+    return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
+  }
+
+  Tooltip.prototype.validate = function () {
+    if (!this.$element[0].parentNode) {
+      this.hide()
+      this.$element = null
+      this.options  = null
+    }
+  }
+
+  Tooltip.prototype.enable = function () {
+    this.enabled = true
+  }
+
+  Tooltip.prototype.disable = function () {
+    this.enabled = false
+  }
+
+  Tooltip.prototype.toggleEnabled = function () {
+    this.enabled = !this.enabled
+  }
+
+  Tooltip.prototype.toggle = function (e) {
+    var self = e ? $(e.currentTarget)[this.type](this._options).data('bs.' + this.type) : this
+    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+  }
+
+  Tooltip.prototype.destroy = function () {
+    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
+  }
+
+
+  // TOOLTIP PLUGIN DEFINITION
+  // =========================
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.tooltip')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tooltip.Constructor = Tooltip
+
+
+  // TOOLTIP NO CONFLICT
+  // ===================
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#popovers
+ * ========================================================================
+ * Copyright 2012 Twitter, 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 ($) { "use strict";
+
+  // POPOVER PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+  Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, {
+    placement: 'right'
+  , trigger: 'click'
+  , content: ''
+  , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+  })
+
+
+  // NOTE: POPOVER EXTENDS tooltip.js
+  // ================================
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+  Popover.prototype.constructor = Popover
+
+  Popover.prototype.getDefaults = function () {
+    return Popover.DEFAULTS
+  }
+
+  Popover.prototype.setContent = function () {
+    var $tip    = this.tip()
+    var title   = this.getTitle()
+    var content = this.getContent()
+
+    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+    $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+    $tip.removeClass('fade top bottom left right in')
+
+    $tip.find('.popover-title:empty').hide()
+  }
+
+  Popover.prototype.hasContent = function () {
+    return this.getTitle() || this.getContent()
+  }
+
+  Popover.prototype.getContent = function () {
+    var $e = this.$element
+    var o  = this.options
+
+    return $e.attr('data-content')
+      || (typeof o.content == 'function' ?
+            o.content.call($e[0]) :
+            o.content)
+  }
+
+  Popover.prototype.arrow =function () {
+    return this.$arrow = this.$arrow || this.tip().find('.arrow')
+  }
+
+  Popover.prototype.tip = function () {
+    if (!this.$tip) this.$tip = $(this.options.template)
+    return this.$tip
+  }
+
+
+  // POPOVER PLUGIN DEFINITION
+  // =========================
+
+  var old = $.fn.popover
+
+  $.fn.popover = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.popover')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.popover.Constructor = Popover
+
+
+  // POPOVER NO CONFLICT
+  // ===================
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#scrollspy
+ * ========================================================================
+ * Copyright 2012 Twitter, 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 ($) { "use strict";
+
+  // SCROLLSPY CLASS DEFINITION
+  // ==========================
+
+  function ScrollSpy(element, options) {
+    var href
+    var process  = $.proxy(this.process, this)
+
+    this.$element       = $(element).is('body') ? $(window) : $(element)
+    this.$body          = $('body')
+    this.$scrollElement = this.$element.on('scroll.bs.scroll-spy.data-api', process)
+    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
+    this.selector       = (this.options.target
+      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      || '') + ' .nav li > a'
+    this.offsets        = $([])
+    this.targets        = $([])
+    this.activeTarget   = null
+
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.DEFAULTS = {
+    offset: 10
+  }
+
+  ScrollSpy.prototype.refresh = function () {
+    var offsetMethod = this.$element[0] == window ? 'offset' : 'position'
+
+    this.offsets = $([])
+    this.targets = $([])
+
+    var self     = this
+    var $targets = this.$body
+      .find(this.selector)
+      .map(function () {
+        var $el   = $(this)
+        var href  = $el.data('target') || $el.attr('href')
+        var $href = /^#\w/.test(href) && $(href)
+
+        return ($href
+          && $href.length
+          && [[ $href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]]) || null
+      })
+      .sort(function (a, b) { return a[0] - b[0] })
+      .each(function () {
+        self.offsets.push(this[0])
+        self.targets.push(this[1])
+      })
+  }
+
+  ScrollSpy.prototype.process = function () {
+    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
+    var scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+    var maxScroll    = scrollHeight - this.$scrollElement.height()
+    var offsets      = this.offsets
+    var targets      = this.targets
+    var activeTarget = this.activeTarget
+    var i
+
+    if (scrollTop >= maxScroll) {
+      return activeTarget != (i = targets.last()[0]) && this.activate(i)
+    }
+
+    for (i = offsets.length; i--;) {
+      activeTarget != targets[i]
+        && scrollTop >= offsets[i]
+        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+        && this.activate( targets[i] )
+    }
+  }
+
+  ScrollSpy.prototype.activate = function (target) {
+    this.activeTarget = target
+
+    $(this.selector)
+      .parents('.active')
+      .removeClass('active')
+
+    var selector = this.selector
+      + '[data-target="' + target + '"],'
+      + this.selector + '[href="' + target + '"]'
+
+    var active = $(selector)
+      .parents('li')
+      .addClass('active')
+
+    if (active.parent('.dropdown-menu').length)  {
+      active = active
+        .closest('li.dropdown')
+        .addClass('active')
+    }
+
+    active.trigger('activate')
+  }
+
+
+  // SCROLLSPY PLUGIN DEFINITION
+  // ===========================
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.scrollspy')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+
+  // SCROLLSPY NO CONFLICT
+  // =====================
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+  // SCROLLSPY DATA-API
+  // ==================
+
+  $(window).on('load', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      $spy.scrollspy($spy.data())
+    })
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#tabs
+ * ========================================================================
+ * Copyright 2012 Twitter, 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 ($) { "use strict";
+
+  // TAB CLASS DEFINITION
+  // ====================
+
+  var Tab = function (element) {
+    this.element = $(element)
+  }
+
+  Tab.prototype.show = function () {
+    var $this    = this.element
+    var $ul      = $this.closest('ul:not(.dropdown-menu)')
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    if ($this.parent('li').hasClass('active')) return
+
+    var previous = $ul.find('.active:last a')[0]
+    var e        = $.Event('show.bs.tab', {
+      relatedTarget: previous
+    })
+
+    $this.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    var $target = $(selector)
+
+    this.activate($this.parent('li'), $ul)
+    this.activate($target, $target.parent(), function () {
+      $this.trigger({
+        type: 'shown.bs.tab'
+      , relatedTarget: previous
+      })
+    })
+  }
+
+  Tab.prototype.activate = function (element, container, callback) {
+    var $active    = container.find('> .active')
+    var transition = callback
+      && $.support.transition
+      && $active.hasClass('fade')
+
+    function next() {
+      $active
+        .removeClass('active')
+        .find('> .dropdown-menu > .active')
+        .removeClass('active')
+
+      element.addClass('active')
+
+      if (transition) {
+        element[0].offsetWidth // reflow for transition
+        element.addClass('in')
+      } else {
+        element.removeClass('fade')
+      }
+
+      if (element.parent('.dropdown-menu')) {
+        element.closest('li.dropdown').addClass('active')
+      }
+
+      callback && callback()
+    }
+
+    transition ?
+      $active
+        .one($.support.transition.end, next)
+        .emulateTransitionEnd(150) :
+      next()
+
+    $active.removeClass('in')
+  }
+
+
+  // TAB PLUGIN DEFINITION
+  // =====================
+
+  var old = $.fn.tab
+
+  $.fn.tab = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.tab')
+
+      if (!data) $this.data('bs.tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tab.Constructor = Tab
+
+
+  // TAB NO CONFLICT
+  // ===============
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+  // TAB DATA-API
+  // ============
+
+  $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+    e.preventDefault()
+    $(this).tab('show')
+  })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#affix
+ * ========================================================================
+ * Copyright 2012 Twitter, 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 ($) { "use strict";
+
+  // AFFIX CLASS DEFINITION
+  // ======================
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, Affix.DEFAULTS, options)
+    this.$window = $(window)
+      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))
+
+    this.$element = $(element)
+    this.affixed  =
+    this.unpin    = null
+
+    this.checkPosition()
+  }
+
+  Affix.RESET = 'affix affix-top affix-bottom'
+
+  Affix.DEFAULTS = {
+    offset: 0
+  }
+
+  Affix.prototype.checkPositionWithEventLoop = function () {
+    setTimeout($.proxy(this.checkPosition, this), 1)
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var scrollHeight = $(document).height()
+    var scrollTop    = this.$window.scrollTop()
+    var position     = this.$element.offset()
+    var offset       = this.options.offset
+    var offsetTop    = offset.top
+    var offsetBottom = offset.bottom
+
+    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function')    offsetTop    = offset.top()
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+    var affix = this.unpin   != null && (scrollTop + this.unpin <= position.top) ? false :
+                offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
+                offsetTop    != null && (scrollTop <= offsetTop) ? 'top' : false
+
+    if (this.affixed === affix) return
+    if (this.unpin) this.$element.css('top', '')
+
+    this.affixed = affix
+    this.unpin   = affix == 'bottom' ? position.top - scrollTop : null
+
+    this.$element.removeClass(Affix.RESET).addClass('affix' + (affix ? '-' + affix : ''))
+
+    if (affix == 'bottom') {
+      this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() })
+    }
+  }
+
+
+  // AFFIX PLUGIN DEFINITION
+  // =======================
+
+  var old = $.fn.affix
+
+  $.fn.affix = function (option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.affix')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.affix.Constructor = Affix
+
+
+  // AFFIX NO CONFLICT
+  // =================
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+  // AFFIX DATA-API
+  // ==============
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+      var data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      if (data.offsetBottom) data.offset.bottom = data.offsetBottom
+      if (data.offsetTop)    data.offset.top    = data.offsetTop
+
+      $spy.affix(data)
+    })
+  })
+
+}(window.jQuery);
diff --git a/bitbake/lib/webhob/bldviewer/static/js/jquery-2.0.3.js b/bitbake/lib/webhob/bldviewer/static/js/jquery-2.0.3.js
new file mode 100644
index 0000000..ebc6c18
--- /dev/null
+++ b/bitbake/lib/webhob/bldviewer/static/js/jquery-2.0.3.js
@@ -0,0 +1,8829 @@
+/*!
+ * jQuery JavaScript Library v2.0.3
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2013-07-03T13:30Z
+ */
+(function( window, undefined ) {
+
+// Can't do this because several apps including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+// Support: Firefox 18+
+//"use strict";
+var
+	// A central reference to the root jQuery(document)
+	rootjQuery,
+
+	// The deferred used on DOM ready
+	readyList,
+
+	// Support: IE9
+	// For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
+	core_strundefined = typeof undefined,
+
+	// Use the correct document accordingly with window argument (sandbox)
+	location = window.location,
+	document = window.document,
+	docElem = document.documentElement,
+
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$,
+
+	// [[Class]] -> type pairs
+	class2type = {},
+
+	// List of deleted data cache ids, so we can reuse them
+	core_deletedIds = [],
+
+	core_version = "2.0.3",
+
+	// Save a reference to some core methods
+	core_concat = core_deletedIds.concat,
+	core_push = core_deletedIds.push,
+	core_slice = core_deletedIds.slice,
+	core_indexOf = core_deletedIds.indexOf,
+	core_toString = class2type.toString,
+	core_hasOwn = class2type.hasOwnProperty,
+	core_trim = core_version.trim,
+
+	// Define a local copy of jQuery
+	jQuery = function( selector, context ) {
+		// The jQuery object is actually just the init constructor 'enhanced'
+		return new jQuery.fn.init( selector, context, rootjQuery );
+	},
+
+	// Used for matching numbers
+	core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
+
+	// Used for splitting on whitespace
+	core_rnotwhite = /\S+/g,
+
+	// A simple way to check for HTML strings
+	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+	// Strict HTML recognition (#11290: must start with <)
+	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+	// Match a standalone tag
+	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+	// Matches dashed string for camelizing
+	rmsPrefix = /^-ms-/,
+	rdashAlpha = /-([\da-z])/gi,
+
+	// Used by jQuery.camelCase as callback to replace()
+	fcamelCase = function( all, letter ) {
+		return letter.toUpperCase();
+	},
+
+	// The ready event handler and self cleanup method
+	completed = function() {
+		document.removeEventListener( "DOMContentLoaded", completed, false );
+		window.removeEventListener( "load", completed, false );
+		jQuery.ready();
+	};
+
+jQuery.fn = jQuery.prototype = {
+	// The current version of jQuery being used
+	jquery: core_version,
+
+	constructor: jQuery,
+	init: function( selector, context, rootjQuery ) {
+		var match, elem;
+
+		// HANDLE: $(""), $(null), $(undefined), $(false)
+		if ( !selector ) {
+			return this;
+		}
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+				// Assume that strings that start and end with <> are HTML and skip the regex check
+				match = [ null, selector, null ];
+
+			} else {
+				match = rquickExpr.exec( selector );
+			}
+
+			// Match html or make sure no context is specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] ) {
+					context = context instanceof jQuery ? context[0] : context;
+
+					// scripts is true for back-compat
+					jQuery.merge( this, jQuery.parseHTML(
+						match[1],
+						context && context.nodeType ? context.ownerDocument || context : document,
+						true
+					) );
+
+					// HANDLE: $(html, props)
+					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+						for ( match in context ) {
+							// Properties of context are called as methods if possible
+							if ( jQuery.isFunction( this[ match ] ) ) {
+								this[ match ]( context[ match ] );
+
+							// ...and otherwise set as attributes
+							} else {
+								this.attr( match, context[ match ] );
+							}
+						}
+					}
+
+					return this;
+
+				// HANDLE: $(#id)
+				} else {
+					elem = document.getElementById( match[2] );
+
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document #6963
+					if ( elem && elem.parentNode ) {
+						// Inject the element directly into the jQuery object
+						this.length = 1;
+						this[0] = elem;
+					}
+
+					this.context = document;
+					this.selector = selector;
+					return this;
+				}
+
+			// HANDLE: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return ( context || rootjQuery ).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( context ).find( selector );
+			}
+
+		// HANDLE: $(DOMElement)
+		} else if ( selector.nodeType ) {
+			this.context = this[0] = selector;
+			this.length = 1;
+			return this;
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) ) {
+			return rootjQuery.ready( selector );
+		}
+
+		if ( selector.selector !== undefined ) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return jQuery.makeArray( selector, this );
+	},
+
+	// Start with an empty selector
+	selector: "",
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	toArray: function() {
+		return core_slice.call( this );
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == null ?
+
+			// Return a 'clean' array
+			this.toArray() :
+
+			// Return just the object
+			( num < 0 ? this[ this.length + num ] : this[ num ] );
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems ) {
+
+		// Build a new jQuery matched element set
+		var ret = jQuery.merge( this.constructor(), elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+		ret.context = this.context;
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	ready: function( fn ) {
+		// Add the callback
+		jQuery.ready.promise().done( fn );
+
+		return this;
+	},
+
+	slice: function() {
+		return this.pushStack( core_slice.apply( this, arguments ) );
+	},
+
+	first: function() {
+		return this.eq( 0 );
+	},
+
+	last: function() {
+		return this.eq( -1 );
+	},
+
+	eq: function( i ) {
+		var len = this.length,
+			j = +i + ( i < 0 ? len : 0 );
+		return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor(null);
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: core_push,
+	sort: [].sort,
+	splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+	var options, name, src, copy, copyIsArray, clone,
+		target = arguments[0] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+		target = {};
+	}
+
+	// extend jQuery itself if only one argument is passed
+	if ( length === i ) {
+		target = this;
+		--i;
+	}
+
+	for ( ; i < length; i++ ) {
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null ) {
+			// Extend the base object
+			for ( name in options ) {
+				src = target[ name ];
+				copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy ) {
+					continue;
+				}
+
+				// Recurse if we're merging plain objects or arrays
+				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+					if ( copyIsArray ) {
+						copyIsArray = false;
+						clone = src && jQuery.isArray(src) ? src : [];
+
+					} else {
+						clone = src && jQuery.isPlainObject(src) ? src : {};
+					}
+
+					// Never move original objects, clone them
+					target[ name ] = jQuery.extend( deep, clone, copy );
+
+				// Don't bring in undefined values
+				} else if ( copy !== undefined ) {
+					target[ name ] = copy;
+				}
+			}
+		}
+	}
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend({
+	// Unique for each copy of jQuery on the page
+	expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
+
+	noConflict: function( deep ) {
+		if ( window.$ === jQuery ) {
+			window.$ = _$;
+		}
+
+		if ( deep && window.jQuery === jQuery ) {
+			window.jQuery = _jQuery;
+		}
+
+		return jQuery;
+	},
+
+	// Is the DOM ready to be used? Set to true once it occurs.
+	isReady: false,
+
+	// A counter to track how many items to wait for before
+	// the ready event fires. See #6781
+	readyWait: 1,
+
+	// Hold (or release) the ready event
+	holdReady: function( hold ) {
+		if ( hold ) {
+			jQuery.readyWait++;
+		} else {
+			jQuery.ready( true );
+		}
+	},
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+
+		// Abort if there are pending holds or we're already ready
+		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+			return;
+		}
+
+		// Remember that the DOM is ready
+		jQuery.isReady = true;
+
+		// If a normal DOM Ready event fired, decrement, and wait if need be
+		if ( wait !== true && --jQuery.readyWait > 0 ) {
+			return;
+		}
+
+		// If there are functions bound, to execute
+		readyList.resolveWith( document, [ jQuery ] );
+
+		// Trigger any bound ready events
+		if ( jQuery.fn.trigger ) {
+			jQuery( document ).trigger("ready").off("ready");
+		}
+	},
+
+	// See test/unit/core.js for details concerning isFunction.
+	// Since version 1.3, DOM methods and functions like alert
+	// aren't supported. They return false on IE (#2968).
+	isFunction: function( obj ) {
+		return jQuery.type(obj) === "function";
+	},
+
+	isArray: Array.isArray,
+
+	isWindow: function( obj ) {
+		return obj != null && obj === obj.window;
+	},
+
+	isNumeric: function( obj ) {
+		return !isNaN( parseFloat(obj) ) && isFinite( obj );
+	},
+
+	type: function( obj ) {
+		if ( obj == null ) {
+			return String( obj );
+		}
+		// Support: Safari <= 5.1 (functionish RegExp)
+		return typeof obj === "object" || typeof obj === "function" ?
+			class2type[ core_toString.call(obj) ] || "object" :
+			typeof obj;
+	},
+
+	isPlainObject: function( obj ) {
+		// Not plain objects:
+		// - Any object or value whose internal [[Class]] property is not "[object Object]"
+		// - DOM nodes
+		// - window
+		if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		// Support: Firefox <20
+		// The try/catch suppresses exceptions thrown when attempting to access
+		// the "constructor" property of certain host objects, ie. |window.location|
+		// https://bugzilla.mozilla.org/show_bug.cgi?id=814622
+		try {
+			if ( obj.constructor &&
+					!core_hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
+				return false;
+			}
+		} catch ( e ) {
+			return false;
+		}
+
+		// If the function hasn't returned already, we're confident that
+		// |obj| is a plain object, created by {} or constructed with new Object
+		return true;
+	},
+
+	isEmptyObject: function( obj ) {
+		var name;
+		for ( name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	error: function( msg ) {
+		throw new Error( msg );
+	},
+
+	// data: string of html
+	// context (optional): If specified, the fragment will be created in this context, defaults to document
+	// keepScripts (optional): If true, will include scripts passed in the html string
+	parseHTML: function( data, context, keepScripts ) {
+		if ( !data || typeof data !== "string" ) {
+			return null;
+		}
+		if ( typeof context === "boolean" ) {
+			keepScripts = context;
+			context = false;
+		}
+		context = context || document;
+
+		var parsed = rsingleTag.exec( data ),
+			scripts = !keepScripts && [];
+
+		// Single tag
+		if ( parsed ) {
+			return [ context.createElement( parsed[1] ) ];
+		}
+
+		parsed = jQuery.buildFragment( [ data ], context, scripts );
+
+		if ( scripts ) {
+			jQuery( scripts ).remove();
+		}
+
+		return jQuery.merge( [], parsed.childNodes );
+	},
+
+	parseJSON: JSON.parse,
+
+	// Cross-browser xml parsing
+	parseXML: function( data ) {
+		var xml, tmp;
+		if ( !data || typeof data !== "string" ) {
+			return null;
+		}
+
+		// Support: IE9
+		try {
+			tmp = new DOMParser();
+			xml = tmp.parseFromString( data , "text/xml" );
+		} catch ( e ) {
+			xml = undefined;
+		}
+
+		if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+		return xml;
+	},
+
+	noop: function() {},
+
+	// Evaluates a script in a global context
+	globalEval: function( code ) {
+		var script,
+				indirect = eval;
+
+		code = jQuery.trim( code );
+
+		if ( code ) {
+			// If the code includes a valid, prologue position
+			// strict mode pragma, execute code by injecting a
+			// script tag into the document.
+			if ( code.indexOf("use strict") === 1 ) {
+				script = document.createElement("script");
+				script.text = code;
+				document.head.appendChild( script ).parentNode.removeChild( script );
+			} else {
+			// Otherwise, avoid the DOM node creation, insertion
+			// and removal by using an indirect global eval
+				indirect( code );
+			}
+		}
+	},
+
+	// Convert dashed to camelCase; used by the css and data modules
+	// Microsoft forgot to hump their vendor prefix (#9572)
+	camelCase: function( string ) {
+		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+	},
+
+	// args is for internal usage only
+	each: function( obj, callback, args ) {
+		var value,
+			i = 0,
+			length = obj.length,
+			isArray = isArraylike( obj );
+
+		if ( args ) {
+			if ( isArray ) {
+				for ( ; i < length; i++ ) {
+					value = callback.apply( obj[ i ], args );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( i in obj ) {
+					value = callback.apply( obj[ i ], args );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			}
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( isArray ) {
+				for ( ; i < length; i++ ) {
+					value = callback.call( obj[ i ], i, obj[ i ] );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( i in obj ) {
+					value = callback.call( obj[ i ], i, obj[ i ] );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			}
+		}
+
+		return obj;
+	},
+
+	trim: function( text ) {
+		return text == null ? "" : core_trim.call( text );
+	},
+
+	// results is for internal usage only
+	makeArray: function( arr, results ) {
+		var ret = results || [];
+
+		if ( arr != null ) {
+			if ( isArraylike( Object(arr) ) ) {
+				jQuery.merge( ret,
+					typeof arr === "string" ?
+					[ arr ] : arr
+				);
+			} else {
+				core_push.call( ret, arr );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, arr, i ) {
+		return arr == null ? -1 : core_indexOf.call( arr, elem, i );
+	},
+
+	merge: function( first, second ) {
+		var l = second.length,
+			i = first.length,
+			j = 0;
+
+		if ( typeof l === "number" ) {
+			for ( ; j < l; j++ ) {
+				first[ i++ ] = second[ j ];
+			}
+		} else {
+			while ( second[j] !== undefined ) {
+				first[ i++ ] = second[ j++ ];
+			}
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var retVal,
+			ret = [],
+			i = 0,
+			length = elems.length;
+		inv = !!inv;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( ; i < length; i++ ) {
+			retVal = !!callback( elems[ i ], i );
+			if ( inv !== retVal ) {
+				ret.push( elems[ i ] );
+			}
+		}
+
+		return ret;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var value,
+			i = 0,
+			length = elems.length,
+			isArray = isArraylike( elems ),
+			ret = [];
+
+		// Go through the array, translating each of the items to their
+		if ( isArray ) {
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( i in elems ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return core_concat.apply( [], ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	// Bind a function to a context, optionally partially applying any
+	// arguments.
+	proxy: function( fn, context ) {
+		var tmp, args, proxy;
+
+		if ( typeof context === "string" ) {
+			tmp = fn[ context ];
+			context = fn;
+			fn = tmp;
+		}
+
+		// Quick check to determine if target is callable, in the spec
+		// this throws a TypeError, but we will just return undefined.
+		if ( !jQuery.isFunction( fn ) ) {
+			return undefined;
+		}
+
+		// Simulated bind
+		args = core_slice.call( arguments, 2 );
+		proxy = function() {
+			return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
+		};
+
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+		return proxy;
+	},
+
+	// Multifunctional method to get and set values of a collection
+	// The value/s can optionally be executed if it's a function
+	access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
+		var i = 0,
+			length = elems.length,
+			bulk = key == null;
+
+		// Sets many values
+		if ( jQuery.type( key ) === "object" ) {
+			chainable = true;
+			for ( i in key ) {
+				jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+			}
+
+		// Sets one value
+		} else if ( value !== undefined ) {
+			chainable = true;
+
+			if ( !jQuery.isFunction( value ) ) {
+				raw = true;
+			}
+
+			if ( bulk ) {
+				// Bulk operations run against the entire set
+				if ( raw ) {
+					fn.call( elems, value );
+					fn = null;
+
+				// ...except when executing function values
+				} else {
+					bulk = fn;
+					fn = function( elem, key, value ) {
+						return bulk.call( jQuery( elem ), value );
+					};
+				}
+			}
+
+			if ( fn ) {
+				for ( ; i < length; i++ ) {
+					fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+				}
+			}
+		}
+
+		return chainable ?
+			elems :
+
+			// Gets
+			bulk ?
+				fn.call( elems ) :
+				length ? fn( elems[0], key ) : emptyGet;
+	},
+
+	now: Date.now,
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations.
+	// Note: this method belongs to the css module but it's needed here for the support module.
+	// If support gets modularized, this method should be moved back to the css module.
+	swap: function( elem, options, callback, args ) {
+		var ret, name,
+			old = {};
+
+		// Remember the old values, and insert the new ones
+		for ( name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		ret = callback.apply( elem, args || [] );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+
+		return ret;
+	}
+});
+
+jQuery.ready.promise = function( obj ) {
+	if ( !readyList ) {
+
+		readyList = jQuery.Deferred();
+
+		// Catch cases where $(document).ready() is called after the browser event has already occurred.
+		// we once tried to use readyState "interactive" here, but it caused issues like the one
+		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+		if ( document.readyState === "complete" ) {
+			// Handle it asynchronously to allow scripts the opportunity to delay ready
+			setTimeout( jQuery.ready );
+
+		} else {
+
+			// Use the handy event callback
+			document.addEventListener( "DOMContentLoaded", completed, false );
+
+			// A fallback to window.onload, that will always work
+			window.addEventListener( "load", completed, false );
+		}
+	}
+	return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+	var length = obj.length,
+		type = jQuery.type( obj );
+
+	if ( jQuery.isWindow( obj ) ) {
+		return false;
+	}
+
+	if ( obj.nodeType === 1 && length ) {
+		return true;
+	}
+
+	return type === "array" || type !== "function" &&
+		( length === 0 ||
+		typeof length === "number" && length > 0 && ( length - 1 ) in obj );
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+/*!
+ * Sizzle CSS Selector Engine v1.9.4-pre
+ * http://sizzlejs.com/
+ *
+ * Copyright 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2013-06-03
+ */
+(function( window, undefined ) {
+
+var i,
+	support,
+	cachedruns,
+	Expr,
+	getText,
+	isXML,
+	compile,
+	outermostContext,
+	sortInput,
+
+	// Local document vars
+	setDocument,
+	document,
+	docElem,
+	documentIsHTML,
+	rbuggyQSA,
+	rbuggyMatches,
+	matches,
+	contains,
+
+	// Instance-specific data
+	expando = "sizzle" + -(new Date()),
+	preferredDoc = window.document,
+	dirruns = 0,
+	done = 0,
+	classCache = createCache(),
+	tokenCache = createCache(),
+	compilerCache = createCache(),
+	hasDuplicate = false,
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+		return 0;
+	},
+
+	// General-purpose constants
+	strundefined = typeof undefined,
+	MAX_NEGATIVE = 1 << 31,
+
+	// Instance methods
+	hasOwn = ({}).hasOwnProperty,
+	arr = [],
+	pop = arr.pop,
+	push_native = arr.push,
+	push = arr.push,
+	slice = arr.slice,
+	// Use a stripped-down indexOf if we can't use a native one
+	indexOf = arr.indexOf || function( elem ) {
+		var i = 0,
+			len = this.length;
+		for ( ; i < len; i++ ) {
+			if ( this[i] === elem ) {
+				return i;
+			}
+		}
+		return -1;
+	},
+
+	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+	// Regular expressions
+
+	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+	whitespace = "[\\x20\\t\\r\\n\\f]",
+	// http://www.w3.org/TR/css3-syntax/#characters
+	characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
+
+	// Loosely modeled on CSS identifier characters
+	// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
+	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+	identifier = characterEncoding.replace( "w", "w#" ),
+
+	// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+		"*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+
+	// Prefer arguments quoted,
+	//   then not containing pseudos/brackets,
+	//   then attribute selectors/non-parenthetical expressions,
+	//   then anything else
+	// These preferences are here to reduce the number of selectors
+	//   needing tokenize in the PSEUDO preFilter
+	pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
+
+	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+	rsibling = new RegExp( whitespace + "*[+~]" ),
+	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ),
+
+	rpseudo = new RegExp( pseudos ),
+	ridentifier = new RegExp( "^" + identifier + "$" ),
+
+	matchExpr = {
+		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
+		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+		"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+		"ATTR": new RegExp( "^" + attributes ),
+		"PSEUDO": new RegExp( "^" + pseudos ),
+		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+		// For use in libraries implementing .is()
+		// We use this for POS matching in `select`
+		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+	},
+
+	rnative = /^[^{]+\{\s*\[native \w/,
+
+	// Easily-parseable/retrievable ID or TAG or CLASS selectors
+	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+	rinputs = /^(?:input|select|textarea|button)$/i,
+	rheader = /^h\d$/i,
+
+	rescape = /'|\\/g,
+
+	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+	funescape = function( _, escaped, escapedWhitespace ) {
+		var high = "0x" + escaped - 0x10000;
+		// NaN means non-codepoint
+		// Support: Firefox
+		// Workaround erroneous numeric interpretation of +"0x"
+		return high !== high || escapedWhitespace ?
+			escaped :
+			// BMP codepoint
+			high < 0 ?
+				String.fromCharCode( high + 0x10000 ) :
+				// Supplemental Plane codepoint (surrogate pair)
+				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+	};
+
+// Optimize for push.apply( _, NodeList )
+try {
+	push.apply(
+		(arr = slice.call( preferredDoc.childNodes )),
+		preferredDoc.childNodes
+	);
+	// Support: Android<4.0
+	// Detect silently failing push.apply
+	arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+	push = { apply: arr.length ?
+
+		// Leverage slice if possible
+		function( target, els ) {
+			push_native.apply( target, slice.call(els) );
+		} :
+
+		// Support: IE<9
+		// Otherwise append directly
+		function( target, els ) {
+			var j = target.length,
+				i = 0;
+			// Can't trust NodeList.length
+			while ( (target[j++] = els[i++]) ) {}
+			target.length = j - 1;
+		}
+	};
+}
+
+function Sizzle( selector, context, results, seed ) {
+	var match, elem, m, nodeType,
+		// QSA vars
+		i, groups, old, nid, newContext, newSelector;
+
+	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+		setDocument( context );
+	}
+
+	context = context || document;
+	results = results || [];
+
+	if ( !selector || typeof selector !== "string" ) {
+		return results;
+	}
+
+	if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
+		return [];
+	}
+
+	if ( documentIsHTML && !seed ) {
+
+		// Shortcuts
+		if ( (match = rquickExpr.exec( selector )) ) {
+			// Speed-up: Sizzle("#ID")
+			if ( (m = match[1]) ) {
+				if ( nodeType === 9 ) {
+					elem = context.getElementById( m );
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document #6963
+					if ( elem && elem.parentNode ) {
+						// Handle the case where IE, Opera, and Webkit return items
+						// by name instead of ID
+						if ( elem.id === m ) {
+							results.push( elem );
+							return results;
+						}
+					} else {
+						return results;
+					}
+				} else {
+					// Context is not a document
+					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+						contains( context, elem ) && elem.id === m ) {
+						results.push( elem );
+						return results;
+					}
+				}
+
+			// Speed-up: Sizzle("TAG")
+			} else if ( match[2] ) {
+				push.apply( results, context.getElementsByTagName( selector ) );
+				return results;
+
+			// Speed-up: Sizzle(".CLASS")
+			} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
+				push.apply( results, context.getElementsByClassName( m ) );
+				return results;
+			}
+		}
+
+		// QSA path
+		if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+			nid = old = expando;
+			newContext = context;
+			newSelector = nodeType === 9 && selector;
+
+			// qSA works strangely on Element-rooted queries
+			// We can work around this by specifying an extra ID on the root
+			// and working up from there (Thanks to Andrew Dupont for the technique)
+			// IE 8 doesn't work on object elements
+			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+				groups = tokenize( selector );
+
+				if ( (old = context.getAttribute("id")) ) {
+					nid = old.replace( rescape, "\\$&" );
+				} else {
+					context.setAttribute( "id", nid );
+				}
+				nid = "[id='" + nid + "'] ";
+
+				i = groups.length;
+				while ( i-- ) {
+					groups[i] = nid + toSelector( groups[i] );
+				}
+				newContext = rsibling.test( selector ) && context.parentNode || context;
+				newSelector = groups.join(",");
+			}
+
+			if ( newSelector ) {
+				try {
+					push.apply( results,
+						newContext.querySelectorAll( newSelector )
+					);
+					return results;
+				} catch(qsaError) {
+				} finally {
+					if ( !old ) {
+						context.removeAttribute("id");
+					}
+				}
+			}
+		}
+	}
+
+	// All others
+	return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
+ *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ *	deleting the oldest entry
+ */
+function createCache() {
+	var keys = [];
+
+	function cache( key, value ) {
+		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+		if ( keys.push( key += " " ) > Expr.cacheLength ) {
+			// Only keep the most recent entries
+			delete cache[ keys.shift() ];
+		}
+		return (cache[ key ] = value);
+	}
+	return cache;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+	fn[ expando ] = true;
+	return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created div and expects a boolean result
+ */
+function assert( fn ) {
+	var div = document.createElement("div");
+
+	try {
+		return !!fn( div );
+	} catch (e) {
+		return false;
+	} finally {
+		// Remove from its parent by default
+		if ( div.parentNode ) {
+			div.parentNode.removeChild( div );
+		}
+		// release memory in IE
+		div = null;
+	}
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+	var arr = attrs.split("|"),
+		i = attrs.length;
+
+	while ( i-- ) {
+		Expr.attrHandle[ arr[i] ] = handler;
+	}
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+	var cur = b && a,
+		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+			( ~b.sourceIndex || MAX_NEGATIVE ) -
+			( ~a.sourceIndex || MAX_NEGATIVE );
+
+	// Use IE sourceIndex if available on both nodes
+	if ( diff ) {
+		return diff;
+	}
+
+	// Check if b follows a
+	if ( cur ) {
+		while ( (cur = cur.nextSibling) ) {
+			if ( cur === b ) {
+				return -1;
+			}
+		}
+	}
+
+	return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+	return function( elem ) {
+		var name = elem.nodeName.toLowerCase();
+		return name === "input" && elem.type === type;
+	};
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+	return function( elem ) {
+		var name = elem.nodeName.toLowerCase();
+		return (name === "input" || name === "button") && elem.type === type;
+	};
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+	return markFunction(function( argument ) {
+		argument = +argument;
+		return markFunction(function( seed, matches ) {
+			var j,
+				matchIndexes = fn( [], seed.length, argument ),
+				i = matchIndexes.length;
+
+			// Match elements found at the specified indexes
+			while ( i-- ) {
+				if ( seed[ (j = matchIndexes[i]) ] ) {
+					seed[j] = !(matches[j] = seed[j]);
+				}
+			}
+		});
+	});
+}
+
+/**
+ * Detect xml
+ * @param {Element|Object} elem An element or a document
+ */
+isXML = Sizzle.isXML = function( elem ) {
+	// documentElement is verified for cases where it doesn't yet exist
+	// (such as loading iframes in IE - #4833)
+	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+	return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+	var doc = node ? node.ownerDocument || node : preferredDoc,
+		parent = doc.defaultView;
+
+	// If no document and documentElement is available, return
+	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+		return document;
+	}
+
+	// Set our document
+	document = doc;
+	docElem = doc.documentElement;
+
+	// Support tests
+	documentIsHTML = !isXML( doc );
+
+	// Support: IE>8
+	// If iframe document is assigned to "document" variable and if iframe has been reloaded,
+	// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
+	// IE6-8 do not support the defaultView property so parent will be undefined
+	if ( parent && parent.attachEvent && parent !== parent.top ) {
+		parent.attachEvent( "onbeforeunload", function() {
+			setDocument();
+		});
+	}
+
+	/* Attributes
+	---------------------------------------------------------------------- */
+
+	// Support: IE<8
+	// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
+	support.attributes = assert(function( div ) {
+		div.className = "i";
+		return !div.getAttribute("className");
+	});
+
+	/* getElement(s)By*
+	---------------------------------------------------------------------- */
+
+	// Check if getElementsByTagName("*") returns only elements
+	support.getElementsByTagName = assert(function( div ) {
+		div.appendChild( doc.createComment("") );
+		return !div.getElementsByTagName("*").length;
+	});
+
+	// Check if getElementsByClassName can be trusted
+	support.getElementsByClassName = assert(function( div ) {
+		div.innerHTML = "<div class='a'></div><div class='a i'></div>";
+
+		// Support: Safari<4
+		// Catch class over-caching
+		div.firstChild.className = "i";
+		// Support: Opera<10
+		// Catch gEBCN failure to find non-leading classes
+		return div.getElementsByClassName("i").length === 2;
+	});
+
+	// Support: IE<10
+	// Check if getElementById returns elements by name
+	// The broken getElementById methods don't pick up programatically-set names,
+	// so use a roundabout getElementsByName test
+	support.getById = assert(function( div ) {
+		docElem.appendChild( div ).id = expando;
+		return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+	});
+
+	// ID find and filter
+	if ( support.getById ) {
+		Expr.find["ID"] = function( id, context ) {
+			if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
+				var m = context.getElementById( id );
+				// Check parentNode to catch when Blackberry 4.6 returns
+				// nodes that are no longer in the document #6963
+				return m && m.parentNode ? [m] : [];
+			}
+		};
+		Expr.filter["ID"] = function( id ) {
+			var attrId = id.replace( runescape, funescape );
+			return function( elem ) {
+				return elem.getAttribute("id") === attrId;
+			};
+		};
+	} else {
+		// Support: IE6/7
+		// getElementById is not reliable as a find shortcut
+		delete Expr.find["ID"];
+
+		Expr.filter["ID"] =  function( id ) {
+			var attrId = id.replace( runescape, funescape );
+			return function( elem ) {
+				var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+				return node && node.value === attrId;
+			};
+		};
+	}
+
+	// Tag
+	Expr.find["TAG"] = support.getElementsByTagName ?
+		function( tag, context ) {
+			if ( typeof context.getElementsByTagName !== strundefined ) {
+				return context.getElementsByTagName( tag );
+			}
+		} :
+		function( tag, context ) {
+			var elem,
+				tmp = [],
+				i = 0,
+				results = context.getElementsByTagName( tag );
+
+			// Filter out possible comments
+			if ( tag === "*" ) {
+				while ( (elem = results[i++]) ) {
+					if ( elem.nodeType === 1 ) {
+						tmp.push( elem );
+					}
+				}
+
+				return tmp;
+			}
+			return results;
+		};
+
+	// Class
+	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+		if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
+			return context.getElementsByClassName( className );
+		}
+	};
+
+	/* QSA/matchesSelector
+	---------------------------------------------------------------------- */
+
+	// QSA and matchesSelector support
+
+	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+	rbuggyMatches = [];
+
+	// qSa(:focus) reports false when true (Chrome 21)
+	// We allow this because of a bug in IE8/9 that throws an error
+	// whenever `document.activeElement` is accessed on an iframe
+	// So, we allow :focus to pass through QSA all the time to avoid the IE error
+	// See http://bugs.jquery.com/ticket/13378
+	rbuggyQSA = [];
+
+	if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
+		// Build QSA regex
+		// Regex strategy adopted from Diego Perini
+		assert(function( div ) {
+			// Select is set to empty string on purpose
+			// This is to test IE's treatment of not explicitly
+			// setting a boolean content attribute,
+			// since its presence should be enough
+			// http://bugs.jquery.com/ticket/12359
+			div.innerHTML = "<select><option selected=''></option></select>";
+
+			// Support: IE8
+			// Boolean attributes and "value" are not treated correctly
+			if ( !div.querySelectorAll("[selected]").length ) {
+				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+			}
+
+			// Webkit/Opera - :checked should return selected option elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			// IE8 throws error here and will not see later tests
+			if ( !div.querySelectorAll(":checked").length ) {
+				rbuggyQSA.push(":checked");
+			}
+		});
+
+		assert(function( div ) {
+
+			// Support: Opera 10-12/IE8
+			// ^= $= *= and empty values
+			// Should not select anything
+			// Support: Windows 8 Native Apps
+			// The type attribute is restricted during .innerHTML assignment
+			var input = doc.createElement("input");
+			input.setAttribute( "type", "hidden" );
+			div.appendChild( input ).setAttribute( "t", "" );
+
+			if ( div.querySelectorAll("[t^='']").length ) {
+				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+			}
+
+			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+			// IE8 throws error here and will not see later tests
+			if ( !div.querySelectorAll(":enabled").length ) {
+				rbuggyQSA.push( ":enabled", ":disabled" );
+			}
+
+			// Opera 10-11 does not throw on post-comma invalid pseudos
+			div.querySelectorAll("*,:x");
+			rbuggyQSA.push(",.*:");
+		});
+	}
+
+	if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||
+		docElem.mozMatchesSelector ||
+		docElem.oMatchesSelector ||
+		docElem.msMatchesSelector) )) ) {
+
+		assert(function( div ) {
+			// Check to see if it's possible to do matchesSelector
+			// on a disconnected node (IE 9)
+			support.disconnectedMatch = matches.call( div, "div" );
+
+			// This should fail with an exception
+			// Gecko does not error, returns false instead
+			matches.call( div, "[s!='']:x" );
+			rbuggyMatches.push( "!=", pseudos );
+		});
+	}
+
+	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+	/* Contains
+	---------------------------------------------------------------------- */
+
+	// Element contains another
+	// Purposefully does not implement inclusive descendent
+	// As in, an element does not contain itself
+	contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ?
+		function( a, b ) {
+			var adown = a.nodeType === 9 ? a.documentElement : a,
+				bup = b && b.parentNode;
+			return a === bup || !!( bup && bup.nodeType === 1 && (
+				adown.contains ?
+					adown.contains( bup ) :
+					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+			));
+		} :
+		function( a, b ) {
+			if ( b ) {
+				while ( (b = b.parentNode) ) {
+					if ( b === a ) {
+						return true;
+					}
+				}
+			}
+			return false;
+		};
+
+	/* Sorting
+	---------------------------------------------------------------------- */
+
+	// Document order sorting
+	sortOrder = docElem.compareDocumentPosition ?
+	function( a, b ) {
+
+		// Flag for duplicate removal
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );
+
+		if ( compare ) {
+			// Disconnected nodes
+			if ( compare & 1 ||
+				(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+				// Choose the first element that is related to our preferred document
+				if ( a === doc || contains(preferredDoc, a) ) {
+					return -1;
+				}
+				if ( b === doc || contains(preferredDoc, b) ) {
+					return 1;
+				}
+
+				// Maintain original order
+				return sortInput ?
+					( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+					0;
+			}
+
+			return compare & 4 ? -1 : 1;
+		}
+
+		// Not directly comparable, sort on existence of method
+		return a.compareDocumentPosition ? -1 : 1;
+	} :
+	function( a, b ) {
+		var cur,
+			i = 0,
+			aup = a.parentNode,
+			bup = b.parentNode,
+			ap = [ a ],
+			bp = [ b ];
+
+		// Exit early if the nodes are identical
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+
+		// Parentless nodes are either documents or disconnected
+		} else if ( !aup || !bup ) {
+			return a === doc ? -1 :
+				b === doc ? 1 :
+				aup ? -1 :
+				bup ? 1 :
+				sortInput ?
+				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+				0;
+
+		// If the nodes are siblings, we can do a quick check
+		} else if ( aup === bup ) {
+			return siblingCheck( a, b );
+		}
+
+		// Otherwise we need full lists of their ancestors for comparison
+		cur = a;
+		while ( (cur = cur.parentNode) ) {
+			ap.unshift( cur );
+		}
+		cur = b;
+		while ( (cur = cur.parentNode) ) {
+			bp.unshift( cur );
+		}
+
+		// Walk down the tree looking for a discrepancy
+		while ( ap[i] === bp[i] ) {
+			i++;
+		}
+
+		return i ?
+			// Do a sibling check if the nodes have a common ancestor
+			siblingCheck( ap[i], bp[i] ) :
+
+			// Otherwise nodes in our document sort first
+			ap[i] === preferredDoc ? -1 :
+			bp[i] === preferredDoc ? 1 :
+			0;
+	};
+
+	return doc;
+};
+
+Sizzle.matches = function( expr, elements ) {
+	return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+	// Set document vars if needed
+	if ( ( elem.ownerDocument || elem ) !== document ) {
+		setDocument( elem );
+	}
+
+	// Make sure that attribute selectors are quoted
+	expr = expr.replace( rattributeQuotes, "='$1']" );
+
+	if ( support.matchesSelector && documentIsHTML &&
+		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
+
+		try {
+			var ret = matches.call( elem, expr );
+
+			// IE 9's matchesSelector returns false on disconnected nodes
+			if ( ret || support.disconnectedMatch ||
+					// As well, disconnected nodes are said to be in a document
+					// fragment in IE 9
+					elem.document && elem.document.nodeType !== 11 ) {
+				return ret;
+			}
+		} catch(e) {}
+	}
+
+	return Sizzle( expr, document, null, [elem] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+	// Set document vars if needed
+	if ( ( context.ownerDocument || context ) !== document ) {
+		setDocument( context );
+	}
+	return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+	// Set document vars if needed
+	if ( ( elem.ownerDocument || elem ) !== document ) {
+		setDocument( elem );
+	}
+
+	var fn = Expr.attrHandle[ name.toLowerCase() ],
+		// Don't get fooled by Object.prototype properties (jQuery #13807)
+		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+			fn( elem, name, !documentIsHTML ) :
+			undefined;
+
+	return val === undefined ?
+		support.attributes || !documentIsHTML ?
+			elem.getAttribute( name ) :
+			(val = elem.getAttributeNode(name)) && val.specified ?
+				val.value :
+				null :
+		val;
+};
+
+Sizzle.error = function( msg ) {
+	throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+	var elem,
+		duplicates = [],
+		j = 0,
+		i = 0;
+
+	// Unless we *know* we can detect duplicates, assume their presence
+	hasDuplicate = !support.detectDuplicates;
+	sortInput = !support.sortStable && results.slice( 0 );
+	results.sort( sortOrder );
+
+	if ( hasDuplicate ) {
+		while ( (elem = results[i++]) ) {
+			if ( elem === results[ i ] ) {
+				j = duplicates.push( i );
+			}
+		}
+		while ( j-- ) {
+			results.splice( duplicates[ j ], 1 );
+		}
+	}
+
+	return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+	var node,
+		ret = "",
+		i = 0,
+		nodeType = elem.nodeType;
+
+	if ( !nodeType ) {
+		// If no nodeType, this is expected to be an array
+		for ( ; (node = elem[i]); i++ ) {
+			// Do not traverse comment nodes
+			ret += getText( node );
+		}
+	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+		// Use textContent for elements
+		// innerText usage removed for consistency of new lines (see #11153)
+		if ( typeof elem.textContent === "string" ) {
+			return elem.textContent;
+		} else {
+			// Traverse its children
+			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+				ret += getText( elem );
+			}
+		}
+	} else if ( nodeType === 3 || nodeType === 4 ) {
+		return elem.nodeValue;
+	}
+	// Do not include comment or processing instruction nodes
+
+	return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+	// Can be adjusted by the user
+	cacheLength: 50,
+
+	createPseudo: markFunction,
+
+	match: matchExpr,
+
+	attrHandle: {},
+
+	find: {},
+
+	relative: {
+		">": { dir: "parentNode", first: true },
+		" ": { dir: "parentNode" },
+		"+": { dir: "previousSibling", first: true },
+		"~": { dir: "previousSibling" }
+	},
+
+	preFilter: {
+		"ATTR": function( match ) {
+			match[1] = match[1].replace( runescape, funescape );
+
+			// Move the given value to match[3] whether quoted or unquoted
+			match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
+
+			if ( match[2] === "~=" ) {
+				match[3] = " " + match[3] + " ";
+			}
+
+			return match.slice( 0, 4 );
+		},
+
+		"CHILD": function( match ) {
+			/* matches from matchExpr["CHILD"]
+				1 type (only|nth|...)
+				2 what (child|of-type)
+				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+				4 xn-component of xn+y argument ([+-]?\d*n|)
+				5 sign of xn-component
+				6 x of xn-component
+				7 sign of y-component
+				8 y of y-component
+			*/
+			match[1] = match[1].toLowerCase();
+
+			if ( match[1].slice( 0, 3 ) === "nth" ) {
+				// nth-* requires argument
+				if ( !match[3] ) {
+					Sizzle.error( match[0] );
+				}
+
+				// numeric x and y parameters for Expr.filter.CHILD
+				// remember that false/true cast respectively to 0/1
+				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+			// other types prohibit arguments
+			} else if ( match[3] ) {
+				Sizzle.error( match[0] );
+			}
+
+			return match;
+		},
+
+		"PSEUDO": function( match ) {
+			var excess,
+				unquoted = !match[5] && match[2];
+
+			if ( matchExpr["CHILD"].test( match[0] ) ) {
+				return null;
+			}
+
+			// Accept quoted arguments as-is
+			if ( match[3] && match[4] !== undefined ) {
+				match[2] = match[4];
+
+			// Strip excess characters from unquoted arguments
+			} else if ( unquoted && rpseudo.test( unquoted ) &&
+				// Get excess from tokenize (recursively)
+				(excess = tokenize( unquoted, true )) &&
+				// advance to the next closing parenthesis
+				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+				// excess is a negative index
+				match[0] = match[0].slice( 0, excess );
+				match[2] = unquoted.slice( 0, excess );
+			}
+
+			// Return only captures needed by the pseudo filter method (type and argument)
+			return match.slice( 0, 3 );
+		}
+	},
+
+	filter: {
+
+		"TAG": function( nodeNameSelector ) {
+			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+			return nodeNameSelector === "*" ?
+				function() { return true; } :
+				function( elem ) {
+					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+				};
+		},
+
+		"CLASS": function( className ) {
+			var pattern = classCache[ className + " " ];
+
+			return pattern ||
+				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+				classCache( className, function( elem ) {
+					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
+				});
+		},
+
+		"ATTR": function( name, operator, check ) {
+			return function( elem ) {
+				var result = Sizzle.attr( elem, name );
+
+				if ( result == null ) {
+					return operator === "!=";
+				}
+				if ( !operator ) {
+					return true;
+				}
+
+				result += "";
+
+				return operator === "=" ? result === check :
+					operator === "!=" ? result !== check :
+					operator === "^=" ? check && result.indexOf( check ) === 0 :
+					operator === "*=" ? check && result.indexOf( check ) > -1 :
+					operator === "$=" ? check && result.slice( -check.length ) === check :
+					operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+					false;
+			};
+		},
+
+		"CHILD": function( type, what, argument, first, last ) {
+			var simple = type.slice( 0, 3 ) !== "nth",
+				forward = type.slice( -4 ) !== "last",
+				ofType = what === "of-type";
+
+			return first === 1 && last === 0 ?
+
+				// Shortcut for :nth-*(n)
+				function( elem ) {
+					return !!elem.parentNode;
+				} :
+
+				function( elem, context, xml ) {
+					var cache, outerCache, node, diff, nodeIndex, start,
+						dir = simple !== forward ? "nextSibling" : "previousSibling",
+						parent = elem.parentNode,
+						name = ofType && elem.nodeName.toLowerCase(),
+						useCache = !xml && !ofType;
+
+					if ( parent ) {
+
+						// :(first|last|only)-(child|of-type)
+						if ( simple ) {
+							while ( dir ) {
+								node = elem;
+								while ( (node = node[ dir ]) ) {
+									if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
+										return false;
+									}
+								}
+								// Reverse direction for :only-* (if we haven't yet done so)
+								start = dir = type === "only" && !start && "nextSibling";
+							}
+							return true;
+						}
+
+						start = [ forward ? parent.firstChild : parent.lastChild ];
+
+						// non-xml :nth-child(...) stores cache data on `parent`
+						if ( forward && useCache ) {
+							// Seek `elem` from a previously-cached index
+							outerCache = parent[ expando ] || (parent[ expando ] = {});
+							cache = outerCache[ type ] || [];
+							nodeIndex = cache[0] === dirruns && cache[1];
+							diff = cache[0] === dirruns && cache[2];
+							node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+							while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+								// Fallback to seeking `elem` from the start
+								(diff = nodeIndex = 0) || start.pop()) ) {
+
+								// When found, cache indexes on `parent` and break
+								if ( node.nodeType === 1 && ++diff && node === elem ) {
+									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+									break;
+								}
+							}
+
+						// Use previously-cached element index if available
+						} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+							diff = cache[1];
+
+						// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
+						} else {
+							// Use the same loop as above to seek `elem` from the start
+							while ( (node = ++nodeIndex && node && node[ dir ] ||
+								(diff = nodeIndex = 0) || start.pop()) ) {
+
+								if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
+									// Cache the index of each encountered element
+									if ( useCache ) {
+										(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
+									}
+
+									if ( node === elem ) {
+										break;
+									}
+								}
+							}
+						}
+
+						// Incorporate the offset, then check against cycle size
+						diff -= last;
+						return diff === first || ( diff % first === 0 && diff / first >= 0 );
+					}
+				};
+		},
+
+		"PSEUDO": function( pseudo, argument ) {
+			// pseudo-class names are case-insensitive
+			// http://www.w3.org/TR/selectors/#pseudo-classes
+			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+			// Remember that setFilters inherits from pseudos
+			var args,
+				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+					Sizzle.error( "unsupported pseudo: " + pseudo );
+
+			// The user may use createPseudo to indicate that
+			// arguments are needed to create the filter function
+			// just as Sizzle does
+			if ( fn[ expando ] ) {
+				return fn( argument );
+			}
+
+			// But maintain support for old signatures
+			if ( fn.length > 1 ) {
+				args = [ pseudo, pseudo, "", argument ];
+				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+					markFunction(function( seed, matches ) {
+						var idx,
+							matched = fn( seed, argument ),
+							i = matched.length;
+						while ( i-- ) {
+							idx = indexOf.call( seed, matched[i] );
+							seed[ idx ] = !( matches[ idx ] = matched[i] );
+						}
+					}) :
+					function( elem ) {
+						return fn( elem, 0, args );
+					};
+			}
+
+			return fn;
+		}
+	},
+
+	pseudos: {
+		// Potentially complex pseudos
+		"not": markFunction(function( selector ) {
+			// Trim the selector passed to compile
+			// to avoid treating leading and trailing
+			// spaces as combinators
+			var input = [],
+				results = [],
+				matcher = compile( selector.replace( rtrim, "$1" ) );
+
+			return matcher[ expando ] ?
+				markFunction(function( seed, matches, context, xml ) {
+					var elem,
+						unmatched = matcher( seed, null, xml, [] ),
+						i = seed.length;
+
+					// Match elements unmatched by `matcher`
+					while ( i-- ) {
+						if ( (elem = unmatched[i]) ) {
+							seed[i] = !(matches[i] = elem);
+						}
+					}
+				}) :
+				function( elem, context, xml ) {
+					input[0] = elem;
+					matcher( input, null, xml, results );
+					return !results.pop();
+				};
+		}),
+
+		"has": markFunction(function( selector ) {
+			return function( elem ) {
+				return Sizzle( selector, elem ).length > 0;
+			};
+		}),
+
+		"contains": markFunction(function( text ) {
+			return function( elem ) {
+				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+			};
+		}),
+
+		// "Whether an element is represented by a :lang() selector
+		// is based solely on the element's language value
+		// being equal to the identifier C,
+		// or beginning with the identifier C immediately followed by "-".
+		// The matching of C against the element's language value is performed case-insensitively.
+		// The identifier C does not have to be a valid language name."
+		// http://www.w3.org/TR/selectors/#lang-pseudo
+		"lang": markFunction( function( lang ) {
+			// lang value must be a valid identifier
+			if ( !ridentifier.test(lang || "") ) {
+				Sizzle.error( "unsupported lang: " + lang );
+			}
+			lang = lang.replace( runescape, funescape ).toLowerCase();
+			return function( elem ) {
+				var elemLang;
+				do {
+					if ( (elemLang = documentIsHTML ?
+						elem.lang :
+						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+						elemLang = elemLang.toLowerCase();
+						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+					}
+				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+				return false;
+			};
+		}),
+
+		// Miscellaneous
+		"target": function( elem ) {
+			var hash = window.location && window.location.hash;
+			return hash && hash.slice( 1 ) === elem.id;
+		},
+
+		"root": function( elem ) {
+			return elem === docElem;
+		},
+
+		"focus": function( elem ) {
+			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+		},
+
+		// Boolean properties
+		"enabled": function( elem ) {
+			return elem.disabled === false;
+		},
+
+		"disabled": function( elem ) {
+			return elem.disabled === true;
+		},
+
+		"checked": function( elem ) {
+			// In CSS3, :checked should return both checked and selected elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			var nodeName = elem.nodeName.toLowerCase();
+			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+		},
+
+		"selected": function( elem ) {
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			if ( elem.parentNode ) {
+				elem.parentNode.selectedIndex;
+			}
+
+			return elem.selected === true;
+		},
+
+		// Contents
+		"empty": function( elem ) {
+			// http://www.w3.org/TR/selectors/#empty-pseudo
+			// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
+			//   not comment, processing instructions, or others
+			// Thanks to Diego Perini for the nodeName shortcut
+			//   Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+				if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) {
+					return false;
+				}
+			}
+			return true;
+		},
+
+		"parent": function( elem ) {
+			return !Expr.pseudos["empty"]( elem );
+		},
+
+		// Element/input types
+		"header": function( elem ) {
+			return rheader.test( elem.nodeName );
+		},
+
+		"input": function( elem ) {
+			return rinputs.test( elem.nodeName );
+		},
+
+		"button": function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && elem.type === "button" || name === "button";
+		},
+
+		"text": function( elem ) {
+			var attr;
+			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+			// use getAttribute instead to test this case
+			return elem.nodeName.toLowerCase() === "input" &&
+				elem.type === "text" &&
+				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type );
+		},
+
+		// Position-in-collection
+		"first": createPositionalPseudo(function() {
+			return [ 0 ];
+		}),
+
+		"last": createPositionalPseudo(function( matchIndexes, length ) {
+			return [ length - 1 ];
+		}),
+
+		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+			return [ argument < 0 ? argument + length : argument ];
+		}),
+
+		"even": createPositionalPseudo(function( matchIndexes, length ) {
+			var i = 0;
+			for ( ; i < length; i += 2 ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		}),
+
+		"odd": createPositionalPseudo(function( matchIndexes, length ) {
+			var i = 1;
+			for ( ; i < length; i += 2 ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		}),
+
+		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+			var i = argument < 0 ? argument + length : argument;
+			for ( ; --i >= 0; ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		}),
+
+		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+			var i = argument < 0 ? argument + length : argument;
+			for ( ; ++i < length; ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		})
+	}
+};
+
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+	Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+	Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+function tokenize( selector, parseOnly ) {
+	var matched, match, tokens, type,
+		soFar, groups, preFilters,
+		cached = tokenCache[ selector + " " ];
+
+	if ( cached ) {
+		return parseOnly ? 0 : cached.slice( 0 );
+	}
+
+	soFar = selector;
+	groups = [];
+	preFilters = Expr.preFilter;
+
+	while ( soFar ) {
+
+		// Comma and first run
+		if ( !matched || (match = rcomma.exec( soFar )) ) {
+			if ( match ) {
+				// Don't consume trailing commas as valid
+				soFar = soFar.slice( match[0].length ) || soFar;
+			}
+			groups.push( tokens = [] );
+		}
+
+		matched = false;
+
+		// Combinators
+		if ( (match = rcombinators.exec( soFar )) ) {
+			matched = match.shift();
+			tokens.push({
+				value: matched,
+				// Cast descendant combinators to space
+				type: match[0].replace( rtrim, " " )
+			});
+			soFar = soFar.slice( matched.length );
+		}
+
+		// Filters
+		for ( type in Expr.filter ) {
+			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+				(match = preFilters[ type ]( match ))) ) {
+				matched = match.shift();
+				tokens.push({
+					value: matched,
+					type: type,
+					matches: match
+				});
+				soFar = soFar.slice( matched.length );
+			}
+		}
+
+		if ( !matched ) {
+			break;
+		}
+	}
+
+	// Return the length of the invalid excess
+	// if we're just parsing
+	// Otherwise, throw an error or return tokens
+	return parseOnly ?
+		soFar.length :
+		soFar ?
+			Sizzle.error( selector ) :
+			// Cache the tokens
+			tokenCache( selector, groups ).slice( 0 );
+}
+
+function toSelector( tokens ) {
+	var i = 0,
+		len = tokens.length,
+		selector = "";
+	for ( ; i < len; i++ ) {
+		selector += tokens[i].value;
+	}
+	return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+	var dir = combinator.dir,
+		checkNonElements = base && dir === "parentNode",
+		doneName = done++;
+
+	return combinator.first ?
+		// Check against closest ancestor/preceding element
+		function( elem, context, xml ) {
+			while ( (elem = elem[ dir ]) ) {
+				if ( elem.nodeType === 1 || checkNonElements ) {
+					return matcher( elem, context, xml );
+				}
+			}
+		} :
+
+		// Check against all ancestor/preceding elements
+		function( elem, context, xml ) {
+			var data, cache, outerCache,
+				dirkey = dirruns + " " + doneName;
+
+			// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+			if ( xml ) {
+				while ( (elem = elem[ dir ]) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						if ( matcher( elem, context, xml ) ) {
+							return true;
+						}
+					}
+				}
+			} else {
+				while ( (elem = elem[ dir ]) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						outerCache = elem[ expando ] || (elem[ expando ] = {});
+						if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
+							if ( (data = cache[1]) === true || data === cachedruns ) {
+								return data === true;
+							}
+						} else {
+							cache = outerCache[ dir ] = [ dirkey ];
+							cache[1] = matcher( elem, context, xml ) || cachedruns;
+							if ( cache[1] === true ) {
+								return true;
+							}
+						}
+					}
+				}
+			}
+		};
+}
+
+function elementMatcher( matchers ) {
+	return matchers.length > 1 ?
+		function( elem, context, xml ) {
+			var i = matchers.length;
+			while ( i-- ) {
+				if ( !matchers[i]( elem, context, xml ) ) {
+					return false;
+				}
+			}
+			return true;
+		} :
+		matchers[0];
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+	var elem,
+		newUnmatched = [],
+		i = 0,
+		len = unmatched.length,
+		mapped = map != null;
+
+	for ( ; i < len; i++ ) {
+		if ( (elem = unmatched[i]) ) {
+			if ( !filter || filter( elem, context, xml ) ) {
+				newUnmatched.push( elem );
+				if ( mapped ) {
+					map.push( i );
+				}
+			}
+		}
+	}
+
+	return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+	if ( postFilter && !postFilter[ expando ] ) {
+		postFilter = setMatcher( postFilter );
+	}
+	if ( postFinder && !postFinder[ expando ] ) {
+		postFinder = setMatcher( postFinder, postSelector );
+	}
+	return markFunction(function( seed, results, context, xml ) {
+		var temp, i, elem,
+			preMap = [],
+			postMap = [],
+			preexisting = results.length,
+
+			// Get initial elements from seed or context
+			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+			// Prefilter to get matcher input, preserving a map for seed-results synchronization
+			matcherIn = preFilter && ( seed || !selector ) ?
+				condense( elems, preMap, preFilter, context, xml ) :
+				elems,
+
+			matcherOut = matcher ?
+				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+					// ...intermediate processing is necessary
+					[] :
+
+					// ...otherwise use results directly
+					results :
+				matcherIn;
+
+		// Find primary matches
+		if ( matcher ) {
+			matcher( matcherIn, matcherOut, context, xml );
+		}
+
+		// Apply postFilter
+		if ( postFilter ) {
+			temp = condense( matcherOut, postMap );
+			postFilter( temp, [], context, xml );
+
+			// Un-match failing elements by moving them back to matcherIn
+			i = temp.length;
+			while ( i-- ) {
+				if ( (elem = temp[i]) ) {
+					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+				}
+			}
+		}
+
+		if ( seed ) {
+			if ( postFinder || preFilter ) {
+				if ( postFinder ) {
+					// Get the final matcherOut by condensing this intermediate into postFinder contexts
+					temp = [];
+					i = matcherOut.length;
+					while ( i-- ) {
+						if ( (elem = matcherOut[i]) ) {
+							// Restore matcherIn since elem is not yet a final match
+							temp.push( (matcherIn[i] = elem) );
+						}
+					}
+					postFinder( null, (matcherOut = []), temp, xml );
+				}
+
+				// Move matched elements from seed to results to keep them synchronized
+				i = matcherOut.length;
+				while ( i-- ) {
+					if ( (elem = matcherOut[i]) &&
+						(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
+
+						seed[temp] = !(results[temp] = elem);
+					}
+				}
+			}
+
+		// Add elements to results, through postFinder if defined
+		} else {
+			matcherOut = condense(
+				matcherOut === results ?
+					matcherOut.splice( preexisting, matcherOut.length ) :
+					matcherOut
+			);
+			if ( postFinder ) {
+				postFinder( null, results, matcherOut, xml );
+			} else {
+				push.apply( results, matcherOut );
+			}
+		}
+	});
+}
+
+function matcherFromTokens( tokens ) {
+	var checkContext, matcher, j,
+		len = tokens.length,
+		leadingRelative = Expr.relative[ tokens[0].type ],
+		implicitRelative = leadingRelative || Expr.relative[" "],
+		i = leadingRelative ? 1 : 0,
+
+		// The foundational matcher ensures that elements are reachable from top-level context(s)
+		matchContext = addCombinator( function( elem ) {
+			return elem === checkContext;
+		}, implicitRelative, true ),
+		matchAnyContext = addCombinator( function( elem ) {
+			return indexOf.call( checkContext, elem ) > -1;
+		}, implicitRelative, true ),
+		matchers = [ function( elem, context, xml ) {
+			return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+				(checkContext = context).nodeType ?
+					matchContext( elem, context, xml ) :
+					matchAnyContext( elem, context, xml ) );
+		} ];
+
+	for ( ; i < len; i++ ) {
+		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+		} else {
+			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+			// Return special upon seeing a positional matcher
+			if ( matcher[ expando ] ) {
+				// Find the next relative operator (if any) for proper handling
+				j = ++i;
+				for ( ; j < len; j++ ) {
+					if ( Expr.relative[ tokens[j].type ] ) {
+						break;
+					}
+				}
+				return setMatcher(
+					i > 1 && elementMatcher( matchers ),
+					i > 1 && toSelector(
+						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
+						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+					).replace( rtrim, "$1" ),
+					matcher,
+					i < j && matcherFromTokens( tokens.slice( i, j ) ),
+					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+					j < len && toSelector( tokens )
+				);
+			}
+			matchers.push( matcher );
+		}
+	}
+
+	return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+	// A counter to specify which element is currently being matched
+	var matcherCachedRuns = 0,
+		bySet = setMatchers.length > 0,
+		byElement = elementMatchers.length > 0,
+		superMatcher = function( seed, context, xml, results, expandContext ) {
+			var elem, j, matcher,
+				setMatched = [],
+				matchedCount = 0,
+				i = "0",
+				unmatched = seed && [],
+				outermost = expandContext != null,
+				contextBackup = outermostContext,
+				// We must always have either seed elements or context
+				elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
+				// Use integer dirruns iff this is the outermost matcher
+				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);
+
+			if ( outermost ) {
+				outermostContext = context !== document && context;
+				cachedruns = matcherCachedRuns;
+			}
+
+			// Add elements passing elementMatchers directly to results
+			// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
+			for ( ; (elem = elems[i]) != null; i++ ) {
+				if ( byElement && elem ) {
+					j = 0;
+					while ( (matcher = elementMatchers[j++]) ) {
+						if ( matcher( elem, context, xml ) ) {
+							results.push( elem );
+							break;
+						}
+					}
+					if ( outermost ) {
+						dirruns = dirrunsUnique;
+						cachedruns = ++matcherCachedRuns;
+					}
+				}
+
+				// Track unmatched elements for set filters
+				if ( bySet ) {
+					// They will have gone through all possible matchers
+					if ( (elem = !matcher && elem) ) {
+						matchedCount--;
+					}
+
+					// Lengthen the array for every element, matched or not
+					if ( seed ) {
+						unmatched.push( elem );
+					}
+				}
+			}
+
+			// Apply set filters to unmatched elements
+			matchedCount += i;
+			if ( bySet && i !== matchedCount ) {
+				j = 0;
+				while ( (matcher = setMatchers[j++]) ) {
+					matcher( unmatched, setMatched, context, xml );
+				}
+
+				if ( seed ) {
+					// Reintegrate element matches to eliminate the need for sorting
+					if ( matchedCount > 0 ) {
+						while ( i-- ) {
+							if ( !(unmatched[i] || setMatched[i]) ) {
+								setMatched[i] = pop.call( results );
+							}
+						}
+					}
+
+					// Discard index placeholder values to get only actual matches
+					setMatched = condense( setMatched );
+				}
+
+				// Add matches to results
+				push.apply( results, setMatched );
+
+				// Seedless set matches succeeding multiple successful matchers stipulate sorting
+				if ( outermost && !seed && setMatched.length > 0 &&
+					( matchedCount + setMatchers.length ) > 1 ) {
+
+					Sizzle.uniqueSort( results );
+				}
+			}
+
+			// Override manipulation of globals by nested matchers
+			if ( outermost ) {
+				dirruns = dirrunsUnique;
+				outermostContext = contextBackup;
+			}
+
+			return unmatched;
+		};
+
+	return bySet ?
+		markFunction( superMatcher ) :
+		superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
+	var i,
+		setMatchers = [],
+		elementMatchers = [],
+		cached = compilerCache[ selector + " " ];
+
+	if ( !cached ) {
+		// Generate a function of recursive functions that can be used to check each element
+		if ( !group ) {
+			group = tokenize( selector );
+		}
+		i = group.length;
+		while ( i-- ) {
+			cached = matcherFromTokens( group[i] );
+			if ( cached[ expando ] ) {
+				setMatchers.push( cached );
+			} else {
+				elementMatchers.push( cached );
+			}
+		}
+
+		// Cache the compiled function
+		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+	}
+	return cached;
+};
+
+function multipleContexts( selector, contexts, results ) {
+	var i = 0,
+		len = contexts.length;
+	for ( ; i < len; i++ ) {
+		Sizzle( selector, contexts[i], results );
+	}
+	return results;
+}
+
+function select( selector, context, results, seed ) {
+	var i, tokens, token, type, find,
+		match = tokenize( selector );
+
+	if ( !seed ) {
+		// Try to minimize operations if there is only one group
+		if ( match.length === 1 ) {
+
+			// Take a shortcut and set the context if the root selector is an ID
+			tokens = match[0] = match[0].slice( 0 );
+			if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+					support.getById && context.nodeType === 9 && documentIsHTML &&
+					Expr.relative[ tokens[1].type ] ) {
+
+				context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+				if ( !context ) {
+					return results;
+				}
+				selector = selector.slice( tokens.shift().value.length );
+			}
+
+			// Fetch a seed set for right-to-left matching
+			i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+			while ( i-- ) {
+				token = tokens[i];
+
+				// Abort if we hit a combinator
+				if ( Expr.relative[ (type = token.type) ] ) {
+					break;
+				}
+				if ( (find = Expr.find[ type ]) ) {
+					// Search, expanding context for leading sibling combinators
+					if ( (seed = find(
+						token.matches[0].replace( runescape, funescape ),
+						rsibling.test( tokens[0].type ) && context.parentNode || context
+					)) ) {
+
+						// If seed is empty or no tokens remain, we can return early
+						tokens.splice( i, 1 );
+						selector = seed.length && toSelector( tokens );
+						if ( !selector ) {
+							push.apply( results, seed );
+							return results;
+						}
+
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	// Compile and execute a filtering function
+	// Provide `match` to avoid retokenization if we modified the selector above
+	compile( selector, match )(
+		seed,
+		context,
+		!documentIsHTML,
+		results,
+		rsibling.test( selector )
+	);
+	return results;
+}
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+// Support: Chrome<14
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert(function( div1 ) {
+	// Should return 1, but returns 4 (following)
+	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+});
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert(function( div ) {
+	div.innerHTML = "<a href='#'></a>";
+	return div.firstChild.getAttribute("href") === "#" ;
+}) ) {
+	addHandle( "type|href|height|width", function( elem, name, isXML ) {
+		if ( !isXML ) {
+			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+		}
+	});
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert(function( div ) {
+	div.innerHTML = "<input/>";
+	div.firstChild.setAttribute( "value", "" );
+	return div.firstChild.getAttribute( "value" ) === "";
+}) ) {
+	addHandle( "value", function( elem, name, isXML ) {
+		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+			return elem.defaultValue;
+		}
+	});
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert(function( div ) {
+	return div.getAttribute("disabled") == null;
+}) ) {
+	addHandle( booleans, function( elem, name, isXML ) {
+		var val;
+		if ( !isXML ) {
+			return (val = elem.getAttributeNode( name )) && val.specified ?
+				val.value :
+				elem[ name ] === true ? name.toLowerCase() : null;
+		}
+	});
+}
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})( window );
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+	var object = optionsCache[ options ] = {};
+	jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
+		object[ flag ] = true;
+	});
+	return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ *	options: an optional list of space-separated options that will change how
+ *			the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ *	once:			will ensure the callback list can only be fired once (like a Deferred)
+ *
+ *	memory:			will keep track of previous values and will call any callback added
+ *					after the list has been fired right away with the latest "memorized"
+ *					values (like a Deferred)
+ *
+ *	unique:			will ensure a callback can only be added once (no duplicate in the list)
+ *
+ *	stopOnFalse:	interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+	// Convert options from String-formatted to Object-formatted if needed
+	// (we check in cache first)
+	options = typeof options === "string" ?
+		( optionsCache[ options ] || createOptions( options ) ) :
+		jQuery.extend( {}, options );
+
+	var // Last fire value (for non-forgettable lists)
+		memory,
+		// Flag to know if list was already fired
+		fired,
+		// Flag to know if list is currently firing
+		firing,
+		// First callback to fire (used internally by add and fireWith)
+		firingStart,
+		// End of the loop when firing
+		firingLength,
+		// Index of currently firing callback (modified by remove if needed)
+		firingIndex,
+		// Actual callback list
+		list = [],
+		// Stack of fire calls for repeatable lists
+		stack = !options.once && [],
+		// Fire callbacks
+		fire = function( data ) {
+			memory = options.memory && data;
+			fired = true;
+			firingIndex = firingStart || 0;
+			firingStart = 0;
+			firingLength = list.length;
+			firing = true;
+			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+					memory = false; // To prevent further calls using add
+					break;
+				}
+			}
+			firing = false;
+			if ( list ) {
+				if ( stack ) {
+					if ( stack.length ) {
+						fire( stack.shift() );
+					}
+				} else if ( memory ) {
+					list = [];
+				} else {
+					self.disable();
+				}
+			}
+		},
+		// Actual Callbacks object
+		self = {
+			// Add a callback or a collection of callbacks to the list
+			add: function() {
+				if ( list ) {
+					// First, we save the current length
+					var start = list.length;
+					(function add( args ) {
+						jQuery.each( args, function( _, arg ) {
+							var type = jQuery.type( arg );
+							if ( type === "function" ) {
+								if ( !options.unique || !self.has( arg ) ) {
+									list.push( arg );
+								}
+							} else if ( arg && arg.length && type !== "string" ) {
+								// Inspect recursively
+								add( arg );
+							}
+						});
+					})( arguments );
+					// Do we need to add the callbacks to the
+					// current firing batch?
+					if ( firing ) {
+						firingLength = list.length;
+					// With memory, if we're not firing then
+					// we should call right away
+					} else if ( memory ) {
+						firingStart = start;
+						fire( memory );
+					}
+				}
+				return this;
+			},
+			// Remove a callback from the list
+			remove: function() {
+				if ( list ) {
+					jQuery.each( arguments, function( _, arg ) {
+						var index;
+						while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+							list.splice( index, 1 );
+							// Handle firing indexes
+							if ( firing ) {
+								if ( index <= firingLength ) {
+									firingLength--;
+								}
+								if ( index <= firingIndex ) {
+									firingIndex--;
+								}
+							}
+						}
+					});
+				}
+				return this;
+			},
+			// Check if a given callback is in the list.
+			// If no argument is given, return whether or not list has callbacks attached.
+			has: function( fn ) {
+				return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
+			},
+			// Remove all callbacks from the list
+			empty: function() {
+				list = [];
+				firingLength = 0;
+				return this;
+			},
+			// Have the list do nothing anymore
+			disable: function() {
+				list = stack = memory = undefined;
+				return this;
+			},
+			// Is it disabled?
+			disabled: function() {
+				return !list;
+			},
+			// Lock the list in its current state
+			lock: function() {
+				stack = undefined;
+				if ( !memory ) {
+					self.disable();
+				}
+				return this;
+			},
+			// Is it locked?
+			locked: function() {
+				return !stack;
+			},
+			// Call all callbacks with the given context and arguments
+			fireWith: function( context, args ) {
+				if ( list && ( !fired || stack ) ) {
+					args = args || [];
+					args = [ context, args.slice ? args.slice() : args ];
+					if ( firing ) {
+						stack.push( args );
+					} else {
+						fire( args );
+					}
+				}
+				return this;
+			},
+			// Call all the callbacks with the given arguments
+			fire: function() {
+				self.fireWith( this, arguments );
+				return this;
+			},
+			// To know if the callbacks have already been called at least once
+			fired: function() {
+				return !!fired;
+			}
+		};
+
+	return self;
+};
+jQuery.extend({
+
+	Deferred: function( func ) {
+		var tuples = [
+				// action, add listener, listener list, final state
+				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+				[ "notify", "progress", jQuery.Callbacks("memory") ]
+			],
+			state = "pending",
+			promise = {
+				state: function() {
+					return state;
+				},
+				always: function() {
+					deferred.done( arguments ).fail( arguments );
+					return this;
+				},
+				then: function( /* fnDone, fnFail, fnProgress */ ) {
+					var fns = arguments;
+					return jQuery.Deferred(function( newDefer ) {
+						jQuery.each( tuples, function( i, tuple ) {
+							var action = tuple[ 0 ],
+								fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+							// deferred[ done | fail | progress ] for forwarding actions to newDefer
+							deferred[ tuple[1] ](function() {
+								var returned = fn && fn.apply( this, arguments );
+								if ( returned && jQuery.isFunction( returned.promise ) ) {
+									returned.promise()
+										.done( newDefer.resolve )
+										.fail( newDefer.reject )
+										.progress( newDefer.notify );
+								} else {
+									newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+								}
+							});
+						});
+						fns = null;
+					}).promise();
+				},
+				// Get a promise for this deferred
+				// If obj is provided, the promise aspect is added to the object
+				promise: function( obj ) {
+					return obj != null ? jQuery.extend( obj, promise ) : promise;
+				}
+			},
+			deferred = {};
+
+		// Keep pipe for back-compat
+		promise.pipe = promise.then;
+
+		// Add list-specific methods
+		jQuery.each( tuples, function( i, tuple ) {
+			var list = tuple[ 2 ],
+				stateString = tuple[ 3 ];
+
+			// promise[ done | fail | progress ] = list.add
+			promise[ tuple[1] ] = list.add;
+
+			// Handle state
+			if ( stateString ) {
+				list.add(function() {
+					// state = [ resolved | rejected ]
+					state = stateString;
+
+				// [ reject_list | resolve_list ].disable; progress_list.lock
+				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+			}
+
+			// deferred[ resolve | reject | notify ]
+			deferred[ tuple[0] ] = function() {
+				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+				return this;
+			};
+			deferred[ tuple[0] + "With" ] = list.fireWith;
+		});
+
+		// Make the deferred a promise
+		promise.promise( deferred );
+
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+
+		// All done!
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( subordinate /* , ..., subordinateN */ ) {
+		var i = 0,
+			resolveValues = core_slice.call( arguments ),
+			length = resolveValues.length,
+
+			// the count of uncompleted subordinates
+			remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+			// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+			// Update function for both resolve and progress values
+			updateFunc = function( i, contexts, values ) {
+				return function( value ) {
+					contexts[ i ] = this;
+					values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+					if( values === progressValues ) {
+						deferred.notifyWith( contexts, values );
+					} else if ( !( --remaining ) ) {
+						deferred.resolveWith( contexts, values );
+					}
+				};
+			},
+
+			progressValues, progressContexts, resolveContexts;
+
+		// add listeners to Deferred subordinates; treat others as resolved
+		if ( length > 1 ) {
+			progressValues = new Array( length );
+			progressContexts = new Array( length );
+			resolveContexts = new Array( length );
+			for ( ; i < length; i++ ) {
+				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+					resolveValues[ i ].promise()
+						.done( updateFunc( i, resolveContexts, resolveValues ) )
+						.fail( deferred.reject )
+						.progress( updateFunc( i, progressContexts, progressValues ) );
+				} else {
+					--remaining;
+				}
+			}
+		}
+
+		// if we're not waiting on anything, resolve the master
+		if ( !remaining ) {
+			deferred.resolveWith( resolveContexts, resolveValues );
+		}
+
+		return deferred.promise();
+	}
+});
+jQuery.support = (function( support ) {
+	var input = document.createElement("input"),
+		fragment = document.createDocumentFragment(),
+		div = document.createElement("div"),
+		select = document.createElement("select"),
+		opt = select.appendChild( document.createElement("option") );
+
+	// Finish early in limited environments
+	if ( !input.type ) {
+		return support;
+	}
+
+	input.type = "checkbox";
+
+	// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
+	// Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere)
+	support.checkOn = input.value !== "";
+
+	// Must access the parent to make an option select properly
+	// Support: IE9, IE10
+	support.optSelected = opt.selected;
+
+	// Will be defined later
+	support.reliableMarginRight = true;
+	support.boxSizingReliable = true;
+	support.pixelPosition = false;
+
+	// Make sure checked status is properly cloned
+	// Support: IE9, IE10
+	input.checked = true;
+	support.noCloneChecked = input.cloneNode( true ).checked;
+
+	// Make sure that the options inside disabled selects aren't marked as disabled
+	// (WebKit marks them as disabled)
+	select.disabled = true;
+	support.optDisabled = !opt.disabled;
+
+	// Check if an input maintains its value after becoming a radio
+	// Support: IE9, IE10
+	input = document.createElement("input");
+	input.value = "t";
+	input.type = "radio";
+	support.radioValue = input.value === "t";
+
+	// #11217 - WebKit loses check when the name is after the checked attribute
+	input.setAttribute( "checked", "t" );
+	input.setAttribute( "name", "t" );
+
+	fragment.appendChild( input );
+
+	// Support: Safari 5.1, Android 4.x, Android 2.3
+	// old WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	// Support: Firefox, Chrome, Safari
+	// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
+	support.focusinBubbles = "onfocusin" in window;
+
+	div.style.backgroundClip = "content-box";
+	div.cloneNode( true ).style.backgroundClip = "";
+	support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+	// Run tests that need a body at doc ready
+	jQuery(function() {
+		var container, marginDiv,
+			// Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
+			divReset = "padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",
+			body = document.getElementsByTagName("body")[ 0 ];
+
+		if ( !body ) {
+			// Return for frameset docs that don't have a body
+			return;
+		}
+
+		container = document.createElement("div");
+		container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
+
+		// Check box-sizing and margin behavior.
+		body.appendChild( container ).appendChild( div );
+		div.innerHTML = "";
+		// Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
+		div.style.cssText = "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%";
+
+		// Workaround failing boxSizing test due to offsetWidth returning wrong value
+		// with some non-1 values of body zoom, ticket #13543
+		jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {
+			support.boxSizing = div.offsetWidth === 4;
+		});
+
+		// Use window.getComputedStyle because jsdom on node.js will break without it.
+		if ( window.getComputedStyle ) {
+			support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+			support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+			// Support: Android 2.3
+			// Check if div with explicit width and no margin-right incorrectly
+			// gets computed margin-right based on width of container. (#3333)
+			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+			marginDiv = div.appendChild( document.createElement("div") );
+			marginDiv.style.cssText = div.style.cssText = divReset;
+			marginDiv.style.marginRight = marginDiv.style.width = "0";
+			div.style.width = "1px";
+
+			support.reliableMarginRight =
+				!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+		}
+
+		body.removeChild( container );
+	});
+
+	return support;
+})( {} );
+
+/*
+	Implementation Summary
+
+	1. Enforce API surface and semantic compatibility with 1.9.x branch
+	2. Improve the module's maintainability by reducing the storage
+		paths to a single mechanism.
+	3. Use the same single mechanism to support "private" and "user" data.
+	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+	5. Avoid exposing implementation details on user objects (eg. expando properties)
+	6. Provide a clear path for implementation upgrade to WeakMap in 2014
+*/
+var data_user, data_priv,
+	rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+	rmultiDash = /([A-Z])/g;
+
+function Data() {
+	// Support: Android < 4,
+	// Old WebKit does not have Object.preventExtensions/freeze method,
+	// return new empty object instead with no [[set]] accessor
+	Object.defineProperty( this.cache = {}, 0, {
+		get: function() {
+			return {};
+		}
+	});
+
+	this.expando = jQuery.expando + Math.random();
+}
+
+Data.uid = 1;
+
+Data.accepts = function( owner ) {
+	// Accepts only:
+	//  - Node
+	//    - Node.ELEMENT_NODE
+	//    - Node.DOCUMENT_NODE
+	//  - Object
+	//    - Any
+	return owner.nodeType ?
+		owner.nodeType === 1 || owner.nodeType === 9 : true;
+};
+
+Data.prototype = {
+	key: function( owner ) {
+		// We can accept data for non-element nodes in modern browsers,
+		// but we should not, see #8335.
+		// Always return the key for a frozen object.
+		if ( !Data.accepts( owner ) ) {
+			return 0;
+		}
+
+		var descriptor = {},
+			// Check if the owner object already has a cache key
+			unlock = owner[ this.expando ];
+
+		// If not, create one
+		if ( !unlock ) {
+			unlock = Data.uid++;
+
+			// Secure it in a non-enumerable, non-writable property
+			try {
+				descriptor[ this.expando ] = { value: unlock };
+				Object.defineProperties( owner, descriptor );
+
+			// Support: Android < 4
+			// Fallback to a less secure definition
+			} catch ( e ) {
+				descriptor[ this.expando ] = unlock;
+				jQuery.extend( owner, descriptor );
+			}
+		}
+
+		// Ensure the cache object
+		if ( !this.cache[ unlock ] ) {
+			this.cache[ unlock ] = {};
+		}
+
+		return unlock;
+	},
+	set: function( owner, data, value ) {
+		var prop,
+			// There may be an unlock assigned to this node,
+			// if there is no entry for this "owner", create one inline
+			// and set the unlock as though an owner entry had always existed
+			unlock = this.key( owner ),
+			cache = this.cache[ unlock ];
+
+		// Handle: [ owner, key, value ] args
+		if ( typeof data === "string" ) {
+			cache[ data ] = value;
+
+		// Handle: [ owner, { properties } ] args
+		} else {
+			// Fresh assignments by object are shallow copied
+			if ( jQuery.isEmptyObject( cache ) ) {
+				jQuery.extend( this.cache[ unlock ], data );
+			// Otherwise, copy the properties one-by-one to the cache object
+			} else {
+				for ( prop in data ) {
+					cache[ prop ] = data[ prop ];
+				}
+			}
+		}
+		return cache;
+	},
+	get: function( owner, key ) {
+		// Either a valid cache is found, or will be created.
+		// New caches will be created and the unlock returned,
+		// allowing direct access to the newly created
+		// empty data object. A valid owner object must be provided.
+		var cache = this.cache[ this.key( owner ) ];
+
+		return key === undefined ?
+			cache : cache[ key ];
+	},
+	access: function( owner, key, value ) {
+		var stored;
+		// In cases where either:
+		//
+		//   1. No key was specified
+		//   2. A string key was specified, but no value provided
+		//
+		// Take the "read" path and allow the get method to determine
+		// which value to return, respectively either:
+		//
+		//   1. The entire cache object
+		//   2. The data stored at the key
+		//
+		if ( key === undefined ||
+				((key && typeof key === "string") && value === undefined) ) {
+
+			stored = this.get( owner, key );
+
+			return stored !== undefined ?
+				stored : this.get( owner, jQuery.camelCase(key) );
+		}
+
+		// [*]When the key is not a string, or both a key and value
+		// are specified, set or extend (existing objects) with either:
+		//
+		//   1. An object of properties
+		//   2. A key and value
+		//
+		this.set( owner, key, value );
+
+		// Since the "set" path can have two possible entry points
+		// return the expected data based on which path was taken[*]
+		return value !== undefined ? value : key;
+	},
+	remove: function( owner, key ) {
+		var i, name, camel,
+			unlock = this.key( owner ),
+			cache = this.cache[ unlock ];
+
+		if ( key === undefined ) {
+			this.cache[ unlock ] = {};
+
+		} else {
+			// Support array or space separated string of keys
+			if ( jQuery.isArray( key ) ) {
+				// If "name" is an array of keys...
+				// When data is initially created, via ("key", "val") signature,
+				// keys will be converted to camelCase.
+				// Since there is no way to tell _how_ a key was added, remove
+				// both plain key and camelCase key. #12786
+				// This will only penalize the array argument path.
+				name = key.concat( key.map( jQuery.camelCase ) );
+			} else {
+				camel = jQuery.camelCase( key );
+				// Try the string as a key before any manipulation
+				if ( key in cache ) {
+					name = [ key, camel ];
+				} else {
+					// If a key with the spaces exists, use it.
+					// Otherwise, create an array by matching non-whitespace
+					name = camel;
+					name = name in cache ?
+						[ name ] : ( name.match( core_rnotwhite ) || [] );
+				}
+			}
+
+			i = name.length;
+			while ( i-- ) {
+				delete cache[ name[ i ] ];
+			}
+		}
+	},
+	hasData: function( owner ) {
+		return !jQuery.isEmptyObject(
+			this.cache[ owner[ this.expando ] ] || {}
+		);
+	},
+	discard: function( owner ) {
+		if ( owner[ this.expando ] ) {
+			delete this.cache[ owner[ this.expando ] ];
+		}
+	}
+};
+
+// These may be used throughout the jQuery core codebase
+data_user = new Data();
+data_priv = new Data();
+
+
+jQuery.extend({
+	acceptData: Data.accepts,
+
+	hasData: function( elem ) {
+		return data_user.hasData( elem ) || data_priv.hasData( elem );
+	},
+
+	data: function( elem, name, data ) {
+		return data_user.access( elem, name, data );
+	},
+
+	removeData: function( elem, name ) {
+		data_user.remove( elem, name );
+	},
+
+	// TODO: Now that all calls to _data and _removeData have been replaced
+	// with direct calls to data_priv methods, these can be deprecated.
+	_data: function( elem, name, data ) {
+		return data_priv.access( elem, name, data );
+	},
+
+	_removeData: function( elem, name ) {
+		data_priv.remove( elem, name );
+	}
+});
+
+jQuery.fn.extend({
+	data: function( key, value ) {
+		var attrs, name,
+			elem = this[ 0 ],
+			i = 0,
+			data = null;
+
+		// Gets all values
+		if ( key === undefined ) {
+			if ( this.length ) {
+				data = data_user.get( elem );
+
+				if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
+					attrs = elem.attributes;
+					for ( ; i < attrs.length; i++ ) {
+						name = attrs[ i ].name;
+
+						if ( name.indexOf( "data-" ) === 0 ) {
+							name = jQuery.camelCase( name.slice(5) );
+							dataAttr( elem, name, data[ name ] );
+						}
+					}
+					data_priv.set( elem, "hasDataAttrs", true );
+				}
+			}
+
+			return data;
+		}
+
+		// Sets multiple values
+		if ( typeof key === "object" ) {
+			return this.each(function() {
+				data_user.set( this, key );
+			});
+		}
+
+		return jQuery.access( this, function( value ) {
+			var data,
+				camelKey = jQuery.camelCase( key );
+
+			// The calling jQuery object (element matches) is not empty
+			// (and therefore has an element appears at this[ 0 ]) and the
+			// `value` parameter was not undefined. An empty jQuery object
+			// will result in `undefined` for elem = this[ 0 ] which will
+			// throw an exception if an attempt to read a data cache is made.
+			if ( elem && value === undefined ) {
+				// Attempt to get data from the cache
+				// with the key as-is
+				data = data_user.get( elem, key );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// Attempt to get data from the cache
+				// with the key camelized
+				data = data_user.get( elem, camelKey );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// Attempt to "discover" the data in
+				// HTML5 custom data-* attrs
+				data = dataAttr( elem, camelKey, undefined );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// We tried really hard, but the data doesn't exist.
+				return;
+			}
+
+			// Set the data...
+			this.each(function() {
+				// First, attempt to store a copy or reference of any
+				// data that might've been store with a camelCased key.
+				var data = data_user.get( this, camelKey );
+
+				// For HTML5 data-* attribute interop, we have to
+				// store property names with dashes in a camelCase form.
+				// This might not apply to all properties...*
+				data_user.set( this, camelKey, value );
+
+				// *... In the case of properties that might _actually_
+				// have dashes, we need to also store a copy of that
+				// unchanged property.
+				if ( key.indexOf("-") !== -1 && data !== undefined ) {
+					data_user.set( this, key, value );
+				}
+			});
+		}, null, value, arguments.length > 1, null, true );
+	},
+
+	removeData: function( key ) {
+		return this.each(function() {
+			data_user.remove( this, key );
+		});
+	}
+});
+
+function dataAttr( elem, key, data ) {
+	var name;
+
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+		name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+		data = elem.getAttribute( name );
+
+		if ( typeof data === "string" ) {
+			try {
+				data = data === "true" ? true :
+					data === "false" ? false :
+					data === "null" ? null :
+					// Only convert to a number if it doesn't change the string
+					+data + "" === data ? +data :
+					rbrace.test( data ) ? JSON.parse( data ) :
+					data;
+			} catch( e ) {}
+
+			// Make sure we set the data so it isn't changed later
+			data_user.set( elem, key, data );
+		} else {
+			data = undefined;
+		}
+	}
+	return data;
+}
+jQuery.extend({
+	queue: function( elem, type, data ) {
+		var queue;
+
+		if ( elem ) {
+			type = ( type || "fx" ) + "queue";
+			queue = data_priv.get( elem, type );
+
+			// Speed up dequeue by getting out quickly if this is just a lookup
+			if ( data ) {
+				if ( !queue || jQuery.isArray( data ) ) {
+					queue = data_priv.access( elem, type, jQuery.makeArray(data) );
+				} else {
+					queue.push( data );
+				}
+			}
+			return queue || [];
+		}
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			startLength = queue.length,
+			fn = queue.shift(),
+			hooks = jQuery._queueHooks( elem, type ),
+			next = function() {
+				jQuery.dequeue( elem, type );
+			};
+
+		// If the fx queue is dequeued, always remove the progress sentinel
+		if ( fn === "inprogress" ) {
+			fn = queue.shift();
+			startLength--;
+		}
+
+		if ( fn ) {
+
+			// Add a progress sentinel to prevent the fx queue from being
+			// automatically dequeued
+			if ( type === "fx" ) {
+				queue.unshift( "inprogress" );
+			}
+
+			// clear up the last queue stop function
+			delete hooks.stop;
+			fn.call( elem, next, hooks );
+		}
+
+		if ( !startLength && hooks ) {
+			hooks.empty.fire();
+		}
+	},
+
+	// not intended for public consumption - generates a queueHooks object, or returns the current one
+	_queueHooks: function( elem, type ) {
+		var key = type + "queueHooks";
+		return data_priv.get( elem, key ) || data_priv.access( elem, key, {
+			empty: jQuery.Callbacks("once memory").add(function() {
+				data_priv.remove( elem, [ type + "queue", key ] );
+			})
+		});
+	}
+});
+
+jQuery.fn.extend({
+	queue: function( type, data ) {
+		var setter = 2;
+
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+			setter--;
+		}
+
+		if ( arguments.length < setter ) {
+			return jQuery.queue( this[0], type );
+		}
+
+		return data === undefined ?
+			this :
+			this.each(function() {
+				var queue = jQuery.queue( this, type, data );
+
+				// ensure a hooks for this queue
+				jQuery._queueHooks( this, type );
+
+				if ( type === "fx" && queue[0] !== "inprogress" ) {
+					jQuery.dequeue( this, type );
+				}
+			});
+	},
+	dequeue: function( type ) {
+		return this.each(function() {
+			jQuery.dequeue( this, type );
+		});
+	},
+	// Based off of the plugin by Clint Helfers, with permission.
+	// http://blindsignals.com/index.php/2009/07/jquery-delay/
+	delay: function( time, type ) {
+		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+		type = type || "fx";
+
+		return this.queue( type, function( next, hooks ) {
+			var timeout = setTimeout( next, time );
+			hooks.stop = function() {
+				clearTimeout( timeout );
+			};
+		});
+	},
+	clearQueue: function( type ) {
+		return this.queue( type || "fx", [] );
+	},
+	// Get a promise resolved when queues of a certain type
+	// are emptied (fx is the type by default)
+	promise: function( type, obj ) {
+		var tmp,
+			count = 1,
+			defer = jQuery.Deferred(),
+			elements = this,
+			i = this.length,
+			resolve = function() {
+				if ( !( --count ) ) {
+					defer.resolveWith( elements, [ elements ] );
+				}
+			};
+
+		if ( typeof type !== "string" ) {
+			obj = type;
+			type = undefined;
+		}
+		type = type || "fx";
+
+		while( i-- ) {
+			tmp = data_priv.get( elements[ i ], type + "queueHooks" );
+			if ( tmp && tmp.empty ) {
+				count++;
+				tmp.empty.add( resolve );
+			}
+		}
+		resolve();
+		return defer.promise( obj );
+	}
+});
+var nodeHook, boolHook,
+	rclass = /[\t\r\n\f]/g,
+	rreturn = /\r/g,
+	rfocusable = /^(?:input|select|textarea|button)$/i;
+
+jQuery.fn.extend({
+	attr: function( name, value ) {
+		return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+	},
+
+	removeAttr: function( name ) {
+		return this.each(function() {
+			jQuery.removeAttr( this, name );
+		});
+	},
+
+	prop: function( name, value ) {
+		return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+	},
+
+	removeProp: function( name ) {
+		return this.each(function() {
+			delete this[ jQuery.propFix[ name ] || name ];
+		});
+	},
+
+	addClass: function( value ) {
+		var classes, elem, cur, clazz, j,
+			i = 0,
+			len = this.length,
+			proceed = typeof value === "string" && value;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).addClass( value.call( this, j, this.className ) );
+			});
+		}
+
+		if ( proceed ) {
+			// The disjunction here is for better compressibility (see removeClass)
+			classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+			for ( ; i < len; i++ ) {
+				elem = this[ i ];
+				cur = elem.nodeType === 1 && ( elem.className ?
+					( " " + elem.className + " " ).replace( rclass, " " ) :
+					" "
+				);
+
+				if ( cur ) {
+					j = 0;
+					while ( (clazz = classes[j++]) ) {
+						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+							cur += clazz + " ";
+						}
+					}
+					elem.className = jQuery.trim( cur );
+
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var classes, elem, cur, clazz, j,
+			i = 0,
+			len = this.length,
+			proceed = arguments.length === 0 || typeof value === "string" && value;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).removeClass( value.call( this, j, this.className ) );
+			});
+		}
+		if ( proceed ) {
+			classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+			for ( ; i < len; i++ ) {
+				elem = this[ i ];
+				// This expression is here for better compressibility (see addClass)
+				cur = elem.nodeType === 1 && ( elem.className ?
+					( " " + elem.className + " " ).replace( rclass, " " ) :
+					""
+				);
+
+				if ( cur ) {
+					j = 0;
+					while ( (clazz = classes[j++]) ) {
+						// Remove *all* instances
+						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
+							cur = cur.replace( " " + clazz + " ", " " );
+						}
+					}
+					elem.className = value ? jQuery.trim( cur ) : "";
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value;
+
+		if ( typeof stateVal === "boolean" && type === "string" ) {
+			return stateVal ? this.addClass( value ) : this.removeClass( value );
+		}
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+			});
+		}
+
+		return this.each(function() {
+			if ( type === "string" ) {
+				// toggle individual class names
+				var className,
+					i = 0,
+					self = jQuery( this ),
+					classNames = value.match( core_rnotwhite ) || [];
+
+				while ( (className = classNames[ i++ ]) ) {
+					// check each className given, space separated list
+					if ( self.hasClass( className ) ) {
+						self.removeClass( className );
+					} else {
+						self.addClass( className );
+					}
+				}
+
+			// Toggle whole class name
+			} else if ( type === core_strundefined || type === "boolean" ) {
+				if ( this.className ) {
+					// store className if set
+					data_priv.set( this, "__className__", this.className );
+				}
+
+				// If the element has a class name or if we're passed "false",
+				// then remove the whole classname (if there was one, the above saved it).
+				// Otherwise bring back whatever was previously saved (if anything),
+				// falling back to the empty string if nothing was stored.
+				this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
+			}
+		});
+	},
+
+	hasClass: function( selector ) {
+		var className = " " + selector + " ",
+			i = 0,
+			l = this.length;
+		for ( ; i < l; i++ ) {
+			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+				return true;
+			}
+		}
+
+		return false;
+	},
+
+	val: function( value ) {
+		var hooks, ret, isFunction,
+			elem = this[0];
+
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				return typeof ret === "string" ?
+					// handle most common string cases
+					ret.replace(rreturn, "") :
+					// handle cases where value is null/undef or number
+					ret == null ? "" : ret;
+			}
+
+			return;
+		}
+
+		isFunction = jQuery.isFunction( value );
+
+		return this.each(function( i ) {
+			var val;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call( this, i, jQuery( this ).val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+			} else if ( typeof val === "number" ) {
+				val += "";
+			} else if ( jQuery.isArray( val ) ) {
+				val = jQuery.map(val, function ( value ) {
+					return value == null ? "" : value + "";
+				});
+			}
+
+			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	valHooks: {
+		option: {
+			get: function( elem ) {
+				// attributes.value is undefined in Blackberry 4.7 but
+				// uses .value. See #6932
+				var val = elem.attributes.value;
+				return !val || val.specified ? elem.value : elem.text;
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value, option,
+					options = elem.options,
+					index = elem.selectedIndex,
+					one = elem.type === "select-one" || index < 0,
+					values = one ? null : [],
+					max = one ? index + 1 : options.length,
+					i = index < 0 ?
+						max :
+						one ? index : 0;
+
+				// Loop through all the selected options
+				for ( ; i < max; i++ ) {
+					option = options[ i ];
+
+					// IE6-9 doesn't update selected after form reset (#2551)
+					if ( ( option.selected || i === index ) &&
+							// Don't return options that are disabled or in a disabled optgroup
+							( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
+							( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+						// Get the specific value for the option
+						value = jQuery( option ).val();
+
+						// We don't need an array for one selects
+						if ( one ) {
+							return value;
+						}
+
+						// Multi-Selects return an array
+						values.push( value );
+					}
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var optionSet, option,
+					options = elem.options,
+					values = jQuery.makeArray( value ),
+					i = options.length;
+
+				while ( i-- ) {
+					option = options[ i ];
+					if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
+						optionSet = true;
+					}
+				}
+
+				// force browsers to behave consistently when non-matching value is set
+				if ( !optionSet ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	},
+
+	attr: function( elem, name, value ) {
+		var hooks, ret,
+			nType = elem.nodeType;
+
+		// don't get/set attributes on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( typeof elem.getAttribute === core_strundefined ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		// All attributes are lowercase
+		// Grab necessary hook if one is defined
+		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+			name = name.toLowerCase();
+			hooks = jQuery.attrHooks[ name ] ||
+				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
+		}
+
+		if ( value !== undefined ) {
+
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+
+			} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				elem.setAttribute( name, value + "" );
+				return value;
+			}
+
+		} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+			return ret;
+
+		} else {
+			ret = jQuery.find.attr( elem, name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret == null ?
+				undefined :
+				ret;
+		}
+	},
+
+	removeAttr: function( elem, value ) {
+		var name, propName,
+			i = 0,
+			attrNames = value && value.match( core_rnotwhite );
+
+		if ( attrNames && elem.nodeType === 1 ) {
+			while ( (name = attrNames[i++]) ) {
+				propName = jQuery.propFix[ name ] || name;
+
+				// Boolean attributes get special treatment (#10870)
+				if ( jQuery.expr.match.bool.test( name ) ) {
+					// Set corresponding property to false
+					elem[ propName ] = false;
+				}
+
+				elem.removeAttribute( name );
+			}
+		}
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+					// Setting the type on a radio button after the value resets the value in IE6-9
+					// Reset value to default in case type is set after value during creation
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		}
+	},
+
+	propFix: {
+		"for": "htmlFor",
+		"class": "className"
+	},
+
+	prop: function( elem, name, value ) {
+		var ret, hooks, notxml,
+			nType = elem.nodeType;
+
+		// don't get/set properties on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		if ( notxml ) {
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
+				ret :
+				( elem[ name ] = value );
+
+		} else {
+			return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
+				ret :
+				elem[ name ];
+		}
+	},
+
+	propHooks: {
+		tabIndex: {
+			get: function( elem ) {
+				return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ?
+					elem.tabIndex :
+					-1;
+			}
+		}
+	}
+});
+
+// Hooks for boolean attributes
+boolHook = {
+	set: function( elem, value, name ) {
+		if ( value === false ) {
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else {
+			elem.setAttribute( name, name );
+		}
+		return name;
+	}
+};
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+	var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;
+
+	jQuery.expr.attrHandle[ name ] = function( elem, name, isXML ) {
+		var fn = jQuery.expr.attrHandle[ name ],
+			ret = isXML ?
+				undefined :
+				/* jshint eqeqeq: false */
+				// Temporarily disable this handler to check existence
+				(jQuery.expr.attrHandle[ name ] = undefined) !=
+					getter( elem, name, isXML ) ?
+
+					name.toLowerCase() :
+					null;
+
+		// Restore handler
+		jQuery.expr.attrHandle[ name ] = fn;
+
+		return ret;
+	};
+});
+
+// Support: IE9+
+// Selectedness for an option in an optgroup can be inaccurate
+if ( !jQuery.support.optSelected ) {
+	jQuery.propHooks.selected = {
+		get: function( elem ) {
+			var parent = elem.parentNode;
+			if ( parent && parent.parentNode ) {
+				parent.parentNode.selectedIndex;
+			}
+			return null;
+		}
+	};
+}
+
+jQuery.each([
+	"tabIndex",
+	"readOnly",
+	"maxLength",
+	"cellSpacing",
+	"cellPadding",
+	"rowSpan",
+	"colSpan",
+	"useMap",
+	"frameBorder",
+	"contentEditable"
+], function() {
+	jQuery.propFix[ this.toLowerCase() ] = this;
+});
+
+// Radios and checkboxes getter/setter
+jQuery.each([ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = {
+		set: function( elem, value ) {
+			if ( jQuery.isArray( value ) ) {
+				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+			}
+		}
+	};
+	if ( !jQuery.support.checkOn ) {
+		jQuery.valHooks[ this ].get = function( elem ) {
+			// Support: Webkit
+			// "" is returned instead of "on" if a value isn't specified
+			return elem.getAttribute("value") === null ? "on" : elem.value;
+		};
+	}
+});
+var rkeyEvent = /^key/,
+	rmouseEvent = /^(?:mouse|contextmenu)|click/,
+	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+	rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+	return true;
+}
+
+function returnFalse() {
+	return false;
+}
+
+function safeActiveElement() {
+	try {
+		return document.activeElement;
+	} catch ( err ) { }
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+	global: {},
+
+	add: function( elem, types, handler, data, selector ) {
+
+		var handleObjIn, eventHandle, tmp,
+			events, t, handleObj,
+			special, handlers, type, namespaces, origType,
+			elemData = data_priv.get( elem );
+
+		// Don't attach events to noData or text/comment nodes (but allow plain objects)
+		if ( !elemData ) {
+			return;
+		}
+
+		// Caller can pass in an object of custom data in lieu of the handler
+		if ( handler.handler ) {
+			handleObjIn = handler;
+			handler = handleObjIn.handler;
+			selector = handleObjIn.selector;
+		}
+
+		// Make sure that the handler has a unique ID, used to find/remove it later
+		if ( !handler.guid ) {
+			handler.guid = jQuery.guid++;
+		}
+
+		// Init the element's event structure and main handler, if this is the first
+		if ( !(events = elemData.events) ) {
+			events = elemData.events = {};
+		}
+		if ( !(eventHandle = elemData.handle) ) {
+			eventHandle = elemData.handle = function( e ) {
+				// Discard the second event of a jQuery.event.trigger() and
+				// when an event is called after a page has unloaded
+				return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
+					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+					undefined;
+			};
+			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+			eventHandle.elem = elem;
+		}
+
+		// Handle multiple events separated by a space
+		types = ( types || "" ).match( core_rnotwhite ) || [""];
+		t = types.length;
+		while ( t-- ) {
+			tmp = rtypenamespace.exec( types[t] ) || [];
+			type = origType = tmp[1];
+			namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+			// There *must* be a type, no attaching namespace-only handlers
+			if ( !type ) {
+				continue;
+			}
+
+			// If event changes its type, use the special event handlers for the changed type
+			special = jQuery.event.special[ type ] || {};
+
+			// If selector defined, determine special event api type, otherwise given type
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+
+			// Update special based on newly reset type
+			special = jQuery.event.special[ type ] || {};
+
+			// handleObj is passed to all event handlers
+			handleObj = jQuery.extend({
+				type: type,
+				origType: origType,
+				data: data,
+				handler: handler,
+				guid: handler.guid,
+				selector: selector,
+				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+				namespace: namespaces.join(".")
+			}, handleObjIn );
+
+			// Init the event handler queue if we're the first
+			if ( !(handlers = events[ type ]) ) {
+				handlers = events[ type ] = [];
+				handlers.delegateCount = 0;
+
+				// Only use addEventListener if the special events handler returns false
+				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+					if ( elem.addEventListener ) {
+						elem.addEventListener( type, eventHandle, false );
+					}
+				}
+			}
+
+			if ( special.add ) {
+				special.add.call( elem, handleObj );
+
+				if ( !handleObj.handler.guid ) {
+					handleObj.handler.guid = handler.guid;
+				}
+			}
+
+			// Add to the element's handler list, delegates in front
+			if ( selector ) {
+				handlers.splice( handlers.delegateCount++, 0, handleObj );
+			} else {
+				handlers.push( handleObj );
+			}
+
+			// Keep track of which events have ever been used, for event optimization
+			jQuery.event.global[ type ] = true;
+		}
+
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	// Detach an event or set of events from an element
+	remove: function( elem, types, handler, selector, mappedTypes ) {
+
+		var j, origCount, tmp,
+			events, t, handleObj,
+			special, handlers, type, namespaces, origType,
+			elemData = data_priv.hasData( elem ) && data_priv.get( elem );
+
+		if ( !elemData || !(events = elemData.events) ) {
+			return;
+		}
+
+		// Once for each type.namespace in types; type may be omitted
+		types = ( types || "" ).match( core_rnotwhite ) || [""];
+		t = types.length;
+		while ( t-- ) {
+			tmp = rtypenamespace.exec( types[t] ) || [];
+			type = origType = tmp[1];
+			namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+			// Unbind all events (on this namespace, if provided) for the element
+			if ( !type ) {
+				for ( type in events ) {
+					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+				}
+				continue;
+			}
+
+			special = jQuery.event.special[ type ] || {};
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+			handlers = events[ type ] || [];
+			tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+
+			// Remove matching events
+			origCount = j = handlers.length;
+			while ( j-- ) {
+				handleObj = handlers[ j ];
+
+				if ( ( mappedTypes || origType === handleObj.origType ) &&
+					( !handler || handler.guid === handleObj.guid ) &&
+					( !tmp || tmp.test( handleObj.namespace ) ) &&
+					( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+					handlers.splice( j, 1 );
+
+					if ( handleObj.selector ) {
+						handlers.delegateCount--;
+					}
+					if ( special.remove ) {
+						special.remove.call( elem, handleObj );
+					}
+				}
+			}
+
+			// Remove generic event handler if we removed something and no more handlers exist
+			// (avoids potential for endless recursion during removal of special event handlers)
+			if ( origCount && !handlers.length ) {
+				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+					jQuery.removeEvent( elem, type, elemData.handle );
+				}
+
+				delete events[ type ];
+			}
+		}
+
+		// Remove the expando if it's no longer used
+		if ( jQuery.isEmptyObject( events ) ) {
+			delete elemData.handle;
+			data_priv.remove( elem, "events" );
+		}
+	},
+
+	trigger: function( event, data, elem, onlyHandlers ) {
+
+		var i, cur, tmp, bubbleType, ontype, handle, special,
+			eventPath = [ elem || document ],
+			type = core_hasOwn.call( event, "type" ) ? event.type : event,
+			namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
+
+		cur = tmp = elem = elem || document;
+
+		// Don't do events on text and comment nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		// focus/blur morphs to focusin/out; ensure we're not firing them right now
+		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+			return;
+		}
+
+		if ( type.indexOf(".") >= 0 ) {
+			// Namespaced trigger; create a regexp to match event type in handle()
+			namespaces = type.split(".");
+			type = namespaces.shift();
+			namespaces.sort();
+		}
+		ontype = type.indexOf(":") < 0 && "on" + type;
+
+		// Caller can pass in a jQuery.Event object, Object, or just an event type string
+		event = event[ jQuery.expando ] ?
+			event :
+			new jQuery.Event( type, typeof event === "object" && event );
+
+		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+		event.isTrigger = onlyHandlers ? 2 : 3;
+		event.namespace = namespaces.join(".");
+		event.namespace_re = event.namespace ?
+			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+			null;
+
+		// Clean up the event in case it is being reused
+		event.result = undefined;
+		if ( !event.target ) {
+			event.target = elem;
+		}
+
+		// Clone any incoming data and prepend the event, creating the handler arg list
+		data = data == null ?
+			[ event ] :
+			jQuery.makeArray( data, [ event ] );
+
+		// Allow special events to draw outside the lines
+		special = jQuery.event.special[ type ] || {};
+		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+			return;
+		}
+
+		// Determine event propagation path in advance, per W3C events spec (#9951)
+		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+			bubbleType = special.delegateType || type;
+			if ( !rfocusMorph.test( bubbleType + type ) ) {
+				cur = cur.parentNode;
+			}
+			for ( ; cur; cur = cur.parentNode ) {
+				eventPath.push( cur );
+				tmp = cur;
+			}
+
+			// Only add window if we got to document (e.g., not plain obj or detached DOM)
+			if ( tmp === (elem.ownerDocument || document) ) {
+				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+			}
+		}
+
+		// Fire handlers on the event path
+		i = 0;
+		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
+
+			event.type = i > 1 ?
+				bubbleType :
+				special.bindType || type;
+
+			// jQuery handler
+			handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
+			if ( handle ) {
+				handle.apply( cur, data );
+			}
+
+			// Native handler
+			handle = ontype && cur[ ontype ];
+			if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+				event.preventDefault();
+			}
+		}
+		event.type = type;
+
+		// If nobody prevented the default action, do it now
+		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+				jQuery.acceptData( elem ) ) {
+
+				// Call a native DOM method on the target with the same name name as the event.
+				// Don't do default actions on window, that's where global variables be (#6170)
+				if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
+
+					// Don't re-trigger an onFOO event when we call its FOO() method
+					tmp = elem[ ontype ];
+
+					if ( tmp ) {
+						elem[ ontype ] = null;
+					}
+
+					// Prevent re-triggering of the same event, since we already bubbled it above
+					jQuery.event.triggered = type;
+					elem[ type ]();
+					jQuery.event.triggered = undefined;
+
+					if ( tmp ) {
+						elem[ ontype ] = tmp;
+					}
+				}
+			}
+		}
+
+		return event.result;
+	},
+
+	dispatch: function( event ) {
+
+		// Make a writable jQuery.Event from the native event object
+		event = jQuery.event.fix( event );
+
+		var i, j, ret, matched, handleObj,
+			handlerQueue = [],
+			args = core_slice.call( arguments ),
+			handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
+			special = jQuery.event.special[ event.type ] || {};
+
+		// Use the fix-ed jQuery.Event rather than the (read-only) native event
+		args[0] = event;
+		event.delegateTarget = this;
+
+		// Call the preDispatch hook for the mapped type, and let it bail if desired
+		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+			return;
+		}
+
+		// Determine handlers
+		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+		// Run delegates first; they may want to stop propagation beneath us
+		i = 0;
+		while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+			event.currentTarget = matched.elem;
+
+			j = 0;
+			while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+				// Triggered event must either 1) have no namespace, or
+				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+				if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+
+					event.handleObj = handleObj;
+					event.data = handleObj.data;
+
+					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+							.apply( matched.elem, args );
+
+					if ( ret !== undefined ) {
+						if ( (event.result = ret) === false ) {
+							event.preventDefault();
+							event.stopPropagation();
+						}
+					}
+				}
+			}
+		}
+
+		// Call the postDispatch hook for the mapped type
+		if ( special.postDispatch ) {
+			special.postDispatch.call( this, event );
+		}
+
+		return event.result;
+	},
+
+	handlers: function( event, handlers ) {
+		var i, matches, sel, handleObj,
+			handlerQueue = [],
+			delegateCount = handlers.delegateCount,
+			cur = event.target;
+
+		// Find delegate handlers
+		// Black-hole SVG <use> instance trees (#13180)
+		// Avoid non-left-click bubbling in Firefox (#3861)
+		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
+
+			for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+				if ( cur.disabled !== true || event.type !== "click" ) {
+					matches = [];
+					for ( i = 0; i < delegateCount; i++ ) {
+						handleObj = handlers[ i ];
+
+						// Don't conflict with Object.prototype properties (#13203)
+						sel = handleObj.selector + " ";
+
+						if ( matches[ sel ] === undefined ) {
+							matches[ sel ] = handleObj.needsContext ?
+								jQuery( sel, this ).index( cur ) >= 0 :
+								jQuery.find( sel, this, null, [ cur ] ).length;
+						}
+						if ( matches[ sel ] ) {
+							matches.push( handleObj );
+						}
+					}
+					if ( matches.length ) {
+						handlerQueue.push({ elem: cur, handlers: matches });
+					}
+				}
+			}
+		}
+
+		// Add the remaining (directly-bound) handlers
+		if ( delegateCount < handlers.length ) {
+			handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
+		}
+
+		return handlerQueue;
+	},
+
+	// Includes some event props shared by KeyEvent and MouseEvent
+	props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+	fixHooks: {},
+
+	keyHooks: {
+		props: "char charCode key keyCode".split(" "),
+		filter: function( event, original ) {
+
+			// Add which for key events
+			if ( event.which == null ) {
+				event.which = original.charCode != null ? original.charCode : original.keyCode;
+			}
+
+			return event;
+		}
+	},
+
+	mouseHooks: {
+		props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+		filter: function( event, original ) {
+			var eventDoc, doc, body,
+				button = original.button;
+
+			// Calculate pageX/Y if missing and clientX/Y available
+			if ( event.pageX == null && original.clientX != null ) {
+				eventDoc = event.target.ownerDocument || document;
+				doc = eventDoc.documentElement;
+				body = eventDoc.body;
+
+				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
+			}
+
+			// Add which for click: 1 === left; 2 === middle; 3 === right
+			// Note: button is not normalized, so don't use it
+			if ( !event.which && button !== undefined ) {
+				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+			}
+
+			return event;
+		}
+	},
+
+	fix: function( event ) {
+		if ( event[ jQuery.expando ] ) {
+			return event;
+		}
+
+		// Create a writable copy of the event object and normalize some properties
+		var i, prop, copy,
+			type = event.type,
+			originalEvent = event,
+			fixHook = this.fixHooks[ type ];
+
+		if ( !fixHook ) {
+			this.fixHooks[ type ] = fixHook =
+				rmouseEvent.test( type ) ? this.mouseHooks :
+				rkeyEvent.test( type ) ? this.keyHooks :
+				{};
+		}
+		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+		event = new jQuery.Event( originalEvent );
+
+		i = copy.length;
+		while ( i-- ) {
+			prop = copy[ i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Support: Cordova 2.5 (WebKit) (#13255)
+		// All events should have a target; Cordova deviceready doesn't
+		if ( !event.target ) {
+			event.target = document;
+		}
+
+		// Support: Safari 6.0+, Chrome < 28
+		// Target should not be a text node (#504, #13143)
+		if ( event.target.nodeType === 3 ) {
+			event.target = event.target.parentNode;
+		}
+
+		return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+	},
+
+	special: {
+		load: {
+			// Prevent triggered image.load events from bubbling to window.load
+			noBubble: true
+		},
+		focus: {
+			// Fire native event if possible so blur/focus sequence is correct
+			trigger: function() {
+				if ( this !== safeActiveElement() && this.focus ) {
+					this.focus();
+					return false;
+				}
+			},
+			delegateType: "focusin"
+		},
+		blur: {
+			trigger: function() {
+				if ( this === safeActiveElement() && this.blur ) {
+					this.blur();
+					return false;
+				}
+			},
+			delegateType: "focusout"
+		},
+		click: {
+			// For checkbox, fire native event so checked state will be right
+			trigger: function() {
+				if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
+					this.click();
+					return false;
+				}
+			},
+
+			// For cross-browser consistency, don't fire native .click() on links
+			_default: function( event ) {
+				return jQuery.nodeName( event.target, "a" );
+			}
+		},
+
+		beforeunload: {
+			postDispatch: function( event ) {
+
+				// Support: Firefox 20+
+				// Firefox doesn't alert if the returnValue field is not set.
+				if ( event.result !== undefined ) {
+					event.originalEvent.returnValue = event.result;
+				}
+			}
+		}
+	},
+
+	simulate: function( type, elem, event, bubble ) {
+		// Piggyback on a donor event to simulate a different one.
+		// Fake originalEvent to avoid donor's stopPropagation, but if the
+		// simulated event prevents default then we do the same on the donor.
+		var e = jQuery.extend(
+			new jQuery.Event(),
+			event,
+			{
+				type: type,
+				isSimulated: true,
+				originalEvent: {}
+			}
+		);
+		if ( bubble ) {
+			jQuery.event.trigger( e, null, elem );
+		} else {
+			jQuery.event.dispatch.call( elem, e );
+		}
+		if ( e.isDefaultPrevented() ) {
+			event.preventDefault();
+		}
+	}
+};
+
+jQuery.removeEvent = function( elem, type, handle ) {
+	if ( elem.removeEventListener ) {
+		elem.removeEventListener( type, handle, false );
+	}
+};
+
+jQuery.Event = function( src, props ) {
+	// Allow instantiation without the 'new' keyword
+	if ( !(this instanceof jQuery.Event) ) {
+		return new jQuery.Event( src, props );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = ( src.defaultPrevented ||
+			src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// Put explicitly provided properties onto the event object
+	if ( props ) {
+		jQuery.extend( this, props );
+	}
+
+	// Create a timestamp if incoming event doesn't have one
+	this.timeStamp = src && src.timeStamp || jQuery.now();
+
+	// Mark it as fixed
+	this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse,
+
+	preventDefault: function() {
+		var e = this.originalEvent;
+
+		this.isDefaultPrevented = returnTrue;
+
+		if ( e && e.preventDefault ) {
+			e.preventDefault();
+		}
+	},
+	stopPropagation: function() {
+		var e = this.originalEvent;
+
+		this.isPropagationStopped = returnTrue;
+
+		if ( e && e.stopPropagation ) {
+			e.stopPropagation();
+		}
+	},
+	stopImmediatePropagation: function() {
+		this.isImmediatePropagationStopped = returnTrue;
+		this.stopPropagation();
+	}
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// Support: Chrome 15+
+jQuery.each({
+	mouseenter: "mouseover",
+	mouseleave: "mouseout"
+}, function( orig, fix ) {
+	jQuery.event.special[ orig ] = {
+		delegateType: fix,
+		bindType: fix,
+
+		handle: function( event ) {
+			var ret,
+				target = this,
+				related = event.relatedTarget,
+				handleObj = event.handleObj;
+
+			// For mousenter/leave call the handler if related is outside the target.
+			// NB: No relatedTarget if the mouse left/entered the browser window
+			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+				event.type = handleObj.origType;
+				ret = handleObj.handler.apply( this, arguments );
+				event.type = fix;
+			}
+			return ret;
+		}
+	};
+});
+
+// Create "bubbling" focus and blur events
+// Support: Firefox, Chrome, Safari
+if ( !jQuery.support.focusinBubbles ) {
+	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler while someone wants focusin/focusout
+		var attaches = 0,
+			handler = function( event ) {
+				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+			};
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				if ( attaches++ === 0 ) {
+					document.addEventListener( orig, handler, true );
+				}
+			},
+			teardown: function() {
+				if ( --attaches === 0 ) {
+					document.removeEventListener( orig, handler, true );
+				}
+			}
+		};
+	});
+}
+
+jQuery.fn.extend({
+
+	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+		var origFn, type;
+
+		// Types can be a map of types/handlers
+		if ( typeof types === "object" ) {
+			// ( types-Object, selector, data )
+			if ( typeof selector !== "string" ) {
+				// ( types-Object, data )
+				data = data || selector;
+				selector = undefined;
+			}
+			for ( type in types ) {
+				this.on( type, selector, data, types[ type ], one );
+			}
+			return this;
+		}
+
+		if ( data == null && fn == null ) {
+			// ( types, fn )
+			fn = selector;
+			data = selector = undefined;
+		} else if ( fn == null ) {
+			if ( typeof selector === "string" ) {
+				// ( types, selector, fn )
+				fn = data;
+				data = undefined;
+			} else {
+				// ( types, data, fn )
+				fn = data;
+				data = selector;
+				selector = undefined;
+			}
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		} else if ( !fn ) {
+			return this;
+		}
+
+		if ( one === 1 ) {
+			origFn = fn;
+			fn = function( event ) {
+				// Can use an empty set, since event contains the info
+				jQuery().off( event );
+				return origFn.apply( this, arguments );
+			};
+			// Use same guid so caller can remove using origFn
+			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+		}
+		return this.each( function() {
+			jQuery.event.add( this, types, fn, data, selector );
+		});
+	},
+	one: function( types, selector, data, fn ) {
+		return this.on( types, selector, data, fn, 1 );
+	},
+	off: function( types, selector, fn ) {
+		var handleObj, type;
+		if ( types && types.preventDefault && types.handleObj ) {
+			// ( event )  dispatched jQuery.Event
+			handleObj = types.handleObj;
+			jQuery( types.delegateTarget ).off(
+				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+				handleObj.selector,
+				handleObj.handler
+			);
+			return this;
+		}
+		if ( typeof types === "object" ) {
+			// ( types-object [, selector] )
+			for ( type in types ) {
+				this.off( type, selector, types[ type ] );
+			}
+			return this;
+		}
+		if ( selector === false || typeof selector === "function" ) {
+			// ( types [, fn] )
+			fn = selector;
+			selector = undefined;
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		}
+		return this.each(function() {
+			jQuery.event.remove( this, types, fn, selector );
+		});
+	},
+
+	trigger: function( type, data ) {
+		return this.each(function() {
+			jQuery.event.trigger( type, data, this );
+		});
+	},
+	triggerHandler: function( type, data ) {
+		var elem = this[0];
+		if ( elem ) {
+			return jQuery.event.trigger( type, data, elem, true );
+		}
+	}
+});
+var isSimple = /^.[^:#\[\.,]*$/,
+	rparentsprev = /^(?:parents|prev(?:Until|All))/,
+	rneedsContext = jQuery.expr.match.needsContext,
+	// methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.fn.extend({
+	find: function( selector ) {
+		var i,
+			ret = [],
+			self = this,
+			len = self.length;
+
+		if ( typeof selector !== "string" ) {
+			return this.pushStack( jQuery( selector ).filter(function() {
+				for ( i = 0; i < len; i++ ) {
+					if ( jQuery.contains( self[ i ], this ) ) {
+						return true;
+					}
+				}
+			}) );
+		}
+
+		for ( i = 0; i < len; i++ ) {
+			jQuery.find( selector, self[ i ], ret );
+		}
+
+		// Needed because $( selector, context ) becomes $( context ).find( selector )
+		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+		ret.selector = this.selector ? this.selector + " " + selector : selector;
+		return ret;
+	},
+
+	has: function( target ) {
+		var targets = jQuery( target, this ),
+			l = targets.length;
+
+		return this.filter(function() {
+			var i = 0;
+			for ( ; i < l; i++ ) {
+				if ( jQuery.contains( this, targets[i] ) ) {
+					return true;
+				}
+			}
+		});
+	},
+
+	not: function( selector ) {
+		return this.pushStack( winnow(this, selector || [], true) );
+	},
+
+	filter: function( selector ) {
+		return this.pushStack( winnow(this, selector || [], false) );
+	},
+
+	is: function( selector ) {
+		return !!winnow(
+			this,
+
+			// If this is a positional/relative selector, check membership in the returned set
+			// so $("p:first").is("p:last") won't return true for a doc with two "p".
+			typeof selector === "string" && rneedsContext.test( selector ) ?
+				jQuery( selector ) :
+				selector || [],
+			false
+		).length;
+	},
+
+	closest: function( selectors, context ) {
+		var cur,
+			i = 0,
+			l = this.length,
+			matched = [],
+			pos = ( rneedsContext.test( selectors ) || typeof selectors !== "string" ) ?
+				jQuery( selectors, context || this.context ) :
+				0;
+
+		for ( ; i < l; i++ ) {
+			for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
+				// Always skip document fragments
+				if ( cur.nodeType < 11 && (pos ?
+					pos.index(cur) > -1 :
+
+					// Don't pass non-elements to Sizzle
+					cur.nodeType === 1 &&
+						jQuery.find.matchesSelector(cur, selectors)) ) {
+
+					cur = matched.push( cur );
+					break;
+				}
+			}
+		}
+
+		return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
+	},
+
+	// Determine the position of an element within
+	// the matched set of elements
+	index: function( elem ) {
+
+		// No argument, return index in parent
+		if ( !elem ) {
+			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+		}
+
+		// index in selector
+		if ( typeof elem === "string" ) {
+			return core_indexOf.call( jQuery( elem ), this[ 0 ] );
+		}
+
+		// Locate the position of the desired element
+		return core_indexOf.call( this,
+
+			// If it receives a jQuery object, the first element is used
+			elem.jquery ? elem[ 0 ] : elem
+		);
+	},
+
+	add: function( selector, context ) {
+		var set = typeof selector === "string" ?
+				jQuery( selector, context ) :
+				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+			all = jQuery.merge( this.get(), set );
+
+		return this.pushStack( jQuery.unique(all) );
+	},
+
+	addBack: function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter(selector)
+		);
+	}
+});
+
+function sibling( cur, dir ) {
+	while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
+
+	return cur;
+}
+
+jQuery.each({
+	parent: function( elem ) {
+		var parent = elem.parentNode;
+		return parent && parent.nodeType !== 11 ? parent : null;
+	},
+	parents: function( elem ) {
+		return jQuery.dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return sibling( elem, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return sibling( elem, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return jQuery.dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return jQuery.dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+	},
+	children: function( elem ) {
+		return jQuery.sibling( elem.firstChild );
+	},
+	contents: function( elem ) {
+		return elem.contentDocument || jQuery.merge( [], elem.childNodes );
+	}
+}, function( name, fn ) {
+	jQuery.fn[ name ] = function( until, selector ) {
+		var matched = jQuery.map( this, fn, until );
+
+		if ( name.slice( -5 ) !== "Until" ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			matched = jQuery.filter( selector, matched );
+		}
+
+		if ( this.length > 1 ) {
+			// Remove duplicates
+			if ( !guaranteedUnique[ name ] ) {
+				jQuery.unique( matched );
+			}
+
+			// Reverse order for parents* and prev-derivatives
+			if ( rparentsprev.test( name ) ) {
+				matched.reverse();
+			}
+		}
+
+		return this.pushStack( matched );
+	};
+});
+
+jQuery.extend({
+	filter: function( expr, elems, not ) {
+		var elem = elems[ 0 ];
+
+		if ( not ) {
+			expr = ":not(" + expr + ")";
+		}
+
+		return elems.length === 1 && elem.nodeType === 1 ?
+			jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+			jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+				return elem.nodeType === 1;
+			}));
+	},
+
+	dir: function( elem, dir, until ) {
+		var matched = [],
+			truncate = until !== undefined;
+
+		while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
+			if ( elem.nodeType === 1 ) {
+				if ( truncate && jQuery( elem ).is( until ) ) {
+					break;
+				}
+				matched.push( elem );
+			}
+		}
+		return matched;
+	},
+
+	sibling: function( n, elem ) {
+		var matched = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				matched.push( n );
+			}
+		}
+
+		return matched;
+	}
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+	if ( jQuery.isFunction( qualifier ) ) {
+		return jQuery.grep( elements, function( elem, i ) {
+			/* jshint -W018 */
+			return !!qualifier.call( elem, i, elem ) !== not;
+		});
+
+	}
+
+	if ( qualifier.nodeType ) {
+		return jQuery.grep( elements, function( elem ) {
+			return ( elem === qualifier ) !== not;
+		});
+
+	}
+
+	if ( typeof qualifier === "string" ) {
+		if ( isSimple.test( qualifier ) ) {
+			return jQuery.filter( qualifier, elements, not );
+		}
+
+		qualifier = jQuery.filter( qualifier, elements );
+	}
+
+	return jQuery.grep( elements, function( elem ) {
+		return ( core_indexOf.call( qualifier, elem ) >= 0 ) !== not;
+	});
+}
+var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+	rtagName = /<([\w:]+)/,
+	rhtml = /<|&#?\w+;/,
+	rnoInnerhtml = /<(?:script|style|link)/i,
+	manipulation_rcheckableType = /^(?:checkbox|radio)$/i,
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptType = /^$|\/(?:java|ecma)script/i,
+	rscriptTypeMasked = /^true\/(.*)/,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
+
+	// We have to close these tags to support XHTML (#13200)
+	wrapMap = {
+
+		// Support: IE 9
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+
+		thead: [ 1, "<table>", "</table>" ],
+		col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+		_default: [ 0, "", "" ]
+	};
+
+// Support: IE 9
+wrapMap.optgroup = wrapMap.option;
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+jQuery.fn.extend({
+	text: function( value ) {
+		return jQuery.access( this, function( value ) {
+			return value === undefined ?
+				jQuery.text( this ) :
+				this.empty().append( ( this[ 0 ] && this[ 0 ].ownerDocument || document ).createTextNode( value ) );
+		}, null, value, arguments.length );
+	},
+
+	append: function() {
+		return this.domManip( arguments, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+				var target = manipulationTarget( this, elem );
+				target.appendChild( elem );
+			}
+		});
+	},
+
+	prepend: function() {
+		return this.domManip( arguments, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+				var target = manipulationTarget( this, elem );
+				target.insertBefore( elem, target.firstChild );
+			}
+		});
+	},
+
+	before: function() {
+		return this.domManip( arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this );
+			}
+		});
+	},
+
+	after: function() {
+		return this.domManip( arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			}
+		});
+	},
+
+	// keepData is for internal use only--do not document
+	remove: function( selector, keepData ) {
+		var elem,
+			elems = selector ? jQuery.filter( selector, this ) : this,
+			i = 0;
+
+		for ( ; (elem = elems[i]) != null; i++ ) {
+			if ( !keepData && elem.nodeType === 1 ) {
+				jQuery.cleanData( getAll( elem ) );
+			}
+
+			if ( elem.parentNode ) {
+				if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
+					setGlobalEval( getAll( elem, "script" ) );
+				}
+				elem.parentNode.removeChild( elem );
+			}
+		}
+
+		return this;
+	},
+
+	empty: function() {
+		var elem,
+			i = 0;
+
+		for ( ; (elem = this[i]) != null; i++ ) {
+			if ( elem.nodeType === 1 ) {
+
+				// Prevent memory leaks
+				jQuery.cleanData( getAll( elem, false ) );
+
+				// Remove any remaining nodes
+				elem.textContent = "";
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function () {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		});
+	},
+
+	html: function( value ) {
+		return jQuery.access( this, function( value ) {
+			var elem = this[ 0 ] || {},
+				i = 0,
+				l = this.length;
+
+			if ( value === undefined && elem.nodeType === 1 ) {
+				return elem.innerHTML;
+			}
+
+			// See if we can take a shortcut and just use innerHTML
+			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+				value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+				try {
+					for ( ; i < l; i++ ) {
+						elem = this[ i ] || {};
+
+						// Remove element nodes and prevent memory leaks
+						if ( elem.nodeType === 1 ) {
+							jQuery.cleanData( getAll( elem, false ) );
+							elem.innerHTML = value;
+						}
+					}
+
+					elem = 0;
+
+				// If using innerHTML throws an exception, use the fallback method
+				} catch( e ) {}
+			}
+
+			if ( elem ) {
+				this.empty().append( value );
+			}
+		}, null, value, arguments.length );
+	},
+
+	replaceWith: function() {
+		var
+			// Snapshot the DOM in case .domManip sweeps something relevant into its fragment
+			args = jQuery.map( this, function( elem ) {
+				return [ elem.nextSibling, elem.parentNode ];
+			}),
+			i = 0;
+
+		// Make the changes, replacing each context element with the new content
+		this.domManip( arguments, function( elem ) {
+			var next = args[ i++ ],
+				parent = args[ i++ ];
+
+			if ( parent ) {
+				// Don't use the snapshot next if it has moved (#13810)
+				if ( next && next.parentNode !== parent ) {
+					next = this.nextSibling;
+				}
+				jQuery( this ).remove();
+				parent.insertBefore( elem, next );
+			}
+		// Allow new content to include elements from the context set
+		}, true );
+
+		// Force removal if there was no new content (e.g., from empty arguments)
+		return i ? this : this.remove();
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, callback, allowIntersection ) {
+
+		// Flatten any nested arrays
+		args = core_concat.apply( [], args );
+
+		var fragment, first, scripts, hasScripts, node, doc,
+			i = 0,
+			l = this.length,
+			set = this,
+			iNoClone = l - 1,
+			value = args[ 0 ],
+			isFunction = jQuery.isFunction( value );
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
+			return this.each(function( index ) {
+				var self = set.eq( index );
+				if ( isFunction ) {
+					args[ 0 ] = value.call( this, index, self.html() );
+				}
+				self.domManip( args, callback, allowIntersection );
+			});
+		}
+
+		if ( l ) {
+			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );
+			first = fragment.firstChild;
+
+			if ( fragment.childNodes.length === 1 ) {
+				fragment = first;
+			}
+
+			if ( first ) {
+				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+				hasScripts = scripts.length;
+
+				// Use the original fragment for the last item instead of the first because it can end up
+				// being emptied incorrectly in certain situations (#8070).
+				for ( ; i < l; i++ ) {
+					node = fragment;
+
+					if ( i !== iNoClone ) {
+						node = jQuery.clone( node, true, true );
+
+						// Keep references to cloned scripts for later restoration
+						if ( hasScripts ) {
+							// Support: QtWebKit
+							// jQuery.merge because core_push.apply(_, arraylike) throws
+							jQuery.merge( scripts, getAll( node, "script" ) );
+						}
+					}
+
+					callback.call( this[ i ], node, i );
+				}
+
+				if ( hasScripts ) {
+					doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+					// Reenable scripts
+					jQuery.map( scripts, restoreScript );
+
+					// Evaluate executable scripts on first document insertion
+					for ( i = 0; i < hasScripts; i++ ) {
+						node = scripts[ i ];
+						if ( rscriptType.test( node.type || "" ) &&
+							!data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
+
+							if ( node.src ) {
+								// Hope ajax is available...
+								jQuery._evalUrl( node.src );
+							} else {
+								jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return this;
+	}
+});
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var elems,
+			ret = [],
+			insert = jQuery( selector ),
+			last = insert.length - 1,
+			i = 0;
+
+		for ( ; i <= last; i++ ) {
+			elems = i === last ? this : this.clone( true );
+			jQuery( insert[ i ] )[ original ]( elems );
+
+			// Support: QtWebKit
+			// .get() because core_push.apply(_, arraylike) throws
+			core_push.apply( ret, elems.get() );
+		}
+
+		return this.pushStack( ret );
+	};
+});
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var i, l, srcElements, destElements,
+			clone = elem.cloneNode( true ),
+			inPage = jQuery.contains( elem.ownerDocument, elem );
+
+		// Support: IE >= 9
+		// Fix Cloning issues
+		if ( !jQuery.support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) {
+
+			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
+			destElements = getAll( clone );
+			srcElements = getAll( elem );
+
+			for ( i = 0, l = srcElements.length; i < l; i++ ) {
+				fixInput( srcElements[ i ], destElements[ i ] );
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			if ( deepDataAndEvents ) {
+				srcElements = srcElements || getAll( elem );
+				destElements = destElements || getAll( clone );
+
+				for ( i = 0, l = srcElements.length; i < l; i++ ) {
+					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+				}
+			} else {
+				cloneCopyEvent( elem, clone );
+			}
+		}
+
+		// Preserve script evaluation history
+		destElements = getAll( clone, "script" );
+		if ( destElements.length > 0 ) {
+			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+		}
+
+		// Return the cloned set
+		return clone;
+	},
+
+	buildFragment: function( elems, context, scripts, selection ) {
+		var elem, tmp, tag, wrap, contains, j,
+			i = 0,
+			l = elems.length,
+			fragment = context.createDocumentFragment(),
+			nodes = [];
+
+		for ( ; i < l; i++ ) {
+			elem = elems[ i ];
+
+			if ( elem || elem === 0 ) {
+
+				// Add nodes directly
+				if ( jQuery.type( elem ) === "object" ) {
+					// Support: QtWebKit
+					// jQuery.merge because core_push.apply(_, arraylike) throws
+					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+				// Convert non-html into a text node
+				} else if ( !rhtml.test( elem ) ) {
+					nodes.push( context.createTextNode( elem ) );
+
+				// Convert html into DOM nodes
+				} else {
+					tmp = tmp || fragment.appendChild( context.createElement("div") );
+
+					// Deserialize a standard representation
+					tag = ( rtagName.exec( elem ) || ["", ""] )[ 1 ].toLowerCase();
+					wrap = wrapMap[ tag ] || wrapMap._default;
+					tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ];
+
+					// Descend through wrappers to the right content
+					j = wrap[ 0 ];
+					while ( j-- ) {
+						tmp = tmp.lastChild;
+					}
+
+					// Support: QtWebKit
+					// jQuery.merge because core_push.apply(_, arraylike) throws
+					jQuery.merge( nodes, tmp.childNodes );
+
+					// Remember the top-level container
+					tmp = fragment.firstChild;
+
+					// Fixes #12346
+					// Support: Webkit, IE
+					tmp.textContent = "";
+				}
+			}
+		}
+
+		// Remove wrapper from fragment
+		fragment.textContent = "";
+
+		i = 0;
+		while ( (elem = nodes[ i++ ]) ) {
+
+			// #4087 - If origin and destination elements are the same, and this is
+			// that element, do not do anything
+			if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
+				continue;
+			}
+
+			contains = jQuery.contains( elem.ownerDocument, elem );
+
+			// Append to fragment
+			tmp = getAll( fragment.appendChild( elem ), "script" );
+
+			// Preserve script evaluation history
+			if ( contains ) {
+				setGlobalEval( tmp );
+			}
+
+			// Capture executables
+			if ( scripts ) {
+				j = 0;
+				while ( (elem = tmp[ j++ ]) ) {
+					if ( rscriptType.test( elem.type || "" ) ) {
+						scripts.push( elem );
+					}
+				}
+			}
+		}
+
+		return fragment;
+	},
+
+	cleanData: function( elems ) {
+		var data, elem, events, type, key, j,
+			special = jQuery.event.special,
+			i = 0;
+
+		for ( ; (elem = elems[ i ]) !== undefined; i++ ) {
+			if ( Data.accepts( elem ) ) {
+				key = elem[ data_priv.expando ];
+
+				if ( key && (data = data_priv.cache[ key ]) ) {
+					events = Object.keys( data.events || {} );
+					if ( events.length ) {
+						for ( j = 0; (type = events[j]) !== undefined; j++ ) {
+							if ( special[ type ] ) {
+								jQuery.event.remove( elem, type );
+
+							// This is a shortcut to avoid jQuery.event.remove's overhead
+							} else {
+								jQuery.removeEvent( elem, type, data.handle );
+							}
+						}
+					}
+					if ( data_priv.cache[ key ] ) {
+						// Discard any remaining `private` data
+						delete data_priv.cache[ key ];
+					}
+				}
+			}
+			// Discard any remaining `user` data
+			delete data_user.cache[ elem[ data_user.expando ] ];
+		}
+	},
+
+	_evalUrl: function( url ) {
+		return jQuery.ajax({
+			url: url,
+			type: "GET",
+			dataType: "script",
+			async: false,
+			global: false,
+			"throws": true
+		});
+	}
+});
+
+// Support: 1.x compatibility
+// Manipulating tables requires a tbody
+function manipulationTarget( elem, content ) {
+	return jQuery.nodeName( elem, "table" ) &&
+		jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ?
+
+		elem.getElementsByTagName("tbody")[0] ||
+			elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
+		elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+	elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type;
+	return elem;
+}
+function restoreScript( elem ) {
+	var match = rscriptTypeMasked.exec( elem.type );
+
+	if ( match ) {
+		elem.type = match[ 1 ];
+	} else {
+		elem.removeAttribute("type");
+	}
+
+	return elem;
+}
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+	var l = elems.length,
+		i = 0;
+
+	for ( ; i < l; i++ ) {
+		data_priv.set(
+			elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" )
+		);
+	}
+}
+
+function cloneCopyEvent( src, dest ) {
+	var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
+
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// 1. Copy private data: events, handlers, etc.
+	if ( data_priv.hasData( src ) ) {
+		pdataOld = data_priv.access( src );
+		pdataCur = data_priv.set( dest, pdataOld );
+		events = pdataOld.events;
+
+		if ( events ) {
+			delete pdataCur.handle;
+			pdataCur.events = {};
+
+			for ( type in events ) {
+				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+					jQuery.event.add( dest, type, events[ type ][ i ] );
+				}
+			}
+		}
+	}
+
+	// 2. Copy user data
+	if ( data_user.hasData( src ) ) {
+		udataOld = data_user.access( src );
+		udataCur = jQuery.extend( {}, udataOld );
+
+		data_user.set( dest, udataCur );
+	}
+}
+
+
+function getAll( context, tag ) {
+	var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
+			context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
+			[];
+
+	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+		jQuery.merge( [ context ], ret ) :
+		ret;
+}
+
+// Support: IE >= 9
+function fixInput( src, dest ) {
+	var nodeName = dest.nodeName.toLowerCase();
+
+	// Fails to persist the checked state of a cloned checkbox or radio button.
+	if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) {
+		dest.checked = src.checked;
+
+	// Fails to return the selected option to the default selected state when cloning options
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+	}
+}
+jQuery.fn.extend({
+	wrapAll: function( html ) {
+		var wrap;
+
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).wrapAll( html.call(this, i) );
+			});
+		}
+
+		if ( this[ 0 ] ) {
+
+			// The elements to wrap the target around
+			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+			if ( this[ 0 ].parentNode ) {
+				wrap.insertBefore( this[ 0 ] );
+			}
+
+			wrap.map(function() {
+				var elem = this;
+
+				while ( elem.firstElementChild ) {
+					elem = elem.firstElementChild;
+				}
+
+				return elem;
+			}).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).wrapInner( html.call(this, i) );
+			});
+		}
+
+		return this.each(function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		});
+	},
+
+	wrap: function( html ) {
+		var isFunction = jQuery.isFunction( html );
+
+		return this.each(function( i ) {
+			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+		});
+	},
+
+	unwrap: function() {
+		return this.parent().each(function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		}).end();
+	}
+});
+var curCSS, iframe,
+	// swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+	// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+	rmargin = /^margin/,
+	rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+	rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+	rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ),
+	elemdisplay = { BODY: "block" },
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssNormalTransform = {
+		letterSpacing: 0,
+		fontWeight: 400
+	},
+
+	cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+	// shortcut for names that are not vendor prefixed
+	if ( name in style ) {
+		return name;
+	}
+
+	// check for vendor prefixed names
+	var capName = name.charAt(0).toUpperCase() + name.slice(1),
+		origName = name,
+		i = cssPrefixes.length;
+
+	while ( i-- ) {
+		name = cssPrefixes[ i ] + capName;
+		if ( name in style ) {
+			return name;
+		}
+	}
+
+	return origName;
+}
+
+function isHidden( elem, el ) {
+	// isHidden might be called from jQuery#filter function;
+	// in that case, element will be second argument
+	elem = el || elem;
+	return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+}
+
+// NOTE: we've included the "window" in window.getComputedStyle
+// because jsdom on node.js will break without it.
+function getStyles( elem ) {
+	return window.getComputedStyle( elem, null );
+}
+
+function showHide( elements, show ) {
+	var display, elem, hidden,
+		values = [],
+		index = 0,
+		length = elements.length;
+
+	for ( ; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+
+		values[ index ] = data_priv.get( elem, "olddisplay" );
+		display = elem.style.display;
+		if ( show ) {
+			// Reset the inline display of this element to learn if it is
+			// being hidden by cascaded rules or not
+			if ( !values[ index ] && display === "none" ) {
+				elem.style.display = "";
+			}
+
+			// Set elements which have been overridden with display: none
+			// in a stylesheet to whatever the default browser style is
+			// for such an element
+			if ( elem.style.display === "" && isHidden( elem ) ) {
+				values[ index ] = data_priv.access( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+			}
+		} else {
+
+			if ( !values[ index ] ) {
+				hidden = isHidden( elem );
+
+				if ( display && display !== "none" || !hidden ) {
+					data_priv.set( elem, "olddisplay", hidden ? display : jQuery.css(elem, "display") );
+				}
+			}
+		}
+	}
+
+	// Set the display of most of the elements in a second loop
+	// to avoid the constant reflow
+	for ( index = 0; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+			elem.style.display = show ? values[ index ] || "" : "none";
+		}
+	}
+
+	return elements;
+}
+
+jQuery.fn.extend({
+	css: function( name, value ) {
+		return jQuery.access( this, function( elem, name, value ) {
+			var styles, len,
+				map = {},
+				i = 0;
+
+			if ( jQuery.isArray( name ) ) {
+				styles = getStyles( elem );
+				len = name.length;
+
+				for ( ; i < len; i++ ) {
+					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+				}
+
+				return map;
+			}
+
+			return value !== undefined ?
+				jQuery.style( elem, name, value ) :
+				jQuery.css( elem, name );
+		}, name, value, arguments.length > 1 );
+	},
+	show: function() {
+		return showHide( this, true );
+	},
+	hide: function() {
+		return showHide( this );
+	},
+	toggle: function( state ) {
+		if ( typeof state === "boolean" ) {
+			return state ? this.show() : this.hide();
+		}
+
+		return this.each(function() {
+			if ( isHidden( this ) ) {
+				jQuery( this ).show();
+			} else {
+				jQuery( this ).hide();
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity" );
+					return ret === "" ? "1" : ret;
+				}
+			}
+		}
+	},
+
+	// Don't automatically add "px" to these possibly-unitless properties
+	cssNumber: {
+		"columnCount": true,
+		"fillOpacity": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"order": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		// normalize float css property
+		"float": "cssFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, hooks,
+			origName = jQuery.camelCase( name ),
+			style = elem.style;
+
+		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+		// gets hook for the prefixed version
+		// followed by the unprefixed version
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// convert relative number strings (+= or -=) to relative numbers. #7345
+			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that NaN and null values aren't set. See: #7116
+			if ( value == null || type === "number" && isNaN( value ) ) {
+				return;
+			}
+
+			// If a number was passed in, add 'px' to the (except for certain CSS properties)
+			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+				value += "px";
+			}
+
+			// Fixes #8908, it can be done more correctly by specifying setters in cssHooks,
+			// but it would mean to define eight (for every problematic property) identical functions
+			if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
+				style[ name ] = "inherit";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+				style[ name ] = value;
+			}
+
+		} else {
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra, styles ) {
+		var val, num, hooks,
+			origName = jQuery.camelCase( name );
+
+		// Make sure that we're working with the right name
+		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+		// gets hook for the prefixed version
+		// followed by the unprefixed version
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks ) {
+			val = hooks.get( elem, true, extra );
+		}
+
+		// Otherwise, if a way to get the computed value exists, use that
+		if ( val === undefined ) {
+			val = curCSS( elem, name, styles );
+		}
+
+		//convert "normal" to computed value
+		if ( val === "normal" && name in cssNormalTransform ) {
+			val = cssNormalTransform[ name ];
+		}
+
+		// Return, converting to number if forced or a qualifier was provided and val looks numeric
+		if ( extra === "" || extra ) {
+			num = parseFloat( val );
+			return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
+		}
+		return val;
+	}
+});
+
+curCSS = function( elem, name, _computed ) {
+	var width, minWidth, maxWidth,
+		computed = _computed || getStyles( elem ),
+
+		// Support: IE9
+		// getPropertyValue is only needed for .css('filter') in IE9, see #12537
+		ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
+		style = elem.style;
+
+	if ( computed ) {
+
+		if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+			ret = jQuery.style( elem, name );
+		}
+
+		// Support: Safari 5.1
+		// A tribute to the "awesome hack by Dean Edwards"
+		// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+		// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+		if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+
+			// Remember the original values
+			width = style.width;
+			minWidth = style.minWidth;
+			maxWidth = style.maxWidth;
+
+			// Put in the new values to get a computed value out
+			style.minWidth = style.maxWidth = style.width = ret;
+			ret = computed.width;
+
+			// Revert the changed values
+			style.width = width;
+			style.minWidth = minWidth;
+			style.maxWidth = maxWidth;
+		}
+	}
+
+	return ret;
+};
+
+
+function setPositiveNumber( elem, value, subtract ) {
+	var matches = rnumsplit.exec( value );
+	return matches ?
+		// Guard against undefined "subtract", e.g., when used as in cssHooks
+		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+		value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+	var i = extra === ( isBorderBox ? "border" : "content" ) ?
+		// If we already have the right measurement, avoid augmentation
+		4 :
+		// Otherwise initialize for horizontal or vertical properties
+		name === "width" ? 1 : 0,
+
+		val = 0;
+
+	for ( ; i < 4; i += 2 ) {
+		// both box models exclude margin, so add it if we want it
+		if ( extra === "margin" ) {
+			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+		}
+
+		if ( isBorderBox ) {
+			// border-box includes padding, so remove it if we want content
+			if ( extra === "content" ) {
+				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+			}
+
+			// at this point, extra isn't border nor margin, so remove border
+			if ( extra !== "margin" ) {
+				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+		} else {
+			// at this point, extra isn't content, so add padding
+			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+			// at this point, extra isn't content nor padding, so add border
+			if ( extra !== "padding" ) {
+				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+		}
+	}
+
+	return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+	// Start with offset property, which is equivalent to the border-box value
+	var valueIsBorderBox = true,
+		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		styles = getStyles( elem ),
+		isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+	// some non-html elements return undefined for offsetWidth, so check for null/undefined
+	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+	if ( val <= 0 || val == null ) {
+		// Fall back to computed then uncomputed css if necessary
+		val = curCSS( elem, name, styles );
+		if ( val < 0 || val == null ) {
+			val = elem.style[ name ];
+		}
+
+		// Computed unit is not pixels. Stop here and return.
+		if ( rnumnonpx.test(val) ) {
+			return val;
+		}
+
+		// we need the check for style in case a browser which returns unreliable values
+		// for getComputedStyle silently falls back to the reliable elem.style
+		valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+		// Normalize "", auto, and prepare for extra
+		val = parseFloat( val ) || 0;
+	}
+
+	// use the active box-sizing model to add/subtract irrelevant styles
+	return ( val +
+		augmentWidthOrHeight(
+			elem,
+			name,
+			extra || ( isBorderBox ? "border" : "content" ),
+			valueIsBorderBox,
+			styles
+		)
+	) + "px";
+}
+
+// Try to determine the default display value of an element
+function css_defaultDisplay( nodeName ) {
+	var doc = document,
+		display = elemdisplay[ nodeName ];
+
+	if ( !display ) {
+		display = actualDisplay( nodeName, doc );
+
+		// If the simple way fails, read from inside an iframe
+		if ( display === "none" || !display ) {
+			// Use the already-created iframe if possible
+			iframe = ( iframe ||
+				jQuery("<iframe frameborder='0' width='0' height='0'/>")
+				.css( "cssText", "display:block !important" )
+			).appendTo( doc.documentElement );
+
+			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
+			doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;
+			doc.write("<!doctype html><html><body>");
+			doc.close();
+
+			display = actualDisplay( nodeName, doc );
+			iframe.detach();
+		}
+
+		// Store the correct default display
+		elemdisplay[ nodeName ] = display;
+	}
+
+	return display;
+}
+
+// Called ONLY from within css_defaultDisplay
+function actualDisplay( name, doc ) {
+	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
+		display = jQuery.css( elem[0], "display" );
+	elem.remove();
+	return display;
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			if ( computed ) {
+				// certain elements can have dimension info if we invisibly show them
+				// however, it must have a current display style that would benefit from this
+				return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
+					jQuery.swap( elem, cssShow, function() {
+						return getWidthOrHeight( elem, name, extra );
+					}) :
+					getWidthOrHeight( elem, name, extra );
+			}
+		},
+
+		set: function( elem, value, extra ) {
+			var styles = extra && getStyles( elem );
+			return setPositiveNumber( elem, value, extra ?
+				augmentWidthOrHeight(
+					elem,
+					name,
+					extra,
+					jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+					styles
+				) : 0
+			);
+		}
+	};
+});
+
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
+jQuery(function() {
+	// Support: Android 2.3
+	if ( !jQuery.support.reliableMarginRight ) {
+		jQuery.cssHooks.marginRight = {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// Support: Android 2.3
+					// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+					// Work around by temporarily setting element display to inline-block
+					return jQuery.swap( elem, { "display": "inline-block" },
+						curCSS, [ elem, "marginRight" ] );
+				}
+			}
+		};
+	}
+
+	// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+	// getComputedStyle returns percent when specified for top/left/bottom/right
+	// rather than make the css module depend on the offset module, we just check for it here
+	if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+		jQuery.each( [ "top", "left" ], function( i, prop ) {
+			jQuery.cssHooks[ prop ] = {
+				get: function( elem, computed ) {
+					if ( computed ) {
+						computed = curCSS( elem, prop );
+						// if curCSS returns percentage, fallback to offset
+						return rnumnonpx.test( computed ) ?
+							jQuery( elem ).position()[ prop ] + "px" :
+							computed;
+					}
+				}
+			};
+		});
+	}
+
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.hidden = function( elem ) {
+		// Support: Opera <= 12.12
+		// Opera reports offsetWidths and offsetHeights less than zero on some elements
+		return elem.offsetWidth <= 0 && elem.offsetHeight <= 0;
+	};
+
+	jQuery.expr.filters.visible = function( elem ) {
+		return !jQuery.expr.filters.hidden( elem );
+	};
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+	margin: "",
+	padding: "",
+	border: "Width"
+}, function( prefix, suffix ) {
+	jQuery.cssHooks[ prefix + suffix ] = {
+		expand: function( value ) {
+			var i = 0,
+				expanded = {},
+
+				// assumes a single number if not a string
+				parts = typeof value === "string" ? value.split(" ") : [ value ];
+
+			for ( ; i < 4; i++ ) {
+				expanded[ prefix + cssExpand[ i ] + suffix ] =
+					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+			}
+
+			return expanded;
+		}
+	};
+
+	if ( !rmargin.test( prefix ) ) {
+		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+	}
+});
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+	rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+jQuery.fn.extend({
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+	serializeArray: function() {
+		return this.map(function(){
+			// Can add propHook for "elements" to filter or add form elements
+			var elements = jQuery.prop( this, "elements" );
+			return elements ? jQuery.makeArray( elements ) : this;
+		})
+		.filter(function(){
+			var type = this.type;
+			// Use .is(":disabled") so that fieldset[disabled] works
+			return this.name && !jQuery( this ).is( ":disabled" ) &&
+				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+				( this.checked || !manipulation_rcheckableType.test( type ) );
+		})
+		.map(function( i, elem ){
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val ){
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).get();
+	}
+});
+
+//Serialize an array of form elements or a set of
+//key/values into a query string
+jQuery.param = function( a, traditional ) {
+	var prefix,
+		s = [],
+		add = function( key, value ) {
+			// If value is a function, invoke it and return its value
+			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+		};
+
+	// Set traditional to true for jQuery <= 1.3.2 behavior.
+	if ( traditional === undefined ) {
+		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+	}
+
+	// If an array was passed in, assume that it is an array of form elements.
+	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+		// Serialize the form elements
+		jQuery.each( a, function() {
+			add( this.name, this.value );
+		});
+
+	} else {
+		// If traditional, encode the "old" way (the way 1.3.2 or older
+		// did it), otherwise encode params recursively.
+		for ( prefix in a ) {
+			buildParams( prefix, a[ prefix ], traditional, add );
+		}
+	}
+
+	// Return the resulting serialization
+	return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+	var name;
+
+	if ( jQuery.isArray( obj ) ) {
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+				// Item is non-scalar (array or object), encode its numeric index.
+				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+			}
+		});
+
+	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
+		// Serialize object item.
+		for ( name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+	// Handle event binding
+	jQuery.fn[ name ] = function( data, fn ) {
+		return arguments.length > 0 ?
+			this.on( name, null, data, fn ) :
+			this.trigger( name );
+	};
+});
+
+jQuery.fn.extend({
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	},
+
+	bind: function( types, data, fn ) {
+		return this.on( types, null, data, fn );
+	},
+	unbind: function( types, fn ) {
+		return this.off( types, null, fn );
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.on( types, selector, data, fn );
+	},
+	undelegate: function( selector, types, fn ) {
+		// ( namespace ) or ( selector, types [, fn] )
+		return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+	}
+});
+var
+	// Document location
+	ajaxLocParts,
+	ajaxLocation,
+
+	ajax_nonce = jQuery.now(),
+
+	ajax_rquery = /\?/,
+	rhash = /#.*$/,
+	rts = /([?&])_=[^&]*/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
+
+	// Keep a copy of the old load method
+	_load = jQuery.fn.load,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = "*/".concat("*");
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+	ajaxLocation = location.href;
+} catch( e ) {
+	// Use the href attribute of an A element
+	// since IE will modify it given document.location
+	ajaxLocation = document.createElement( "a" );
+	ajaxLocation.href = "";
+	ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		var dataType,
+			i = 0,
+			dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];
+
+		if ( jQuery.isFunction( func ) ) {
+			// For each dataType in the dataTypeExpression
+			while ( (dataType = dataTypes[i++]) ) {
+				// Prepend if requested
+				if ( dataType[0] === "+" ) {
+					dataType = dataType.slice( 1 ) || "*";
+					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );
+
+				// Otherwise append
+				} else {
+					(structure[ dataType ] = structure[ dataType ] || []).push( func );
+				}
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+	var inspected = {},
+		seekingTransport = ( structure === transports );
+
+	function inspect( dataType ) {
+		var selected;
+		inspected[ dataType ] = true;
+		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+			if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+				options.dataTypes.unshift( dataTypeOrTransport );
+				inspect( dataTypeOrTransport );
+				return false;
+			} else if ( seekingTransport ) {
+				return !( selected = dataTypeOrTransport );
+			}
+		});
+		return selected;
+	}
+
+	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var key, deep,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+	for ( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+
+	return target;
+}
+
+jQuery.fn.load = function( url, params, callback ) {
+	if ( typeof url !== "string" && _load ) {
+		return _load.apply( this, arguments );
+	}
+
+	var selector, type, response,
+		self = this,
+		off = url.indexOf(" ");
+
+	if ( off >= 0 ) {
+		selector = url.slice( off );
+		url = url.slice( 0, off );
+	}
+
+	// If it's a function
+	if ( jQuery.isFunction( params ) ) {
+
+		// We assume that it's the callback
+		callback = params;
+		params = undefined;
+
+	// Otherwise, build a param string
+	} else if ( params && typeof params === "object" ) {
+		type = "POST";
+	}
+
+	// If we have elements to modify, make the request
+	if ( self.length > 0 ) {
+		jQuery.ajax({
+			url: url,
+
+			// if "type" variable is undefined, then "GET" method will be used
+			type: type,
+			dataType: "html",
+			data: params
+		}).done(function( responseText ) {
+
+			// Save response for use in complete callback
+			response = arguments;
+
+			self.html( selector ?
+
+				// If a selector was specified, locate the right elements in a dummy div
+				// Exclude scripts to avoid IE 'Permission Denied' errors
+				jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+				// Otherwise use the full result
+				responseText );
+
+		}).complete( callback && function( jqXHR, status ) {
+			self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+		});
+	}
+
+	return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
+	jQuery.fn[ type ] = function( fn ){
+		return this.on( type, fn );
+	};
+});
+
+jQuery.extend({
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {},
+
+	ajaxSettings: {
+		url: ajaxLocation,
+		type: "GET",
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+		global: true,
+		processData: true,
+		async: true,
+		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		throws: false,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			"*": allTypes,
+			text: "text/plain",
+			html: "text/html",
+			xml: "application/xml, text/xml",
+			json: "application/json, text/javascript"
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText",
+			json: "responseJSON"
+		},
+
+		// Data converters
+		// Keys separate source (or catchall "*") and destination types with a single space
+		converters: {
+
+			// Convert anything to text
+			"* text": String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			url: true,
+			context: true
+		}
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		return settings ?
+
+			// Building a settings object
+			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+			// Extending ajaxSettings
+			ajaxExtend( jQuery.ajaxSettings, target );
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var transport,
+			// URL without anti-cache param
+			cacheURL,
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+			// timeout handle
+			timeoutTimer,
+			// Cross-domain detection vars
+			parts,
+			// To know if global events are to be dispatched
+			fireGlobals,
+			// Loop variable
+			i,
+			// Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events is callbackContext if it is a DOM node or jQuery collection
+			globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+				jQuery( callbackContext ) :
+				jQuery.event,
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery.Callbacks("once memory"),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+			// The jqXHR state
+			state = 0,
+			// Default abort message
+			strAbort = "canceled",
+			// Fake xhr
+			jqXHR = {
+				readyState: 0,
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while ( (match = rheaders.exec( responseHeadersString )) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match == null ? null : match;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					var lname = name.toLowerCase();
+					if ( !state ) {
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Status-dependent callbacks
+				statusCode: function( map ) {
+					var code;
+					if ( map ) {
+						if ( state < 2 ) {
+							for ( code in map ) {
+								// Lazy-add the new callback in a way that preserves old ones
+								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+							}
+						} else {
+							// Execute the appropriate callbacks
+							jqXHR.always( map[ jqXHR.status ] );
+						}
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					var finalText = statusText || strAbort;
+					if ( transport ) {
+						transport.abort( finalText );
+					}
+					done( 0, finalText );
+					return this;
+				}
+			};
+
+		// Attach deferreds
+		deferred.promise( jqXHR ).complete = completeDeferred.add;
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (prefilters might expect it)
+		// Handle falsy url in the settings object (#10093: consistency with old signature)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" )
+			.replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+		// Alias method option to type as per ticket #12004
+		s.type = options.method || options.type || s.method || s.type;
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
+
+		// A cross-domain request is in order when we have a protocol:host:port mismatch
+		if ( s.crossDomain == null ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefilter, stop there
+		if ( state === 2 ) {
+			return jqXHR;
+		}
+
+		// We can fire global events as of now if asked to
+		fireGlobals = s.global;
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger("ajaxStart");
+		}
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Save the URL in case we're toying with the If-Modified-Since
+		// and/or If-None-Match header later on
+		cacheURL = s.url;
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+				s.url = rts.test( cacheURL ) ?
+
+					// If there is already a '_' parameter, set its value
+					cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
+
+					// Otherwise add one to the end
+					cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
+			}
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			if ( jQuery.lastModified[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+			}
+			if ( jQuery.etag[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+			// Abort if not done already and return
+			return jqXHR.abort();
+		}
+
+		// aborting is no longer a cancellation
+		strAbort = "abort";
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout(function() {
+					jqXHR.abort("timeout");
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch ( e ) {
+				// Propagate exception as error if not done
+				if ( state < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					throw e;
+				}
+			}
+		}
+
+		// Callback for when everything is done
+		function done( status, nativeStatusText, responses, headers ) {
+			var isSuccess, success, error, response, modified,
+				statusText = nativeStatusText;
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			// Determine if successful
+			isSuccess = status >= 200 && status < 300 || status === 304;
+
+			// Get response data
+			if ( responses ) {
+				response = ajaxHandleResponses( s, jqXHR, responses );
+			}
+
+			// Convert no matter what (that way responseXXX fields are always set)
+			response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+			// If successful, handle type chaining
+			if ( isSuccess ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+					modified = jqXHR.getResponseHeader("Last-Modified");
+					if ( modified ) {
+						jQuery.lastModified[ cacheURL ] = modified;
+					}
+					modified = jqXHR.getResponseHeader("etag");
+					if ( modified ) {
+						jQuery.etag[ cacheURL ] = modified;
+					}
+				}
+
+				// if no content
+				if ( status === 204 || s.type === "HEAD" ) {
+					statusText = "nocontent";
+
+				// if not modified
+				} else if ( status === 304 ) {
+					statusText = "notmodified";
+
+				// If we have data, let's convert it
+				} else {
+					statusText = response.state;
+					success = response.data;
+					error = response.error;
+					isSuccess = !error;
+				}
+			} else {
+				// We extract error from statusText
+				// then normalize statusText and status for non-aborts
+				error = statusText;
+				if ( status || !statusText ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+					[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger("ajaxStop");
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	}
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		return jQuery.ajax({
+			url: url,
+			type: method,
+			dataType: type,
+			data: data,
+			success: callback
+		});
+	};
+});
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var ct, type, finalDataType, firstDataType,
+		contents = s.contents,
+		dataTypes = s.dataTypes;
+
+	// Remove auto dataType and get content-type in the process
+	while( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+	var conv2, current, conv, tmp, prev,
+		converters = {},
+		// Work with a copy of dataTypes in case we need to modify it for conversion
+		dataTypes = s.dataTypes.slice();
+
+	// Create converters map with lowercased keys
+	if ( dataTypes[ 1 ] ) {
+		for ( conv in s.converters ) {
+			converters[ conv.toLowerCase() ] = s.converters[ conv ];
+		}
+	}
+
+	current = dataTypes.shift();
+
+	// Convert to each sequential dataType
+	while ( current ) {
+
+		if ( s.responseFields[ current ] ) {
+			jqXHR[ s.responseFields[ current ] ] = response;
+		}
+
+		// Apply the dataFilter if provided
+		if ( !prev && isSuccess && s.dataFilter ) {
+			response = s.dataFilter( response, s.dataType );
+		}
+
+		prev = current;
+		current = dataTypes.shift();
+
+		if ( current ) {
+
+		// There's only work to do if current dataType is non-auto
+			if ( current === "*" ) {
+
+				current = prev;
+
+			// Convert response if prev dataType is non-auto and differs from current
+			} else if ( prev !== "*" && prev !== current ) {
+
+				// Seek a direct converter
+				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+				// If none found, seek a pair
+				if ( !conv ) {
+					for ( conv2 in converters ) {
+
+						// If conv2 outputs current
+						tmp = conv2.split( " " );
+						if ( tmp[ 1 ] === current ) {
+
+							// If prev can be converted to accepted input
+							conv = converters[ prev + " " + tmp[ 0 ] ] ||
+								converters[ "* " + tmp[ 0 ] ];
+							if ( conv ) {
+								// Condense equivalence converters
+								if ( conv === true ) {
+									conv = converters[ conv2 ];
+
+								// Otherwise, insert the intermediate dataType
+								} else if ( converters[ conv2 ] !== true ) {
+									current = tmp[ 0 ];
+									dataTypes.unshift( tmp[ 1 ] );
+								}
+								break;
+							}
+						}
+					}
+				}
+
+				// Apply converter (if not an equivalence)
+				if ( conv !== true ) {
+
+					// Unless errors are allowed to bubble, catch and return them
+					if ( conv && s[ "throws" ] ) {
+						response = conv( response );
+					} else {
+						try {
+							response = conv( response );
+						} catch ( e ) {
+							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return { state: "success", data: response };
+}
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /(?:java|ecma)script/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and crossDomain
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+	}
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function( s ) {
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+		var script, callback;
+		return {
+			send: function( _, complete ) {
+				script = jQuery("<script>").prop({
+					async: true,
+					charset: s.scriptCharset,
+					src: s.url
+				}).on(
+					"load error",
+					callback = function( evt ) {
+						script.remove();
+						callback = null;
+						if ( evt ) {
+							complete( evt.type === "error" ? 404 : 200, evt.type );
+						}
+					}
+				);
+				document.head.appendChild( script[ 0 ] );
+			},
+			abort: function() {
+				if ( callback ) {
+					callback();
+				}
+			}
+		};
+	}
+});
+var oldCallbacks = [],
+	rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
+		this[ callback ] = true;
+		return callback;
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var callbackName, overwritten, responseContainer,
+		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+			"url" :
+			typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
+		);
+
+	// Handle iff the expected data type is "jsonp" or we have a parameter to set
+	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+		// Get callback name, remembering preexisting value associated with it
+		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+			s.jsonpCallback() :
+			s.jsonpCallback;
+
+		// Insert callback into url or form data
+		if ( jsonProp ) {
+			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+		} else if ( s.jsonp !== false ) {
+			s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+		}
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( callbackName + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Install callback
+		overwritten = window[ callbackName ];
+		window[ callbackName ] = function() {
+			responseContainer = arguments;
+		};
+
+		// Clean-up function (fires after converters)
+		jqXHR.always(function() {
+			// Restore preexisting value
+			window[ callbackName ] = overwritten;
+
+			// Save back as free
+			if ( s[ callbackName ] ) {
+				// make sure that re-using the options doesn't screw things around
+				s.jsonpCallback = originalSettings.jsonpCallback;
+
+				// save the callback name for future use
+				oldCallbacks.push( callbackName );
+			}
+
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+				overwritten( responseContainer[ 0 ] );
+			}
+
+			responseContainer = overwritten = undefined;
+		});
+
+		// Delegate to script
+		return "script";
+	}
+});
+jQuery.ajaxSettings.xhr = function() {
+	try {
+		return new XMLHttpRequest();
+	} catch( e ) {}
+};
+
+var xhrSupported = jQuery.ajaxSettings.xhr(),
+	xhrSuccessStatus = {
+		// file protocol always yields status code 0, assume 200
+		0: 200,
+		// Support: IE9
+		// #1450: sometimes IE returns 1223 when it should be 204
+		1223: 204
+	},
+	// Support: IE9
+	// We need to keep track of outbound xhr and abort them manually
+	// because IE is not smart enough to do it all by itself
+	xhrId = 0,
+	xhrCallbacks = {};
+
+if ( window.ActiveXObject ) {
+	jQuery( window ).on( "unload", function() {
+		for( var key in xhrCallbacks ) {
+			xhrCallbacks[ key ]();
+		}
+		xhrCallbacks = undefined;
+	});
+}
+
+jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+jQuery.support.ajax = xhrSupported = !!xhrSupported;
+
+jQuery.ajaxTransport(function( options ) {
+	var callback;
+	// Cross domain only allowed if supported through XMLHttpRequest
+	if ( jQuery.support.cors || xhrSupported && !options.crossDomain ) {
+		return {
+			send: function( headers, complete ) {
+				var i, id,
+					xhr = options.xhr();
+				xhr.open( options.type, options.url, options.async, options.username, options.password );
+				// Apply custom fields if provided
+				if ( options.xhrFields ) {
+					for ( i in options.xhrFields ) {
+						xhr[ i ] = options.xhrFields[ i ];
+					}
+				}
+				// Override mime type if needed
+				if ( options.mimeType && xhr.overrideMimeType ) {
+					xhr.overrideMimeType( options.mimeType );
+				}
+				// X-Requested-With header
+				// For cross-domain requests, seeing as conditions for a preflight are
+				// akin to a jigsaw puzzle, we simply never set it to be sure.
+				// (it can always be set on a per-request basis or even using ajaxSetup)
+				// For same-domain requests, won't change header if already provided.
+				if ( !options.crossDomain && !headers["X-Requested-With"] ) {
+					headers["X-Requested-With"] = "XMLHttpRequest";
+				}
+				// Set headers
+				for ( i in headers ) {
+					xhr.setRequestHeader( i, headers[ i ] );
+				}
+				// Callback
+				callback = function( type ) {
+					return function() {
+						if ( callback ) {
+							delete xhrCallbacks[ id ];
+							callback = xhr.onload = xhr.onerror = null;
+							if ( type === "abort" ) {
+								xhr.abort();
+							} else if ( type === "error" ) {
+								complete(
+									// file protocol always yields status 0, assume 404
+									xhr.status || 404,
+									xhr.statusText
+								);
+							} else {
+								complete(
+									xhrSuccessStatus[ xhr.status ] || xhr.status,
+									xhr.statusText,
+									// Support: IE9
+									// #11426: When requesting binary data, IE9 will throw an exception
+									// on any attempt to access responseText
+									typeof xhr.responseText === "string" ? {
+										text: xhr.responseText
+									} : undefined,
+									xhr.getAllResponseHeaders()
+								);
+							}
+						}
+					};
+				};
+				// Listen to events
+				xhr.onload = callback();
+				xhr.onerror = callback("error");
+				// Create the abort callback
+				callback = xhrCallbacks[( id = xhrId++ )] = callback("abort");
+				// Do send the request
+				// This may raise an exception which is actually
+				// handled in jQuery.ajax (so no try/catch here)
+				xhr.send( options.hasContent && options.data || null );
+			},
+			abort: function() {
+				if ( callback ) {
+					callback();
+				}
+			}
+		};
+	}
+});
+var fxNow, timerId,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+	rrun = /queueHooks$/,
+	animationPrefilters = [ defaultPrefilter ],
+	tweeners = {
+		"*": [function( prop, value ) {
+			var tween = this.createTween( prop, value ),
+				target = tween.cur(),
+				parts = rfxnum.exec( value ),
+				unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+				// Starting value computation is required for potential unit mismatches
+				start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
+					rfxnum.exec( jQuery.css( tween.elem, prop ) ),
+				scale = 1,
+				maxIterations = 20;
+
+			if ( start && start[ 3 ] !== unit ) {
+				// Trust units reported by jQuery.css
+				unit = unit || start[ 3 ];
+
+				// Make sure we update the tween properties later on
+				parts = parts || [];
+
+				// Iteratively approximate from a nonzero starting point
+				start = +target || 1;
+
+				do {
+					// If previous iteration zeroed out, double until we get *something*
+					// Use a string for doubling factor so we don't accidentally see scale as unchanged below
+					scale = scale || ".5";
+
+					// Adjust and apply
+					start = start / scale;
+					jQuery.style( tween.elem, prop, start + unit );
+
+				// Update scale, tolerating zero or NaN from tween.cur()
+				// And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+				} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+			}
+
+			// Update tween properties
+			if ( parts ) {
+				start = tween.start = +start || +target || 0;
+				tween.unit = unit;
+				// If a +=/-= token was provided, we're doing a relative animation
+				tween.end = parts[ 1 ] ?
+					start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
+					+parts[ 2 ];
+			}
+
+			return tween;
+		}]
+	};
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	setTimeout(function() {
+		fxNow = undefined;
+	});
+	return ( fxNow = jQuery.now() );
+}
+
+function createTween( value, prop, animation ) {
+	var tween,
+		collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+		index = 0,
+		length = collection.length;
+	for ( ; index < length; index++ ) {
+		if ( (tween = collection[ index ].call( animation, prop, value )) ) {
+
+			// we're done with this property
+			return tween;
+		}
+	}
+}
+
+function Animation( elem, properties, options ) {
+	var result,
+		stopped,
+		index = 0,
+		length = animationPrefilters.length,
+		deferred = jQuery.Deferred().always( function() {
+			// don't match elem in the :animated selector
+			delete tick.elem;
+		}),
+		tick = function() {
+			if ( stopped ) {
+				return false;
+			}
+			var currentTime = fxNow || createFxNow(),
+				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+				// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
+				temp = remaining / animation.duration || 0,
+				percent = 1 - temp,
+				index = 0,
+				length = animation.tweens.length;
+
+			for ( ; index < length ; index++ ) {
+				animation.tweens[ index ].run( percent );
+			}
+
+			deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+			if ( percent < 1 && length ) {
+				return remaining;
+			} else {
+				deferred.resolveWith( elem, [ animation ] );
+				return false;
+			}
+		},
+		animation = deferred.promise({
+			elem: elem,
+			props: jQuery.extend( {}, properties ),
+			opts: jQuery.extend( true, { specialEasing: {} }, options ),
+			originalProperties: properties,
+			originalOptions: options,
+			startTime: fxNow || createFxNow(),
+			duration: options.duration,
+			tweens: [],
+			createTween: function( prop, end ) {
+				var tween = jQuery.Tween( elem, animation.opts, prop, end,
+						animation.opts.specialEasing[ prop ] || animation.opts.easing );
+				animation.tweens.push( tween );
+				return tween;
+			},
+			stop: function( gotoEnd ) {
+				var index = 0,
+					// if we are going to the end, we want to run all the tweens
+					// otherwise we skip this part
+					length = gotoEnd ? animation.tweens.length : 0;
+				if ( stopped ) {
+					return this;
+				}
+				stopped = true;
+				for ( ; index < length ; index++ ) {
+					animation.tweens[ index ].run( 1 );
+				}
+
+				// resolve when we played the last frame
+				// otherwise, reject
+				if ( gotoEnd ) {
+					deferred.resolveWith( elem, [ animation, gotoEnd ] );
+				} else {
+					deferred.rejectWith( elem, [ animation, gotoEnd ] );
+				}
+				return this;
+			}
+		}),
+		props = animation.props;
+
+	propFilter( props, animation.opts.specialEasing );
+
+	for ( ; index < length ; index++ ) {
+		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+		if ( result ) {
+			return result;
+		}
+	}
+
+	jQuery.map( props, createTween, animation );
+
+	if ( jQuery.isFunction( animation.opts.start ) ) {
+		animation.opts.start.call( elem, animation );
+	}
+
+	jQuery.fx.timer(
+		jQuery.extend( tick, {
+			elem: elem,
+			anim: animation,
+			queue: animation.opts.queue
+		})
+	);
+
+	// attach callbacks from options
+	return animation.progress( animation.opts.progress )
+		.done( animation.opts.done, animation.opts.complete )
+		.fail( animation.opts.fail )
+		.always( animation.opts.always );
+}
+
+function propFilter( props, specialEasing ) {
+	var index, name, easing, value, hooks;
+
+	// camelCase, specialEasing and expand cssHook pass
+	for ( index in props ) {
+		name = jQuery.camelCase( index );
+		easing = specialEasing[ name ];
+		value = props[ index ];
+		if ( jQuery.isArray( value ) ) {
+			easing = value[ 1 ];
+			value = props[ index ] = value[ 0 ];
+		}
+
+		if ( index !== name ) {
+			props[ name ] = value;
+			delete props[ index ];
+		}
+
+		hooks = jQuery.cssHooks[ name ];
+		if ( hooks && "expand" in hooks ) {
+			value = hooks.expand( value );
+			delete props[ name ];
+
+			// not quite $.extend, this wont overwrite keys already present.
+			// also - reusing 'index' from above because we have the correct "name"
+			for ( index in value ) {
+				if ( !( index in props ) ) {
+					props[ index ] = value[ index ];
+					specialEasing[ index ] = easing;
+				}
+			}
+		} else {
+			specialEasing[ name ] = easing;
+		}
+	}
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+	tweener: function( props, callback ) {
+		if ( jQuery.isFunction( props ) ) {
+			callback = props;
+			props = [ "*" ];
+		} else {
+			props = props.split(" ");
+		}
+
+		var prop,
+			index = 0,
+			length = props.length;
+
+		for ( ; index < length ; index++ ) {
+			prop = props[ index ];
+			tweeners[ prop ] = tweeners[ prop ] || [];
+			tweeners[ prop ].unshift( callback );
+		}
+	},
+
+	prefilter: function( callback, prepend ) {
+		if ( prepend ) {
+			animationPrefilters.unshift( callback );
+		} else {
+			animationPrefilters.push( callback );
+		}
+	}
+});
+
+function defaultPrefilter( elem, props, opts ) {
+	/* jshint validthis: true */
+	var prop, value, toggle, tween, hooks, oldfire,
+		anim = this,
+		orig = {},
+		style = elem.style,
+		hidden = elem.nodeType && isHidden( elem ),
+		dataShow = data_priv.get( elem, "fxshow" );
+
+	// handle queue: false promises
+	if ( !opts.queue ) {
+		hooks = jQuery._queueHooks( elem, "fx" );
+		if ( hooks.unqueued == null ) {
+			hooks.unqueued = 0;
+			oldfire = hooks.empty.fire;
+			hooks.empty.fire = function() {
+				if ( !hooks.unqueued ) {
+					oldfire();
+				}
+			};
+		}
+		hooks.unqueued++;
+
+		anim.always(function() {
+			// doing this makes sure that the complete handler will be called
+			// before this completes
+			anim.always(function() {
+				hooks.unqueued--;
+				if ( !jQuery.queue( elem, "fx" ).length ) {
+					hooks.empty.fire();
+				}
+			});
+		});
+	}
+
+	// height/width overflow pass
+	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+		// Make sure that nothing sneaks out
+		// Record all 3 overflow attributes because IE9-10 do not
+		// change the overflow attribute when overflowX and
+		// overflowY are set to the same value
+		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+		// Set display property to inline-block for height/width
+		// animations on inline elements that are having width/height animated
+		if ( jQuery.css( elem, "display" ) === "inline" &&
+				jQuery.css( elem, "float" ) === "none" ) {
+
+			style.display = "inline-block";
+		}
+	}
+
+	if ( opts.overflow ) {
+		style.overflow = "hidden";
+		anim.always(function() {
+			style.overflow = opts.overflow[ 0 ];
+			style.overflowX = opts.overflow[ 1 ];
+			style.overflowY = opts.overflow[ 2 ];
+		});
+	}
+
+
+	// show/hide pass
+	for ( prop in props ) {
+		value = props[ prop ];
+		if ( rfxtypes.exec( value ) ) {
+			delete props[ prop ];
+			toggle = toggle || value === "toggle";
+			if ( value === ( hidden ? "hide" : "show" ) ) {
+
+				// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
+				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+					hidden = true;
+				} else {
+					continue;
+				}
+			}
+			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+		}
+	}
+
+	if ( !jQuery.isEmptyObject( orig ) ) {
+		if ( dataShow ) {
+			if ( "hidden" in dataShow ) {
+				hidden = dataShow.hidden;
+			}
+		} else {
+			dataShow = data_priv.access( elem, "fxshow", {} );
+		}
+
+		// store state if its toggle - enables .stop().toggle() to "reverse"
+		if ( toggle ) {
+			dataShow.hidden = !hidden;
+		}
+		if ( hidden ) {
+			jQuery( elem ).show();
+		} else {
+			anim.done(function() {
+				jQuery( elem ).hide();
+			});
+		}
+		anim.done(function() {
+			var prop;
+
+			data_priv.remove( elem, "fxshow" );
+			for ( prop in orig ) {
+				jQuery.style( elem, prop, orig[ prop ] );
+			}
+		});
+		for ( prop in orig ) {
+			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+
+			if ( !( prop in dataShow ) ) {
+				dataShow[ prop ] = tween.start;
+				if ( hidden ) {
+					tween.end = tween.start;
+					tween.start = prop === "width" || prop === "height" ? 1 : 0;
+				}
+			}
+		}
+	}
+}
+
+function Tween( elem, options, prop, end, easing ) {
+	return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+	constructor: Tween,
+	init: function( elem, options, prop, end, easing, unit ) {
+		this.elem = elem;
+		this.prop = prop;
+		this.easing = easing || "swing";
+		this.options = options;
+		this.start = this.now = this.cur();
+		this.end = end;
+		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+	},
+	cur: function() {
+		var hooks = Tween.propHooks[ this.prop ];
+
+		return hooks && hooks.get ?
+			hooks.get( this ) :
+			Tween.propHooks._default.get( this );
+	},
+	run: function( percent ) {
+		var eased,
+			hooks = Tween.propHooks[ this.prop ];
+
+		if ( this.options.duration ) {
+			this.pos = eased = jQuery.easing[ this.easing ](
+				percent, this.options.duration * percent, 0, 1, this.options.duration
+			);
+		} else {
+			this.pos = eased = percent;
+		}
+		this.now = ( this.end - this.start ) * eased + this.start;
+
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		if ( hooks && hooks.set ) {
+			hooks.set( this );
+		} else {
+			Tween.propHooks._default.set( this );
+		}
+		return this;
+	}
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+	_default: {
+		get: function( tween ) {
+			var result;
+
+			if ( tween.elem[ tween.prop ] != null &&
+				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+				return tween.elem[ tween.prop ];
+			}
+
+			// passing an empty string as a 3rd parameter to .css will automatically
+			// attempt a parseFloat and fallback to a string if the parse fails
+			// so, simple values such as "10px" are parsed to Float.
+			// complex values such as "rotate(1rad)" are returned as is.
+			result = jQuery.css( tween.elem, tween.prop, "" );
+			// Empty strings, null, undefined and "auto" are converted to 0.
+			return !result || result === "auto" ? 0 : result;
+		},
+		set: function( tween ) {
+			// use step hook for back compat - use cssHook if its there - use .style if its
+			// available and use plain properties where available
+			if ( jQuery.fx.step[ tween.prop ] ) {
+				jQuery.fx.step[ tween.prop ]( tween );
+			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+			} else {
+				tween.elem[ tween.prop ] = tween.now;
+			}
+		}
+	}
+};
+
+// Support: IE9
+// Panic based approach to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+	set: function( tween ) {
+		if ( tween.elem.nodeType && tween.elem.parentNode ) {
+			tween.elem[ tween.prop ] = tween.now;
+		}
+	}
+};
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+	var cssFn = jQuery.fn[ name ];
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return speed == null || typeof speed === "boolean" ?
+			cssFn.apply( this, arguments ) :
+			this.animate( genFx( name, true ), speed, easing, callback );
+	};
+});
+
+jQuery.fn.extend({
+	fadeTo: function( speed, to, easing, callback ) {
+
+		// show any hidden elements after setting opacity to 0
+		return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+			// animate to the value specified
+			.end().animate({ opacity: to }, speed, easing, callback );
+	},
+	animate: function( prop, speed, easing, callback ) {
+		var empty = jQuery.isEmptyObject( prop ),
+			optall = jQuery.speed( speed, easing, callback ),
+			doAnimation = function() {
+				// Operate on a copy of prop so per-property easing won't be lost
+				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+				// Empty animations, or finishing resolves immediately
+				if ( empty || data_priv.get( this, "finish" ) ) {
+					anim.stop( true );
+				}
+			};
+			doAnimation.finish = doAnimation;
+
+		return empty || optall.queue === false ?
+			this.each( doAnimation ) :
+			this.queue( optall.queue, doAnimation );
+	},
+	stop: function( type, clearQueue, gotoEnd ) {
+		var stopQueue = function( hooks ) {
+			var stop = hooks.stop;
+			delete hooks.stop;
+			stop( gotoEnd );
+		};
+
+		if ( typeof type !== "string" ) {
+			gotoEnd = clearQueue;
+			clearQueue = type;
+			type = undefined;
+		}
+		if ( clearQueue && type !== false ) {
+			this.queue( type || "fx", [] );
+		}
+
+		return this.each(function() {
+			var dequeue = true,
+				index = type != null && type + "queueHooks",
+				timers = jQuery.timers,
+				data = data_priv.get( this );
+
+			if ( index ) {
+				if ( data[ index ] && data[ index ].stop ) {
+					stopQueue( data[ index ] );
+				}
+			} else {
+				for ( index in data ) {
+					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+						stopQueue( data[ index ] );
+					}
+				}
+			}
+
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+					timers[ index ].anim.stop( gotoEnd );
+					dequeue = false;
+					timers.splice( index, 1 );
+				}
+			}
+
+			// start the next in the queue if the last step wasn't forced
+			// timers currently will call their complete callbacks, which will dequeue
+			// but only if they were gotoEnd
+			if ( dequeue || !gotoEnd ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	},
+	finish: function( type ) {
+		if ( type !== false ) {
+			type = type || "fx";
+		}
+		return this.each(function() {
+			var index,
+				data = data_priv.get( this ),
+				queue = data[ type + "queue" ],
+				hooks = data[ type + "queueHooks" ],
+				timers = jQuery.timers,
+				length = queue ? queue.length : 0;
+
+			// enable finishing flag on private data
+			data.finish = true;
+
+			// empty the queue first
+			jQuery.queue( this, type, [] );
+
+			if ( hooks && hooks.stop ) {
+				hooks.stop.call( this, true );
+			}
+
+			// look for any active animations, and finish them
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+					timers[ index ].anim.stop( true );
+					timers.splice( index, 1 );
+				}
+			}
+
+			// look for any animations in the old queue and finish them
+			for ( index = 0; index < length; index++ ) {
+				if ( queue[ index ] && queue[ index ].finish ) {
+					queue[ index ].finish.call( this );
+				}
+			}
+
+			// turn off finishing flag
+			delete data.finish;
+		});
+	}
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+	var which,
+		attrs = { height: type },
+		i = 0;
+
+	// if we include width, step value is 1 to do all cssExpand values,
+	// if we don't include width, step value is 2 to skip over Left and Right
+	includeWidth = includeWidth? 1 : 0;
+	for( ; i < 4 ; i += 2 - includeWidth ) {
+		which = cssExpand[ i ];
+		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+	}
+
+	if ( includeWidth ) {
+		attrs.opacity = attrs.width = type;
+	}
+
+	return attrs;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx("show"),
+	slideUp: genFx("hide"),
+	slideToggle: genFx("toggle"),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+		complete: fn || !fn && easing ||
+			jQuery.isFunction( speed ) && speed,
+		duration: speed,
+		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+	};
+
+	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+	// normalize opt.queue - true/undefined/null -> "fx"
+	if ( opt.queue == null || opt.queue === true ) {
+		opt.queue = "fx";
+	}
+
+	// Queueing
+	opt.old = opt.complete;
+
+	opt.complete = function() {
+		if ( jQuery.isFunction( opt.old ) ) {
+			opt.old.call( this );
+		}
+
+		if ( opt.queue ) {
+			jQuery.dequeue( this, opt.queue );
+		}
+	};
+
+	return opt;
+};
+
+jQuery.easing = {
+	linear: function( p ) {
+		return p;
+	},
+	swing: function( p ) {
+		return 0.5 - Math.cos( p*Math.PI ) / 2;
+	}
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+	var timer,
+		timers = jQuery.timers,
+		i = 0;
+
+	fxNow = jQuery.now();
+
+	for ( ; i < timers.length; i++ ) {
+		timer = timers[ i ];
+		// Checks the timer has not already been removed
+		if ( !timer() && timers[ i ] === timer ) {
+			timers.splice( i--, 1 );
+		}
+	}
+
+	if ( !timers.length ) {
+		jQuery.fx.stop();
+	}
+	fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+	if ( timer() && jQuery.timers.push( timer ) ) {
+		jQuery.fx.start();
+	}
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+	if ( !timerId ) {
+		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+	}
+};
+
+jQuery.fx.stop = function() {
+	clearInterval( timerId );
+	timerId = null;
+};
+
+jQuery.fx.speeds = {
+	slow: 600,
+	fast: 200,
+	// Default speed
+	_default: 400
+};
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.animated = function( elem ) {
+		return jQuery.grep(jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		}).length;
+	};
+}
+jQuery.fn.offset = function( options ) {
+	if ( arguments.length ) {
+		return options === undefined ?
+			this :
+			this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+	}
+
+	var docElem, win,
+		elem = this[ 0 ],
+		box = { top: 0, left: 0 },
+		doc = elem && elem.ownerDocument;
+
+	if ( !doc ) {
+		return;
+	}
+
+	docElem = doc.documentElement;
+
+	// Make sure it's not a disconnected DOM node
+	if ( !jQuery.contains( docElem, elem ) ) {
+		return box;
+	}
+
+	// If we don't have gBCR, just use 0,0 rather than error
+	// BlackBerry 5, iOS 3 (original iPhone)
+	if ( typeof elem.getBoundingClientRect !== core_strundefined ) {
+		box = elem.getBoundingClientRect();
+	}
+	win = getWindow( doc );
+	return {
+		top: box.top + win.pageYOffset - docElem.clientTop,
+		left: box.left + win.pageXOffset - docElem.clientLeft
+	};
+};
+
+jQuery.offset = {
+
+	setOffset: function( elem, options, i ) {
+		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+			position = jQuery.css( elem, "position" ),
+			curElem = jQuery( elem ),
+			props = {};
+
+		// Set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		curOffset = curElem.offset();
+		curCSSTop = jQuery.css( elem, "top" );
+		curCSSLeft = jQuery.css( elem, "left" );
+		calculatePosition = ( position === "absolute" || position === "fixed" ) && ( curCSSTop + curCSSLeft ).indexOf("auto") > -1;
+
+		// Need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+			options = options.call( elem, i, curOffset );
+		}
+
+		if ( options.top != null ) {
+			props.top = ( options.top - curOffset.top ) + curTop;
+		}
+		if ( options.left != null ) {
+			props.left = ( options.left - curOffset.left ) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+
+jQuery.fn.extend({
+
+	position: function() {
+		if ( !this[ 0 ] ) {
+			return;
+		}
+
+		var offsetParent, offset,
+			elem = this[ 0 ],
+			parentOffset = { top: 0, left: 0 };
+
+		// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
+		if ( jQuery.css( elem, "position" ) === "fixed" ) {
+			// We assume that getBoundingClientRect is available when computed position is fixed
+			offset = elem.getBoundingClientRect();
+
+		} else {
+			// Get *real* offsetParent
+			offsetParent = this.offsetParent();
+
+			// Get correct offsets
+			offset = this.offset();
+			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+				parentOffset = offsetParent.offset();
+			}
+
+			// Add offsetParent borders
+			parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+		}
+
+		// Subtract parent offsets and element margins
+		return {
+			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+		};
+	},
+
+	offsetParent: function() {
+		return this.map(function() {
+			var offsetParent = this.offsetParent || docElem;
+
+			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+
+			return offsetParent || docElem;
+		});
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+	var top = "pageYOffset" === prop;
+
+	jQuery.fn[ method ] = function( val ) {
+		return jQuery.access( this, function( elem, method, val ) {
+			var win = getWindow( elem );
+
+			if ( val === undefined ) {
+				return win ? win[ prop ] : elem[ method ];
+			}
+
+			if ( win ) {
+				win.scrollTo(
+					!top ? val : window.pageXOffset,
+					top ? val : window.pageYOffset
+				);
+
+			} else {
+				elem[ method ] = val;
+			}
+		}, method, val, arguments.length, null );
+	};
+});
+
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
+}
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+		// margin is only for outerHeight, outerWidth
+		jQuery.fn[ funcName ] = function( margin, value ) {
+			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+			return jQuery.access( this, function( elem, type, value ) {
+				var doc;
+
+				if ( jQuery.isWindow( elem ) ) {
+					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+					// isn't a whole lot we can do. See pull request at this URL for discussion:
+					// https://github.com/jquery/jquery/pull/764
+					return elem.document.documentElement[ "client" + name ];
+				}
+
+				// Get document width or height
+				if ( elem.nodeType === 9 ) {
+					doc = elem.documentElement;
+
+					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+					// whichever is greatest
+					return Math.max(
+						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+						elem.body[ "offset" + name ], doc[ "offset" + name ],
+						doc[ "client" + name ]
+					);
+				}
+
+				return value === undefined ?
+					// Get width or height on the element, requesting but not forcing parseFloat
+					jQuery.css( elem, type, extra ) :
+
+					// Set width or height on the element
+					jQuery.style( elem, type, value, extra );
+			}, type, chainable ? margin : undefined, chainable, null );
+		};
+	});
+});
+// Limit scope pollution from any deprecated API
+// (function() {
+
+// The number of elements contained in the matched element set
+jQuery.fn.size = function() {
+	return this.length;
+};
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// })();
+if ( typeof module === "object" && module && typeof module.exports === "object" ) {
+	// Expose jQuery as module.exports in loaders that implement the Node
+	// module pattern (including browserify). Do not create the global, since
+	// the user will be storing it themselves locally, and globals are frowned
+	// upon in the Node module world.
+	module.exports = jQuery;
+} else {
+	// Register as a named AMD module, since jQuery can be concatenated with other
+	// files that may use define, but not via a proper concatenation script that
+	// understands anonymous AMD modules. A named AMD is safest and most robust
+	// way to register. Lowercase jquery is used because AMD module names are
+	// derived from file names, and jQuery is normally delivered in a lowercase
+	// file name. Do this after creating the global so that if an AMD module wants
+	// to call noConflict to hide this version of jQuery, it will work.
+	if ( typeof define === "function" && define.amd ) {
+		define( "jquery", [], function () { return jQuery; } );
+	}
+}
+
+// If there is a window object, that at least has a document property,
+// define jQuery and $ identifiers
+if ( typeof window === "object" && typeof window.document === "object" ) {
+	window.jQuery = window.$ = jQuery;
+}
+
+})( window );
-- 
1.8.1.2



^ permalink raw reply related	[flat|nested] 96+ messages in thread

* Re: [PATCH 00/94] Webhob patches for DSI and Web modules
  2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
                   ` (93 preceding siblings ...)
  2013-09-24 16:53 ` [PATCH 61/94] bitbake: bldviewer: add jquery and bootstrap frameworks Alex DAMIAN
@ 2013-09-24 17:14 ` Paul Eggleton
  94 siblings, 0 replies; 96+ messages in thread
From: Paul Eggleton @ 2013-09-24 17:14 UTC (permalink / raw)
  To: Alex DAMIAN; +Cc: bitbake-devel

Hi Alex,

On Tuesday 24 September 2013 17:52:50 Alex DAMIAN wrote:
> From: Alexandru DAMIAN <alexandru.damian@intel.com>
> 
> Patches for the DSI and Web modules of Webhob, in the order of development.

I hate to be a pain, but apart from the commits that change existing modules 
e.g. cooker, all of these commits need to be squashed into one commit that 
simply adds Web Hob to bitbake - we don't need the full incremental 
development history. 

(Of course we do want to preserve the list of bug IDs / contributors from the 
commit messages, though.)

Thanks,
Paul

-- 

Paul Eggleton
Intel Open Source Technology Centre


^ permalink raw reply	[flat|nested] 96+ messages in thread

end of thread, other threads:[~2013-09-24 17:14 UTC | newest]

Thread overview: 96+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-09-24 16:52 [PATCH 00/94] Webhob patches for DSI and Web modules Alex DAMIAN
2013-09-24 16:51 ` [PATCH 01/94] webhob: create main WEBHOB project Alex DAMIAN
2013-09-24 16:51 ` [PATCH 02/94] webhob: add system start/stop script Alex DAMIAN
2013-09-24 16:51 ` [PATCH 03/94] webhob: create Django models for webhob Alex DAMIAN
2013-09-24 16:51 ` [PATCH 04/94] bitbake: create Data Store Interface (DSI) file Alex DAMIAN
2013-09-24 16:51 ` [PATCH 05/94] bitbake: webhob: updates to Django models Alex DAMIAN
2013-09-24 16:51 ` [PATCH 06/94] bitbake: webhob: make DSI store build information Alex DAMIAN
2013-09-24 16:51 ` [PATCH 07/94] bitbake: webhob: make DSI store task information Alex DAMIAN
2013-09-24 16:51 ` [PATCH 08/94] bitbake: webhob: view a table with all builds Alex DAMIAN
2013-09-24 16:51 ` [PATCH 09/94] bitbake: webhob: view a table with all the tasks of a build Alex DAMIAN
2013-09-24 16:51 ` [PATCH 10/94] bitbake: webhob: use buildcompleted event as the end of a build operation Alex DAMIAN
2013-09-24 16:51 ` [PATCH 11/94] bitbake: webhob: changes to build information table Alex DAMIAN
2013-09-24 16:51 ` [PATCH 12/94] bitbake: webhob: gather buildstats for each executed task Alex DAMIAN
2013-09-24 16:51 ` [PATCH 13/94] bitbake: webhob: set sane default settings Alex DAMIAN
2013-09-24 16:51 ` [PATCH 14/94] bitbake: webhob: adds dsi support for observer mode Alex DAMIAN
2013-09-24 16:51 ` [PATCH 15/94] bitbake: dsi: Translate runQueue events into Task data Alex DAMIAN
2013-09-24 16:51 ` [PATCH 16/94] bitbake: webhob: disable synchronous sqlite connection Alex DAMIAN
2013-09-24 16:51 ` [PATCH 17/94] bitbake: buildinfohelper: clean-up the imports Alex DAMIAN
2013-09-24 16:51 ` [PATCH 18/94] bitbake: buildinfohelper: clearing up task order Alex DAMIAN
2013-09-24 16:51 ` [PATCH 19/94] bitbake: dsi: saving prebuild task detailed information Alex DAMIAN
2013-09-24 16:51 ` [PATCH 20/94] bitbake: dsi: clear up the state on build completion Alex DAMIAN
2013-09-24 16:51 ` [PATCH 21/94] bitbake: webhob: add layer information to the database Alex DAMIAN
2013-09-24 16:51 ` [PATCH 22/94] bitbake: webhob: mark private methods inside DSI Alex DAMIAN
2013-09-24 16:51 ` [PATCH 23/94] bitbake: webhob: create basic structure for static files Alex DAMIAN
2013-09-24 16:51 ` [PATCH 24/94] bitbake: dsi: event data change Alex DAMIAN
2013-09-24 16:51 ` [PATCH 25/94] bitbake: webhob: fix and cleanup start script Alex DAMIAN
2013-09-24 16:51 ` [PATCH 26/94] bitbake: dsi: refactor the BuildInfoHelper code Alex DAMIAN
2013-09-24 16:51 ` [PATCH 27/94] bitbake: webhob: record dependency info Alex DAMIAN
2013-09-24 16:51 ` [PATCH 28/94] bitbake: webhob: use defined constants for models Alex DAMIAN
2013-09-24 16:51 ` [PATCH 29/94] bitbake: dsi: fix the reading of task event information Alex DAMIAN
2013-09-24 16:51 ` [PATCH 30/94] bitbake: webhob: Setup API for build model Alex DAMIAN
2013-09-24 16:52 ` [PATCH 31/94] bitbake: webhob: orm change to remove Target Alex DAMIAN
2013-09-24 16:52 ` [PATCH 32/94] bitbake: webhob: clear up ORM relations Alex DAMIAN
2013-09-24 16:52 ` [PATCH 33/94] bitbake: dsi: save detailed recipe information Alex DAMIAN
2013-09-24 16:52 ` [PATCH 34/94] bitbake: webhob: determine if a build is an image Alex DAMIAN
2013-09-24 16:52 ` [PATCH 35/94] bitbake: webhob: clean up URL structure Alex DAMIAN
2013-09-24 16:52 ` [PATCH 36/94] bitbake: webhob: add simple pages for Recipes Alex DAMIAN
2013-09-24 16:52 ` [PATCH 37/94] bitbake: webhob: add ordering capabilities to build api Alex DAMIAN
2013-09-24 16:52 ` [PATCH 38/94] bitbake: webhob: validate inputs for " Alex DAMIAN
2013-09-24 16:52 ` [PATCH 39/94] bitbake: webhob: generic view for multiple models Alex DAMIAN
2013-09-24 16:52 ` [PATCH 40/94] bitbake: webhob: enhance Simple browser navigation Alex DAMIAN
2013-09-24 16:52 ` [PATCH 41/94] bitbake: webhob: improve startup script Alex DAMIAN
2013-09-24 16:52 ` [PATCH 42/94] bitbake: webhob: improve validation code flow Alex DAMIAN
2013-09-24 16:52 ` [PATCH 43/94] bitbake: webhob: add more models to webhob API Alex DAMIAN
2013-09-24 16:52 ` [PATCH 44/94] bitbake: webhob: add search for build model Alex DAMIAN
2013-09-24 16:52 ` [PATCH 45/94] bitbake: webhob: fix ordering issue Alex DAMIAN
2013-09-24 16:52 ` [PATCH 46/94] bitbake: webhob: extend search for multiple terms Alex DAMIAN
2013-09-24 16:52 ` [PATCH 47/94] bitbake: webhob: force bitbake server stop Alex DAMIAN
2013-09-24 16:52 ` [PATCH 48/94] bitbake: webhob: simple interface dependency list Alex DAMIAN
2013-09-24 16:52 ` [PATCH 49/94] bitbake: dsi: add feature to store package information Alex DAMIAN
2013-09-24 16:52 ` [PATCH 50/94] bitbake: webhob: add simple viewer for " Alex DAMIAN
2013-09-24 16:52 ` [PATCH 51/94] bitbake: dsi: fix build stats data gathering Alex DAMIAN
2013-09-24 16:52 ` [PATCH 52/94] bitbake: dsi: update build object on command end Alex DAMIAN
2013-09-24 16:52 ` [PATCH 53/94] bitbake: dsi: fix sstate task information gathering Alex DAMIAN
2013-09-24 16:52 ` [PATCH 54/94] bitbake: webhob: clean up starting script Alex DAMIAN
2013-09-24 16:52 ` [PATCH 55/94] bitbake: dsi: store log information Alex DAMIAN
2013-09-24 16:52 ` [PATCH 56/94] bitbake: webhob: add simple visualisation for build errors Alex DAMIAN
2013-09-24 16:52 ` [PATCH 57/94] bitbake: webhob: store logfile and message for Tasks Alex DAMIAN
2013-09-24 16:52 ` [PATCH 58/94] bitbake: webhob: display proper information in Simple Alex DAMIAN
2013-09-24 16:52 ` [PATCH 59/94] bitbake: dsi: clear up global variables between builds Alex DAMIAN
2013-09-24 16:52 ` [PATCH 60/94] bitbake: dsi: small bugfixes in data collection Alex DAMIAN
2013-09-24 16:52 ` [PATCH 62/94] bitbake: webhob: add toggle column functionality to build page Alex DAMIAN
2013-09-24 16:52 ` [PATCH 63/94] bitbake: webhob: add search functionality to the Simple interface Alex DAMIAN
2013-09-24 16:52 ` [PATCH 64/94] bitbake: webhob: refactor column hiding code Alex DAMIAN
2013-09-24 16:52 ` [PATCH 65/94] bitbake: webhob: improve search functionality Alex DAMIAN
2013-09-24 16:52 ` [PATCH 66/94] bitbake: webhob: refactor Simple web interface Alex DAMIAN
2013-09-24 16:52 ` [PATCH 67/94] bitbake: webhob: simple interface CSS Alex DAMIAN
2013-09-24 16:52 ` [PATCH 68/94] bitbake: dsi: use get vars command to store configuration Alex DAMIAN
2013-09-24 16:52 ` [PATCH 69/94] bitbake: webhob: refactor CSS display in Simple Alex DAMIAN
2013-09-24 16:52 ` [PATCH 70/94] bitbake: webhob: add Configuration visualisation " Alex DAMIAN
2013-09-24 16:52 ` [PATCH 71/94] bitbake: webhob: add navigation links Simple interface Alex DAMIAN
2013-09-24 16:52 ` [PATCH 72/94] bitbake: webhob: startup script fixing Alex DAMIAN
2013-09-24 16:52 ` [PATCH 73/94] bitbake: webhob: change database models and related Alex DAMIAN
2013-09-24 16:52 ` [PATCH 74/94] bitbake: webhob: navigation in the Simple interface Alex DAMIAN
2013-09-24 16:52 ` [PATCH 75/94] bitbake: dsi: save build-time package information Alex DAMIAN
2013-09-24 16:52 ` [PATCH 76/94] bitbake: webhob: Simple visualisation for the build-time packages Alex DAMIAN
2013-09-24 16:52 ` [PATCH 77/94] bitbake: webhob: store file size information Alex DAMIAN
2013-09-24 16:52 ` [PATCH 78/94] bitbake: webhob: simple visualisation for package files Alex DAMIAN
2013-09-24 16:52 ` [PATCH 79/94] bitbake: dsi: record recipe dependency Alex DAMIAN
2013-09-24 16:52 ` [PATCH 80/94] bitbake: dsi: add exception dumping code Alex DAMIAN
2013-09-24 16:52 ` [PATCH 81/94] bitbake: webhob: better help message in startup script Alex DAMIAN
2013-09-24 16:52 ` [PATCH 82/94] bitbake: dsi: add build package dependency recording Alex DAMIAN
2013-09-24 16:52 ` [PATCH 83/94] bitbake: webhob: clean up Machine table Alex DAMIAN
2013-09-24 16:52 ` [PATCH 84/94] bitbake: webhob: update API endpoints Alex DAMIAN
2013-09-24 16:52 ` [PATCH 85/94] bitbake: webhob: record task hash information Alex DAMIAN
2013-09-24 16:52 ` [PATCH 86/94] bitbake: cooker: add the inherits attribute to the dependency tree Alex DAMIAN
2013-09-24 16:52 ` [PATCH 87/94] bitbake: webhob: Clean up links in Simple UI Alex DAMIAN
2013-09-24 16:52 ` [PATCH 88/94] bitbake: dsi: read correct recipe description field Alex DAMIAN
2013-09-24 16:52 ` [PATCH 89/94] bitbake: cooker: send layer information with dependency tree Alex DAMIAN
2013-09-24 16:52 ` [PATCH 90/94] bitbake: webhob: store and display layer priorities Alex DAMIAN
2013-09-24 16:53 ` [PATCH 91/94] bitbake: webhob: collect recipe licensing info Alex DAMIAN
2013-09-24 16:53 ` [PATCH 92/94] bitbake: webhob: Simple UI interface CSS fix Alex DAMIAN
2013-09-24 16:53 ` [PATCH 93/94] bitbake: dsi: fix typos Alex DAMIAN
2013-09-24 16:53 ` [PATCH 94/94] bitbake: webhob: do not set timezones Alex DAMIAN
2013-09-24 16:53 ` [PATCH 61/94] bitbake: bldviewer: add jquery and bootstrap frameworks Alex DAMIAN
2013-09-24 17:14 ` [PATCH 00/94] Webhob patches for DSI and Web modules Paul Eggleton

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.