diff options
author | Alexandru DAMIAN <alexandru.damian@intel.com> | 2013-10-11 13:46:23 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2013-10-18 11:13:13 +0100 |
commit | d24334a5e83d09b3ab227af485971bb768bf5412 (patch) | |
tree | 2c7682da9abed81fd2478c869e0324ab051679d9 /lib/toaster/bldviewer | |
parent | 8a8bafc8ded98364a31878b23c64503a53affcd1 (diff) | |
download | bitbake-contrib-d24334a5e83d09b3ab227af485971bb768bf5412.tar.gz |
toaster: add toaster code to bitbake
This patch adds the Toaster component to Bitbake.
Toaster is a module designed to record the progress of a
Bitbake build, and data about the resultant artifacts.
It contains a web-based interface and a REST API allowing
post-facto inspection of the build process and artifacts.
Features present in this build:
* toaster start script
* relational data model
* Django boilerplate code
* the REST API
* the Simple UI web interface
This patch has all the development history squashed together.
Code portions contributed by Calin Dragomir <calindragomir@gmail.com>.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Diffstat (limited to 'lib/toaster/bldviewer')
-rw-r--r-- | lib/toaster/bldviewer/__init__.py | 0 | ||||
-rw-r--r-- | lib/toaster/bldviewer/api.py | 37 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/base.html | 30 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/basebuildpage.html | 17 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/basetable.html | 46 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/bfile.html | 24 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/bpackage.html | 44 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/build.html | 43 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/configuration.html | 20 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/layer.html | 34 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/package.html | 36 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/recipe.html | 54 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templates/task.html | 63 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templatetags/__init__.py | 0 | ||||
-rw-r--r-- | lib/toaster/bldviewer/templatetags/projecttags.py | 26 | ||||
-rw-r--r-- | lib/toaster/bldviewer/urls.py | 32 | ||||
-rw-r--r-- | lib/toaster/bldviewer/views.py | 260 |
17 files changed, 766 insertions, 0 deletions
diff --git a/lib/toaster/bldviewer/__init__.py b/lib/toaster/bldviewer/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/lib/toaster/bldviewer/__init__.py diff --git a/lib/toaster/bldviewer/api.py b/lib/toaster/bldviewer/api.py new file mode 100644 index 000000000..f761ba65a --- /dev/null +++ b/lib/toaster/bldviewer/api.py @@ -0,0 +1,37 @@ +# +# BitBake Toaster 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 django.conf.urls import patterns, include, url + + +urlpatterns = patterns('bldviewer.views', + 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/lib/toaster/bldviewer/templates/base.html b/lib/toaster/bldviewer/templates/base.html new file mode 100644 index 000000000..101880d3e --- /dev/null +++ b/lib/toaster/bldviewer/templates/base.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +{% load static %} +<html> + <head> + <title>Toaster 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 style="height: 100%"> +<div style="width:100%; height: 100%; position:absolute"> +<div style="width: 100%; height: 3em" class="nav"> + <ul class="nav nav-tabs"> + <li><a href="{% url all-builds %}">All Builds</a></li> + <li><a href="{% url all-layers %}">All Layers</a></li> + </ul> +</div> + +<div style="overflow-y:scroll; width: 100%; position: absolute; top: 3em; bottom:70px "> +{% block pagecontent %} +{% endblock %} +</div> +<div class="navbar" style="position: absolute; bottom: 0; width:100%"><br/>About Toaster | Yocto Project </div> +</div> +</body> +</html> + diff --git a/lib/toaster/bldviewer/templates/basebuildpage.html b/lib/toaster/bldviewer/templates/basebuildpage.html new file mode 100644 index 000000000..873f271ba --- /dev/null +++ b/lib/toaster/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:" "}} at {{build.started_on}} : </a></li> + <li><a href="{% url task build.id %}"> Tasks </a></li> + <li><a href="{% url bpackage build.id %}"> Build Packages </a></li> + {% for t in build.target_set.all %} + {% if t.is_image %} + <li><a href="{% url tpackage build.id t.pk %}"> Packages for {{t.target}} </a> </li> + {% endif %} + {% endfor %} + <li><a href="{% url configuration build.id %}"> Configuration </a> </li> +</ul> + <h1>Toaster - Build {% block pagetitle %} {% endblock %}</h1> +{% endblock %} + diff --git a/lib/toaster/bldviewer/templates/basetable.html b/lib/toaster/bldviewer/templates/basetable.html new file mode 100644 index 000000000..083bcb82e --- /dev/null +++ b/lib/toaster/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/lib/toaster/bldviewer/templates/bfile.html b/lib/toaster/bldviewer/templates/bfile.html new file mode 100644 index 000000000..d90f4fbdd --- /dev/null +++ b/lib/toaster/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 (Bytes)</th> + </tr> + + {% for file in files %} + + <tr class="data"> + <td>{{file.path}}</td> + <td>{{file.size}}</td> + + {% endfor %} + + {% endif %} + +{% endblock %} diff --git a/lib/toaster/bldviewer/templates/bpackage.html b/lib/toaster/bldviewer/templates/bpackage.html new file mode 100644 index 000000000..2e254dbad --- /dev/null +++ b/lib/toaster/bldviewer/templates/bpackage.html @@ -0,0 +1,44 @@ +{% extends "basebuildpage.html" %} + +{% block pagetitle %}Packages{% endblock %} +{% block pagetable %} + {% if not packages %} + <p>No packages were recorded for this target!</p> + {% else %} + + <tr> + <th>Name</th> + <th>Version</th> + <th>Recipe</th> + <th>Summary</th> + <th>Section</th> + <th>Description</th> + <th>Size on host disk (KBytes)</th> + <th>License</th> + <th>Dependencies List (all)</th> + </tr> + + {% for package in packages %} + + <tr class="data"> + <td><a name="#{{package.name}}" href="{% url bfile build.pk package.pk %}">{{package.name}} ({{package.filelist_bpackage.count}} files)</a></td> + <td>{{package.version}}-{{package.revision}}</td> + <td><a href="{% url layer_versions_recipes package.recipe.layer_version_id %}#{{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; overflow:auto"> + {% for bpd in package.bpackage_dependencies_package.all %} + {{bpd.dep_type}}: {{bpd.depends_on}} <br/> + {% endfor %} + </div> + </td> + {% endfor %} + + {% endif %} + +{% endblock %} diff --git a/lib/toaster/bldviewer/templates/build.html b/lib/toaster/bldviewer/templates/build.html new file mode 100644 index 000000000..ab6e19643 --- /dev/null +++ b/lib/toaster/bldviewer/templates/build.html @@ -0,0 +1,43 @@ +{% extends "basetable.html" %} + +{% block pagename %} + <h1>Toaster - Builds</h1> +{% endblock %} + +{% block pagetable %} + + {% 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>Output</th> + <th>Log</th> + <th>Bitbake Version</th> + <th>Build Name</th> + </tr> + {% for build in builds %} + <tr class="data"> + <td><a href="{% url configuration build.id %}">{{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="{% url tpackage build.id t.id %}">{{t.target}}</a>{% if t.is_image %} (Img){% endif %}<br/>{% endfor %}</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> + <td>{% if build.outcome == 0 %}{% for t in build.target_set.all %}{% if t.is_image %}{{build.image_fstypes}}{% endif %}{% endfor %}{% endif %}</td> + <td>{{build.cooker_log_path}}</td> + <td>{{build.bitbake_version}}</td> + <td>{{build.build_name}}</td> + </tr> + + {% endfor %} +{% endblock %} + + diff --git a/lib/toaster/bldviewer/templates/configuration.html b/lib/toaster/bldviewer/templates/configuration.html new file mode 100644 index 000000000..052c37c4e --- /dev/null +++ b/lib/toaster/bldviewer/templates/configuration.html @@ -0,0 +1,20 @@ +{% extends "basebuildpage.html" %} + +{% block pagetitle %}Configuration{% 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/lib/toaster/bldviewer/templates/layer.html b/lib/toaster/bldviewer/templates/layer.html new file mode 100644 index 000000000..fa4fd9bde --- /dev/null +++ b/lib/toaster/bldviewer/templates/layer.html @@ -0,0 +1,34 @@ +{% extends "basetable.html" %} + +{% block pagename %} + <h1>Toaster - Layers</h1> +{% endblock %} + +{% block pagetable %} + {% load projecttags %} + + <tr> + <th>Name</th> + <th>Local Path</th> + <th>Layer Index URL</th> + <th>Known Versions</th> + </tr> + + {% for layer in layers %} + + <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> + <td><table> + {% for lv in layer.versions %} + <tr><td> + <a href="{% url layer_versions_recipes lv.id %}">({{lv.priority}}){{lv.branch}}:{{lv.commit}} ({{lv.count}} recipes)</a> + </td></tr> + {% endfor %} + </table></td> + </tr> + + {% endfor %} + +{% endblock %} diff --git a/lib/toaster/bldviewer/templates/package.html b/lib/toaster/bldviewer/templates/package.html new file mode 100644 index 000000000..642fcab9a --- /dev/null +++ b/lib/toaster/bldviewer/templates/package.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>Size (Bytes)</th> + <th>Recipe</th> + <th>Depends on</th> + </tr> + + {% for package in packages %} + + <tr class="data"> + <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="{% url layer_versions_recipes package.recipe.layer_version_id %}#{{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 %} + + {% endif %} + +{% endblock %} diff --git a/lib/toaster/bldviewer/templates/recipe.html b/lib/toaster/bldviewer/templates/recipe.html new file mode 100644 index 000000000..a62437066 --- /dev/null +++ b/lib/toaster/bldviewer/templates/recipe.html @@ -0,0 +1,54 @@ +{% extends "basetable.html" %} + +{% block pagename %} +<ul class="nav nav-tabs" style="display: inline-block"> + <li><a>Layer {{layer_version.layer.name}} : {{layer_version.branch}} : {{layer_version.commit}} : {{layer_version.priority}}</a></li> +</ul> + <h1>Toaster - Recipes for a Layer</h1> +{% endblock %} + +{% block pagetable %} + {% load projecttags %} + + <tr> + </tr> + <th>Name</th> + <th>Version</th> + <th>Summary</th> + <th>Description</th> + <th>Section</th> + <th>License</th> + <th>License file</th> + <th>Homepage</th> + <th>Bugtracker</th> + <th>Author</th> + <th>File_path</th> + <th style="width: 30em">Recipe Dependency</th> + + + {% for recipe in recipes %} + + <tr class="data"> + <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.licensing_info}}</td> + <td>{{recipe.homepage}}</td> + <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 %} + +{% endblock %} diff --git a/lib/toaster/bldviewer/templates/task.html b/lib/toaster/bldviewer/templates/task.html new file mode 100644 index 000000000..e7253698c --- /dev/null +++ b/lib/toaster/bldviewer/templates/task.html @@ -0,0 +1,63 @@ +{% extends "basebuildpage.html" %} + +{% block pagetitle %}Tasks{% endblock %} +{% block pagetable %} + {% if not tasks %} + <p>No tasks were executed in this build!</p> + {% else %} + + <tr> + <th>Order</th> + <th>Task</th> + <th>Recipe Version</th> + <th>Task Type</th> + <th>Checksum</th> + <th>Outcome</th> + <th>Message</th> + <th>Logfile</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 class="data"> + <td>{{task.order}}</td> + <td><a name="{{task.recipe.name}}.{{task.task_name}}"> + <a href="{% url layer_versions_recipes task.recipe.layer_version_id %}#{{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.sstate_checksum}}</td> + <td>{{task.get_outcome_display}}{% if task.provider %}</br>(by <a href="#{{task.provider.recipe.name}}.{{task.provider.task_name}}">{{task.provider.recipe.name}}.{{task.provider.task_name}}</a>){% endif %}</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><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.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> + </tr> + + {% endfor %} + + {% endif %} + +{% endblock %} diff --git a/lib/toaster/bldviewer/templatetags/__init__.py b/lib/toaster/bldviewer/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/lib/toaster/bldviewer/templatetags/__init__.py diff --git a/lib/toaster/bldviewer/templatetags/projecttags.py b/lib/toaster/bldviewer/templatetags/projecttags.py new file mode 100644 index 000000000..0c0d804c0 --- /dev/null +++ b/lib/toaster/bldviewer/templatetags/projecttags.py @@ -0,0 +1,26 @@ +# +# BitBake Toaster 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 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/lib/toaster/bldviewer/urls.py b/lib/toaster/bldviewer/urls.py new file mode 100644 index 000000000..becc67920 --- /dev/null +++ b/lib/toaster/bldviewer/urls.py @@ -0,0 +1,32 @@ +# +# BitBake Toaster 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 django.conf.urls import patterns, include, url +from django.views.generic.simple import redirect_to + +urlpatterns = patterns('bldviewer.views', + url(r'^builds/$', 'build', name='all-builds'), + 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='all-layers'), + url(r'^layerversions/(?P<layerversion_id>\d+)/recipes/.*$', 'layer_versions_recipes', name='layer_versions_recipes'), + url(r'^$', redirect_to, {'url': 'builds/'}), +) diff --git a/lib/toaster/bldviewer/views.py b/lib/toaster/bldviewer/views.py new file mode 100644 index 000000000..7be4d4b89 --- /dev/null +++ b/lib/toaster/bldviewer/views.py @@ -0,0 +1,260 @@ +# +# BitBake Toaster 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. + +import operator + +from django.db.models import Q +from django.shortcuts import render +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) +def build(request): + template = 'build.html' + build_info = Build.objects.all() + + logs = LogMessage.objects.all() + + context = {'builds': build_info, 'logs': logs , + 'hideshowcols' : [ + {'name': 'Output', 'order':10}, + {'name': 'Log', 'order':11}, + ]} + + return render(request, template, context) + + +def _find_task_revdep(task): + tp = [] + for p in Task_Dependency.objects.filter(depends_on=task): + tp.append(p.task); + return tp + +def _find_task_provider(task): + task_revdeps = _find_task_revdep(task) + for tr in task_revdeps: + if tr.outcome != Task.OUTCOME_COVERED: + return tr + for tr in task_revdeps: + trc = _find_task_provider(tr) + if trc is not None: + return trc + return None + +def task(request, build_id): + template = 'task.html' + + tasks = Task.objects.filter(build=build_id) + + for t in tasks: + if t.outcome == Task.OUTCOME_COVERED: + t.provider = _find_task_provider(t) + + 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.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' + + packages = Target_Package.objects.filter(target=target_id) + + context = {'build' : Build.objects.filter(pk=build_id)[0],'packages': packages} + + return render(request, template, context) + +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, + 'layer_version' : Layer_Version.objects.filter( id = layerversion_id )[0] + } + + return render(request, template, context) + +#### API + +import json +from django.core import serializers +from django.http import HttpResponse, HttpResponseBadRequest + + +def model_explorer(request, model_name): + + DESCENDING = 'desc' + response_data = {} + model_mapping = { + 'build': Build, + 'target': Target, + 'target_package': Target_Package, + 'task': Task, + '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(): + return HttpResponseBadRequest() + + model = model_mapping[model_name] + + try: + limit = int(request.GET.get('limit', 0)) + except ValueError: + limit = 0 + + try: + offset = int(request.GET.get('offset', 0)) + except ValueError: + offset = 0 + + ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), + model) + if invalid: + return HttpResponseBadRequest() + + filter_string, invalid = _validate_input(request.GET.get('filter', ''), + model) + if invalid: + return HttpResponseBadRequest() + + search_term = request.GET.get('search', '') + + if filter_string: + filter_terms = _get_filtering_terms(filter_string) + try: + queryset = model.objects.filter(**filter_terms) + except ValueError: + queryset = [] + else: + queryset = model.objects.all() + + 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: + 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)) + +def _validate_input(input, model): + + invalid = 0 + + if input: + input_list = input.split(":") + + # Check we have only one colon + 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 + +def _get_search_results(search_term, queryset, model): + search_objects = [] + for st in search_term.split(" "): + q_map = map(lambda x: Q(**{x+'__icontains': st}), + model.search_allowed_fields) + + search_objects.append(reduce(operator.or_, q_map)) + search_object = reduce(operator.and_, search_objects) + queryset = queryset.filter(search_object) + + return queryset |