summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexandru DAMIAN <alexandru.damian@intel.com>2013-12-11 16:42:34 +0000
committerPaul Eggleton <paul.eggleton@linux.intel.com>2014-01-10 14:13:47 +0000
commitd31f039ae31b77023722c06e66542751536a1362 (patch)
tree6a549f7ebd5500368115b3a5b774553503f5e939
parent0b3d2000c0bcd2bb5a3af5d49a514a90f001a7d2 (diff)
downloadbitbake-d31f039ae31b77023722c06e66542751536a1362.tar.gz
toaster: Create the base page navigation structure
Updating the general container pages to use the graphical design and features from the design phase. In the process of adapting the Simple UI to the designed interface, we create all the pages and the navigation structure for the Toaster GUI. Views for each page have been added, and the url mapping has been updated to reflect newly added pages. The table page has been refactored to be component-oriented instead of class-oriented in order to facilitate reusage. Changes are made in different layers of the template (base, basetable) in order to maximize code reuse among different pages in the build. Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
-rw-r--r--lib/toaster/toastergui/templates/base.html64
-rw-r--r--lib/toaster/toastergui/templates/basebuildpage.html69
-rw-r--r--lib/toaster/toastergui/templates/basetable.html64
-rw-r--r--lib/toaster/toastergui/templates/basetable_bottom.html60
-rw-r--r--lib/toaster/toastergui/templates/basetable_top.html66
-rw-r--r--lib/toaster/toastergui/templates/bpackage.html12
-rw-r--r--lib/toaster/toastergui/templates/build.html115
-rw-r--r--lib/toaster/toastergui/templates/builddashboard.html8
-rw-r--r--lib/toaster/toastergui/templates/buildtime.html4
-rw-r--r--lib/toaster/toastergui/templates/configuration.html10
-rw-r--r--lib/toaster/toastergui/templates/cpuusage.html4
-rw-r--r--lib/toaster/toastergui/templates/diskio.html4
-rw-r--r--lib/toaster/toastergui/templates/recipe.html15
-rw-r--r--lib/toaster/toastergui/templates/target.html8
-rw-r--r--lib/toaster/toastergui/templates/task.html11
-rw-r--r--lib/toaster/toastergui/templatetags/projecttags.py5
-rw-r--r--lib/toaster/toastergui/urls.py34
-rw-r--r--lib/toaster/toastergui/views.py167
-rw-r--r--lib/toaster/toastermain/settings.py10
19 files changed, 571 insertions, 159 deletions
diff --git a/lib/toaster/toastergui/templates/base.html b/lib/toaster/toastergui/templates/base.html
index d58cbeaed..3508962e6 100644
--- a/lib/toaster/toastergui/templates/base.html
+++ b/lib/toaster/toastergui/templates/base.html
@@ -1,30 +1,64 @@
<!DOCTYPE html>
{% load static %}
<html>
- <head>
- <title>Toaster Simple Explorer</title>
-<script src="{% static 'js/jquery-2.0.3.js' %}">
+ <head>
+ <title>Toaster</title>
+<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css">
+<link rel="stylesheet" href="{% static 'css/bootstrap-responsive.min.css' %}" type='text/css'>
+<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'>
+<link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'>
+<link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'>
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<script src="{% static 'js/jquery-2.0.3.min.js' %}">
</script>
-<script src="{% static 'js/bootstrap.js' %}">
+<script src="{% static 'js/bootstrap.min.js' %}">
</script>
-<link href="{% static 'css/bootstrap.css' %}" rel="stylesheet" type="text/css">
- </head>
+<script src="{% static 'js/prettify.js' %}">
+</script>
+<script src="{% static 'js/main.js' %}">
+</script>
+<script>
+function reload_params(params) {
+ uri = window.location.href;
+ [url, parameters] = uri.split("?");
+ // deserialize the call parameters
+ cparams = parameters.split("&");
+ nparams = {}
+ for (i = 0; i < cparams.length; i++) {
+ temp = cparams[i].split("=");
+ nparams[temp[0]] = temp[1];
+ }
+ // update parameter values
+ for (i in params) {
+ nparams[encodeURIComponent(i)] = encodeURIComponent(params[i]);
+ }
+ // serialize the structure
+ callparams = []
+ for (i in nparams) {
+ callparams.push(i+"="+nparams[i]);
+ }
+ window.location.href = url+"?"+callparams.join('&');
+
+}
+</script>
+ </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 class="navbar navbar-static-top">
+ <div class="navbar-inner">
+ <a class="brand logo" href="#"><img src="{% static 'img/logo.png' %}" class="" alt="Yocto logo project"/></a>
+ <a class="brand" href="/">Toaster</a>
+ <a class="pull-right manual" href="#">
+ <i class="icon-book"></i>
+ Toaster manual
+ </a>
+ </div>
</div>
-<div style="overflow-y:scroll; width: 100%; position: absolute; top: 3em; bottom:70px ">
+<div class="container-fluid">
{% 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/toastergui/templates/basebuildpage.html b/lib/toaster/toastergui/templates/basebuildpage.html
index d590f28bc..1b037f953 100644
--- a/lib/toaster/toastergui/templates/basebuildpage.html
+++ b/lib/toaster/toastergui/templates/basebuildpage.html
@@ -1,17 +1,56 @@
-{% 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="{% 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>
+{% extends "base.html" %}
+{% load humanize %}
+{% block pagecontent %}
+
+
+ <div class="span12">
+<!-- Breadcrumbs -->
+ <div class="section">
+ <ul class="breadcrumb" id="breadcrumb">
+<li><a href="{% url 'all-builds' %}">All builds</a></li>
+<li><a href="{%url 'builddashboard' build.pk%}">{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%} {{build.machine}} ({{build.completed_on|naturaltime}})</a></li>
+ {% block localbreadcrumb %}{% endblock %}
+ </ul>
+ <script>
+ $( function () {
+ $('#breadcrumb > li').append("<span class=\"divider\">→</span>");
+ $('#breadcrumb > li:last').addClass("active");
+ $('#breadcrumb > li:last > span').remove();
+ });
+ </script>
+ </div>
+
+ <div class="row-fluid">
+
+ <!-- begin left sidebar container -->
+ <div id="nav" class="span2">
+ <ul class="nav nav-list well">
+ <li class="nav-header">Images</li>
+ {% for t in build.target_set.all %}
+ <li><a href="{% url 'target' build.pk t.pk %}">{{t.target}}</a><li>
+ {% endfor %}
+ <li class="nav-header">Build</li>
+ <li><a href="{% url 'configuration' build.pk %}">Configuration</a></li>
+ <li><a href="{% url 'tasks' build.pk %}">Tasks</a></li>
+ <li><a href="{% url 'recipes' build.pk %}">Recipes</a></li>
+ <li><a href="{% url 'packages' build.pk %}">Packages</a></li>
+ <li class="nav-header">Performance</li>
+ <li><a href="{% url 'buildtime' build.pk %}">Time</a></li>
+ <li><a href="{% url 'cpuusage' build.pk %}">CPU usage</a></li>
+ <li><a href="{% url 'diskio' build.pk %}">Disk I/O</a></li>
+ </ul>
+ </div>
+
+ <!-- end left sidebar container -->
+ <!-- Begin right container -->
+ <div class="row span10">
+ {% block buildinfomain %}{% endblock %}
+ </div>
+
+
+ </div>
+ </div>
+
+
{% endblock %}
diff --git a/lib/toaster/toastergui/templates/basetable.html b/lib/toaster/toastergui/templates/basetable.html
deleted file mode 100644
index 16628eafb..000000000
--- a/lib/toaster/toastergui/templates/basetable.html
+++ /dev/null
@@ -1,64 +0,0 @@
-{% 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"> Filter: <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 style="display: block; float:right; margin-left: auto; margin-right:5em"><span class="pagination" style="vertical-align: top; margin-right: 3em">Showing {{objects.start_index}} to {{objects.end_index}} out of {{objects.paginator.count}} entries.&nbsp;</span>
- <ul class="pagination" style="display: block-inline">
-{%if objects.has_previous %}
- <li><a href="?page={{objects.previous_page_number}}">&laquo;</a></li>
-{%else%}
- <li class="disabled"><a href="#">&laquo;</a></li>
-{%endif%}
-{% for i in objects.page_range %}
- <li{%if i == objects.number %} class="active" {%endif%}><a href="?page={{i}}">{{i}}</a></li>
-{% endfor %}
-{%if objects.has_next%}
- <li><a href="?page={{objects.next_page_number}}">&raquo;</a></li>
-{%else%}
- <li class="disabled"><a href="#">&raquo;</a></li>
-{%endif%}
- </ul>
-</div>
-
- <table class="table table-striped table-condensed" style="width:95%">
-{% block pagetable %}
-{% endblock %}
- </table>
-</div>
-
-{% endblock %}
diff --git a/lib/toaster/toastergui/templates/basetable_bottom.html b/lib/toaster/toastergui/templates/basetable_bottom.html
new file mode 100644
index 000000000..2a6f08492
--- /dev/null
+++ b/lib/toaster/toastergui/templates/basetable_bottom.html
@@ -0,0 +1,60 @@
+ </table>
+
+<!-- Show pagination controls -->
+<div class="pagination pagination-centered">
+ <div class="pull-left">
+ Showing {{objects.start_index}} to {{objects.end_index}} out of {{objects.paginator.count}} entries.
+ </div>
+
+ <ul class="pagination" style="display: block-inline">
+{%if objects.has_previous %}
+ <li><a href="?page={{objects.previous_page_number}}&count={{request.GET.count}}">&laquo;</a></li>
+{%else%}
+ <li class="disabled"><a href="#">&laquo;</a></li>
+{%endif%}
+{% for i in objects.page_range %}
+ <li{%if i == objects.number %} class="active" {%endif%}><a href="?page={{i}}&count={{request.GET.count}}">{{i}}</a></li>
+{% endfor %}
+{%if objects.has_next%}
+ <li><a href="?page={{objects.next_page_number}}&count={{request.GET.count}}">&raquo;</a></li>
+{%else%}
+ <li class="disabled"><a href="#">&raquo;</a></li>
+{%endif%}
+ </ul>
+ <div class="pull-right">
+ <span class="help-inline" style="padding-top:5px;">Show rows:</span>
+ <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
+ {% with "2 5 10 25 50 100" as list%}
+ {% for i in list.split %}<option{%if i == request.GET.count %} selected{%endif%}>{{i}}</option>
+ {% endfor %}
+ {% endwith %}
+ </select>
+ </div>
+</div>
+
+<!-- Update page display settings -->
+
+<script>
+ $(document).ready(function() {
+
+ $('.chbxtoggle').each(function () {
+ showhideTableColumn($(this).attr('id'), $(this).is(':checked'))
+ });
+
+ //turn edit columns dropdown into a multi-select menu
+ $('.dropdown-menu input, .dropdown-menu label').click(function(e) {
+ e.stopPropagation();
+ });
+
+ //show tooltip with applied filter
+ $('#filtered').tooltip({container:'table', placement:'bottom', delay:{hide:1500}, html:true});
+
+ //progress bar tooltip
+ $('.progress, .lead span').tooltip({container:'table', placement:'top'});
+
+ $(".pagesize").change(function () {
+ $(".pagesize option:selected").each(function ()
+ {reload_params({"count":$(this).text()}); });
+ });
+});
+</script>
diff --git a/lib/toaster/toastergui/templates/basetable_top.html b/lib/toaster/toastergui/templates/basetable_top.html
new file mode 100644
index 000000000..b9277b4a3
--- /dev/null
+++ b/lib/toaster/toastergui/templates/basetable_top.html
@@ -0,0 +1,66 @@
+<!-- component to display a generic table -->
+ <script>
+ function showhideTableColumn(clname, sh) {
+ if (sh) $('.' + clname).show();
+ else $('.' + clname).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>
+
+<!-- control header -->
+<div class="navbar">
+ <div class="navbar-inner">
+ <form class="navbar-search input-append pull-left">
+ <input class="input-xxlarge" type="text" placeholder="Search {{objectname}}" />
+ <button class="btn" type="button">Search</button>
+ </form>
+ <div class="pull-right">
+
+ {% if tablecols %}
+ <div class="btn-group">
+ <button class="btn dropdown-toggle" data-toggle="dropdown">
+ Edit columns
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+
+ {% for i in tablecols %}
+ <li>
+ <label class="checkbox">
+<input type="checkbox" class="chbxtoggle" id="{{i.clclass}}" value="ct{{i.name}}" {% if i.clclass %}{% if not i.hidden %}checked="checked"{%endif%} onchange="showhideTableColumn($(this).attr('id'), $(this).is(':checked'))" {%else%} disabled{% endif %}/> {{i.name}}
+ </label>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ {% endif %}
+
+ <div style="display:inline">
+ <span class="divider-vertical"></span>
+ <span class="help-inline" style="padding-top:5px;">Show rows:</span>
+ <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
+ {% with "2 5 10 25 50 100" as list%}
+ {% for i in list.split %}<option{%if i == request.GET.count %} selected{%endif%}>{{i}}</option>
+ {% endfor %}
+ {% endwith %}
+ </select>
+ </div>
+ </div>
+ </div>
+ </div>
+
+<!-- the actual rows of the table -->
+ <table class="table table-bordered table-hover tablesorter" id="otable">
+
diff --git a/lib/toaster/toastergui/templates/bpackage.html b/lib/toaster/toastergui/templates/bpackage.html
index 67fc65ca3..3329ddae5 100644
--- a/lib/toaster/toastergui/templates/bpackage.html
+++ b/lib/toaster/toastergui/templates/bpackage.html
@@ -1,7 +1,12 @@
{% extends "basebuildpage.html" %}
-{% block pagetitle %}Packages{% endblock %}
-{% block pagetable %}
+{% block localbreadcrumb %}
+<li>Packages</li>
+{% endblock %}
+
+{% block buildinfomain %}
+{% include "basetable_top.html" %}
+
{% if not objects %}
<p>No packages were recorded for this target!</p>
{% else %}
@@ -21,7 +26,7 @@
{% for package in objects %}
<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><a name="#{{package.name}}" href="{% url "package" build.pk package.pk %}">{{package.name}} ({{package.filelist_bpackage.count}} files)</a></td>
<td>{{package.version}}-{{package.revision}}</td>
<td>{%if package.recipe%}<a href="{% url "layer_versions_recipes" package.recipe.layer_version_id %}#{{package.recipe.name}}">{{package.recipe.name}}</a>{{package.package_name}}</a>{%endif%}</td>
@@ -41,4 +46,5 @@
{% endif %}
+{% include "basetable_bottom.html" %}
{% endblock %}
diff --git a/lib/toaster/toastergui/templates/build.html b/lib/toaster/toastergui/templates/build.html
index 4fa87d527..27ce1ccbc 100644
--- a/lib/toaster/toastergui/templates/build.html
+++ b/lib/toaster/toastergui/templates/build.html
@@ -1,43 +1,96 @@
-{% extends "basetable.html" %}
+{% extends "base.html" %}
+
+
+{% load projecttags %}
+{% load humanize %}
+
+{% block pagecontent %}
+<div class="row-fluid">
+
+<div class="page-header" style="margin-top:40px;">
+ <h1>
+ Recent Builds
+ </h1>
+</div>
+{{build_mru}}
+{% for build in mru %}
+<div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}">
+ <div class="row-fluid">
+ <div class="lead span5">
+ {%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}
+ <a href="{%url 'builddashboard' build.pk%}">
+ <span data-toggle="tooltip" {%if build.target_set.all.count > 1%}title="Targets: {%for target in build.target_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%} {{build.machine}} ({{build.completed_on|naturaltime}})</span>
+ </a>
+ </div>
+{%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
+ <div class="span2 lead">
+{% if build.errors_no %}
+ <i class="icon-minus-sign red"></i> <a href="{%url 'builddashboard' build.pk%}" class="error">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
+{% endif %}
+ </div>
+ <div class="span2 lead">
+{% if build.warnings_no %}
+ <i class="icon-warning-sign yellow"></i> <a href="{%url 'builddashboard' build.pk%}" class="warning">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>
+{% endif %}
+ </div>
+ <div class="lead pull-right">
+ Build time: <a href="build-time.html">{{ build|timespent }}</a>
+ </div>
+{%endif%}{%if build.outcome == build.IN_PROGRESS %}
+ <div class="span4">
+ <div class="progress" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete">
+ <div style="width: {{build.completeper}}%;" class="bar"></div>
+ </div>
+ </div>
+ <div class="lead pull-right">ETA: in {{build.eta|naturaltime}}</div>
+{%endif%}
+ </div>
+</div>
+
+{% endfor %}
-{% block pagename %}
- <h1>Toaster - Builds</h1>
-{% endblock %}
-{% block pagetable %}
+<div class="page-header" style="margin-top:40px;">
+ <h1>
+ All builds
+ </h1>
+</div>
+
+{% include "basetable_top.html" %}
- {% 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>
+ <th class="outcome span2"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The outcome tells you if a build completed successfully or failed"></i> <a href="#" style="font-weight:normal;">Outcome</a> <div class="btn-group pull-right"> <a href="#outcome" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> </th>
+ <th class="target"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="This is the build target(s): one or more recipes or image recipes"></i> <a href="#" style="font-weight:normal;">Target</a> </th>
+ <th class="machine span3"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The machine is the hardware for which you are building"></i> <a href="#" style="font-weight:normal;">Machine</a> </th>
+ <th class="started_on"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The date and time you started the build"></i> <a href="#" style="font-weight:normal;">Started on</a> <div class="btn-group pull-right"> <a href="#started-on" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> </th>
+ <th class="completed_on"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The date and time the build finished"></i> <a href="#" class="sorted"> Completed on </a> <div class="btn-group pull-right"> <a href="#completed-on" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> <i class="icon-caret-down"></i> </th>
+ <th class="failed_tasks"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="How many tasks failed during the build"></i> <a href="#" style="font-weight:normal;">Failed tasks</a> <div class="btn-group pull-right"> <a href="#failed-tasks" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> <!--div id="filtered" class="btn-group pull-right" title="<p>Showing only builds with failed tasks</p><p><a class='btn btn-mini btn-primary' href='#'>Show all builds</a></p>"> <a class="btn btn-mini btn-primary"> <i class="icon-filter"></i> </a> </div--> </th>
+ <th class="errors"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="How many errors were encountered during the build (if any)"></i> <a href="#" style="font-weight:normal;">Errors</a> <div class="btn-group pull-right"> <a href="#errors" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> </th>
+ <th class="warnings"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="How many warnigns were encountered during the build (if any)"></i> <a href="#" style="font-weight:normal;">Warnings</a> <div class="btn-group pull-right"> <a href="#warnings" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> <!--div id="filtered" class="btn-group pull-right" title="<p>Showing only builds without warnings</p><p><a class='btn btn-mini btn-primary' href='#'>Show all builds</a></p>"> <a class="btn btn-mini btn-primary"> <i class="icon-filter"></i> </a> </div--> </th>
+ <th class="time"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="How long it took the build to finish"></i> <a href="#" style="font-weight:normal;">Time</a> </th>
+ <th class="log span4"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The location in disk of the build main log file"></i> <a href="#" style="font-weight:normal;">Log</a> </th>
+ <th class="output"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory"></i> <a href="#" style="font-weight:normal;">Output</a> </th>
+
</tr>
{% for build in objects %}
<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 %}{%if t.is_image %}<a href="{% url "tpackage" build.id t.id %}">{% endif %}{{t.target}}{% if t.is_image %}</a>{% 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>
+ <td class="outcome"><a href="{% url "configuration" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td>
+ <td class="target">{% for t in build.target_set.all %}{%if t.is_image %}<a href="{% url "target" build.id t.id %}">{% endif %}{{t.target}}{% if t.is_image %}</a>{% endif %}<br/>{% endfor %}</td>
+ <td class="machine">{{build.machine}}</td>
+ <td class="started_on">{{build.started_on}}</td>
+ <td class="completed_on">{{build.completed_on}}</td>
+ <td class="failed_tasks"></td>
+ <td class="errors">{% if build.errors_no %}<a class="error" href="#">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>{%endif%}</td>
+ <td class="warnings">{% if build.warnings_no %}<a class="warning" href="#">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
+ <td class="time">{{build|timespent}}</td>
+ <td class="log">{{build.log}}</td>
+ <td class="output">{% if build.outcome == 0 %}{% for t in build.target_set.all %}{% if t.is_image %}{{build.image_fstypes}}{% endif %}{% endfor %}{% endif %}</td>
</tr>
{% endfor %}
-{% endblock %}
+{% include "basetable_bottom.html" %}
+
+</div>
+{% endblock %}
diff --git a/lib/toaster/toastergui/templates/builddashboard.html b/lib/toaster/toastergui/templates/builddashboard.html
new file mode 100644
index 000000000..7c58cc0b2
--- /dev/null
+++ b/lib/toaster/toastergui/templates/builddashboard.html
@@ -0,0 +1,8 @@
+{% extends "basebuildpage.html" %}
+{% block localbreadcrumb %}
+<li>Dashboard</li>
+{% endblock %}
+
+{% block buildinfomain %}
+
+{% endblock %}
diff --git a/lib/toaster/toastergui/templates/buildtime.html b/lib/toaster/toastergui/templates/buildtime.html
new file mode 100644
index 000000000..ea84ae797
--- /dev/null
+++ b/lib/toaster/toastergui/templates/buildtime.html
@@ -0,0 +1,4 @@
+{% extends "basebuildpage.html" %}
+{% block localbreadcrumb %}
+<li>Build Time</li>
+{% endblock %}
diff --git a/lib/toaster/toastergui/templates/configuration.html b/lib/toaster/toastergui/templates/configuration.html
index 521620fdc..e390a95ff 100644
--- a/lib/toaster/toastergui/templates/configuration.html
+++ b/lib/toaster/toastergui/templates/configuration.html
@@ -1,7 +1,11 @@
{% extends "basebuildpage.html" %}
+{% block localbreadcrumb %}
+<li>Configuration</li>
+{% endblock %}
+
+{% block buildinfomain %}
-{% block pagetitle %}Configuration{% endblock %}
-{% block pagetable %}
+{% include "basetable_top.html" %}
<tr>
<th>Name</th>
@@ -19,4 +23,6 @@
<td>{{variable.variable_value}}</td>
{% endfor %}
+{% include "basetable_bottom.html" %}
+
{% endblock %}
diff --git a/lib/toaster/toastergui/templates/cpuusage.html b/lib/toaster/toastergui/templates/cpuusage.html
new file mode 100644
index 000000000..02f07b760
--- /dev/null
+++ b/lib/toaster/toastergui/templates/cpuusage.html
@@ -0,0 +1,4 @@
+{% extends "basebuildpage.html" %}
+{% block localbreadcrumb %}
+<li>Cpu Usage</li>
+{% endblock %}
diff --git a/lib/toaster/toastergui/templates/diskio.html b/lib/toaster/toastergui/templates/diskio.html
new file mode 100644
index 000000000..c5cef6f38
--- /dev/null
+++ b/lib/toaster/toastergui/templates/diskio.html
@@ -0,0 +1,4 @@
+{% extends "basebuildpage.html" %}
+{% block localbreadcrumb %}
+<li>Disk I/O</li>
+{% endblock %}
diff --git a/lib/toaster/toastergui/templates/recipe.html b/lib/toaster/toastergui/templates/recipe.html
index d7f57eb9e..87c69b8d3 100644
--- a/lib/toaster/toastergui/templates/recipe.html
+++ b/lib/toaster/toastergui/templates/recipe.html
@@ -1,14 +1,11 @@
-{% extends "basetable.html" %}
+{% extends "basebuildpage.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}}&nbsp;:&nbsp;{{layer_version.priority}}</a></li>
-</ul>
- <h1>Toaster - Recipes for a Layer</h1>
+{% block localbreadcrumb %}
+<li>Recipes</li>
{% endblock %}
-{% block pagetable %}
- {% load projecttags %}
+{% block buildinfomain %}
+{% include "basetable_top.html" %}
<tr>
</tr>
@@ -49,4 +46,6 @@
{% endfor %}
+{% include "basetable_bottom.html" %}
+
{% endblock %}
diff --git a/lib/toaster/toastergui/templates/target.html b/lib/toaster/toastergui/templates/target.html
new file mode 100644
index 000000000..f2d0ad461
--- /dev/null
+++ b/lib/toaster/toastergui/templates/target.html
@@ -0,0 +1,8 @@
+{% extends "basebuildpage.html" %}
+
+{% block localbreadcrumb %}
+<li>Target</li>
+{% endblock %}
+
+{% block buildinfomain %}
+{% endblock %}
diff --git a/lib/toaster/toastergui/templates/task.html b/lib/toaster/toastergui/templates/task.html
index de965ab79..6af2c5127 100644
--- a/lib/toaster/toastergui/templates/task.html
+++ b/lib/toaster/toastergui/templates/task.html
@@ -1,7 +1,13 @@
{% extends "basebuildpage.html" %}
-{% block pagetitle %}Tasks{% endblock %}
-{% block pagetable %}
+{% block localbreadcrumb %}
+<li>Tasks</li>
+{% endblock %}
+
+{% block buildinfomain %}
+{% include "basetable_top.html" %}
+
+
{% if not objects %}
<p>No tasks were executed in this build!</p>
{% else %}
@@ -64,4 +70,5 @@
{% endif %}
+{% include "basetable_bottom.html" %}
{% endblock %}
diff --git a/lib/toaster/toastergui/templatetags/projecttags.py b/lib/toaster/toastergui/templatetags/projecttags.py
index 0c0d804c0..5f6037993 100644
--- a/lib/toaster/toastergui/templatetags/projecttags.py
+++ b/lib/toaster/toastergui/templatetags/projecttags.py
@@ -24,3 +24,8 @@ register = template.Library()
@register.simple_tag
def time_difference(start_time, end_time):
return end_time - start_time
+
+@register.filter(name = 'timespent')
+def timespent(build_object):
+ tdsec = (build_object.completed_on - build_object.started_on).total_seconds()
+ return "%02d:%02d:%02d" % (int(tdsec/3600), int((tdsec - tdsec/ 3600)/ 60), int(tdsec) % 60)
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index b84c95f08..f531eb013 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -19,14 +19,34 @@
from django.conf.urls import patterns, include, url
from django.views.generic import RedirectView
-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'),
+urlpatterns = patterns('toastergui.views',
+ # landing page
+ url(r'^builds/$', 'builds', name='all-builds'),
+ # build info navigation
+ url(r'^build/(?P<build_id>\d+)$', 'builddashboard', name="builddashboard"),
+
+ url(r'^build/(?P<build_id>\d+)/tasks/$', 'tasks', name='tasks'),
+ url(r'^build/(?P<build_id>\d+)/task/(?P<task_id>\d+)$', 'task', name='task'),
+
+ url(r'^build/(?P<build_id>\d+)/recipes/$', 'recipes', name='recipes'),
+ url(r'^build/(?P<build_id>\d+)/recipe/(?P<recipe_id>\d+)$', 'recipe', name='recipe'),
+
+ url(r'^build/(?P<build_id>\d+)/packages/$', 'bpackage', name='packages'),
+ url(r'^build/(?P<build_id>\d+)/package/(?P<package_id>\d+)$', 'bfile', name='package'),
+
+ # images are known as targets in the internal model
+ url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)$', 'target', name='target'),
+ url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)/packages$', 'tpackage', name='targetpackages'),
+
+ url(r'^build/(?P<build_id>\d+)/configuration$', 'configuration', name='configuration'),
+ url(r'^build/(?P<build_id>\d+)/buildtime$', 'buildtime', name='buildtime'),
+ url(r'^build/(?P<build_id>\d+)/cpuusage$', 'cpuusage', name='cpuusage'),
+ url(r'^build/(?P<build_id>\d+)/diskio$', 'diskio', name='diskio'),
+
+
+ # urls not linked from the dashboard
url(r'^layers/$', 'layer', name='all-layers'),
url(r'^layerversions/(?P<layerversion_id>\d+)/recipes/.*$', 'layer_versions_recipes', name='layer_versions_recipes'),
+ # default redirection
url(r'^$', RedirectView.as_view( url= 'builds/')),
)
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 7cb9b4237..663e03dfd 100644
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -19,7 +19,7 @@
import operator
from django.db.models import Q
-from django.shortcuts import render
+from django.shortcuts import render, redirect
from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable
from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency
from orm.models import Target_Installed_Package
@@ -35,6 +35,7 @@ def _build_page_range(paginator, index = 1):
except EmptyPage:
page = paginator.page(paginator.num_pages)
+
page.page_range = [page.number]
crt_range = 0
for i in range(1,5):
@@ -48,22 +49,124 @@ def _build_page_range(paginator, index = 1):
break
return page
-@cache_control(no_store=True)
-def build(request):
- template = 'build.html'
- logs = LogMessage.objects.all()
- build_info = _build_page_range(Paginator(Build.objects.order_by("-id"), 10),request.GET.get('page', 1))
+def _verify_parameters(g, mandatory_parameters):
+ miss = []
+ for mp in mandatory_parameters:
+ if not mp in g:
+ miss.append(mp)
+ if len(miss):
+ return miss
+ return None
+
+def _redirect_parameters(view, g, mandatory_parameters):
+ import urllib
+ from django.core.urlresolvers import reverse
+ url = reverse(view)
+ params = {}
+ for i in g:
+ params[i] = g[i]
+ for i in mandatory_parameters:
+ if not i in params:
+ params[i] = mandatory_parameters[i]
+
+ return redirect(url + "?%s" % urllib.urlencode(params))
+
- context = {'objects': build_info, 'logs': logs ,
- 'hideshowcols' : [
- {'name': 'Output', 'order':10},
- {'name': 'Log', 'order':11},
+# shows the "all builds" page
+def builds(request):
+ template = 'build.html'
+ # define here what parameters the view needs in the GET portion in order to
+ # be able to display something. 'count' and 'page' are mandatory for all views
+ # that use paginators.
+ mandatory_parameters = { 'count': 10, 'page' : 1};
+ retval = _verify_parameters( request.GET, mandatory_parameters )
+ if retval:
+ return _redirect_parameters( builds, request.GET, mandatory_parameters)
+
+ # retrieve the objects that will be displayed in the table
+ build_info = _build_page_range(Paginator(Build.objects.exclude(outcome = Build.IN_PROGRESS).order_by("-id"), request.GET.get('count', 10)),request.GET.get('page', 1))
+
+ # build view-specific information; this is rendered specifically in the builds page
+ build_mru = Build.objects.order_by("-started_on")[:3]
+ for b in [ x for x in build_mru if x.outcome == Build.IN_PROGRESS ]:
+ tf = Task.objects.filter(build = b)
+ b.completeper = tf.exclude(order__isnull=True).count()*100/tf.count()
+ from django.utils import timezone
+ b.eta = timezone.now() + ((timezone.now() - b.started_on)*100/b.completeper)
+
+ # send the data to the template
+ context = {
+ # specific info for
+ 'mru' : build_mru,
+ # TODO: common objects for all table views, adapt as needed
+ 'objects' : build_info,
+ 'tablecols' : [
+ {'name': 'Target ', 'clclass': 'target',},
+ {'name': 'Machine ', 'clclass': 'machine'},
+ {'name': 'Completed on ', 'clclass': 'completed_on'},
+ {'name': 'Failed tasks ', 'clclass': 'failed_tasks'},
+ {'name': 'Errors ', 'clclass': 'errors_no'},
+ {'name': 'Warnings', 'clclass': 'warnings_no'},
+ {'name': 'Output ', 'clclass': 'output'},
+ {'name': 'Started on ', 'clclass': 'started_on', 'hidden' : 1},
+ {'name': 'Time ', 'clclass': 'time', 'hidden' : 1},
+ {'name': 'Output', 'clclass': 'output'},
+ {'name': 'Log', 'clclass': 'log', 'hidden': 1},
]}
return render(request, template, context)
+# build dashboard for a single build, coming in as argument
+def builddashboard(request, build_id):
+ template = "builddashboard.html"
+ if Build.objects.filter(pk=build_id).count() == 0 :
+ return redirect(builds)
+ context = {
+ 'build' : Build.objects.filter(pk=build_id)[0],
+ }
+ return render(request, template, context)
+
+def task(request, build_id, task_id):
+ template = "singletask.html"
+ if Build.objects.filter(pk=build_id).count() == 0 :
+ return redirect(builds)
+ context = {
+ 'build' : Build.objects.filter(pk=build_id)[0],
+ }
+ return render(request, template, context)
+
+def recipe(request, build_id, recipe_id):
+ template = "recipe.html"
+ if Recipe.objects.filter(pk=recipe_id).count() == 0 :
+ return redirect(builds)
+ context = {
+ 'build' : Build.objects.filter(pk=build_id)[0],
+ 'object' : Recipe.objects.filter(pk=recipe_id)[0],
+ }
+ return render(request, template, context)
+
+def package(request, build_id, package_id):
+ template = "singlepackage.html"
+ if Build.objects.filter(pk=build_id).count() == 0 :
+ return redirect(builds)
+ context = {
+ 'build' : Build.objects.filter(pk=build_id)[0],
+ }
+ return render(request, template, context)
+
+def target(request, build_id, target_id):
+ template = "target.html"
+ if Build.objects.filter(pk=build_id).count() == 0 :
+ return redirect(builds)
+ context = {
+ 'build' : Build.objects.filter(pk=build_id)[0],
+ }
+ return render(request, template, context)
+
+
+
def _find_task_revdep(task):
tp = []
for p in Task_Dependency.objects.filter(depends_on=task):
@@ -81,7 +184,7 @@ def _find_task_provider(task):
return trc
return None
-def task(request, build_id):
+def tasks(request, build_id):
template = 'task.html'
tasks = _build_page_range(Paginator(Task.objects.filter(build=build_id), 100),request.GET.get('page', 1))
@@ -94,12 +197,52 @@ def task(request, build_id):
return render(request, template, context)
+def recipes(request, build_id):
+ template = 'recipe.html'
+
+ recipes = _build_page_range(Paginator(Recipe.objects.filter(build_recipe=build_id), 100),request.GET.get('page', 1))
+
+ context = {'build': Build.objects.filter(pk=build_id)[0], 'objects': recipes}
+
+ return render(request, template, context)
+
+
def configuration(request, build_id):
template = 'configuration.html'
variables = _build_page_range(Paginator(Variable.objects.filter(build=build_id), 50), request.GET.get('page', 1))
context = {'build': Build.objects.filter(pk=build_id)[0], 'objects' : variables}
return render(request, template, context)
+def buildtime(request, build_id):
+ template = "buildtime.html"
+ if Build.objects.filter(pk=build_id).count() == 0 :
+ return redirect(builds)
+ context = {
+ 'build' : Build.objects.filter(pk=build_id)[0],
+ }
+ return render(request, template, context)
+
+def cpuusage(request, build_id):
+ template = "cpuusage.html"
+ if Build.objects.filter(pk=build_id).count() == 0 :
+ return redirect(builds)
+ context = {
+ 'build' : Build.objects.filter(pk=build_id)[0],
+ }
+ return render(request, template, context)
+
+def diskio(request, build_id):
+ template = "diskio.html"
+ if Build.objects.filter(pk=build_id).count() == 0 :
+ return redirect(builds)
+ context = {
+ 'build' : Build.objects.filter(pk=build_id)[0],
+ }
+ return render(request, template, context)
+
+
+
+
def bpackage(request, build_id):
template = 'bpackage.html'
packages = Package.objects.filter(build = build_id)
@@ -227,8 +370,8 @@ def model_explorer(request, model_name):
response_data['count'] = queryset.count()
else:
response_data['count'] = 0
-
response_data['list'] = serializers.serialize('json', queryset)
+# response_data = serializers.serialize('json', queryset)
return HttpResponse(json.dumps(response_data),
content_type='application/json')
diff --git a/lib/toaster/toastermain/settings.py b/lib/toaster/toastermain/settings.py
index b76218b95..679035e88 100644
--- a/lib/toaster/toastermain/settings.py
+++ b/lib/toaster/toastermain/settings.py
@@ -133,6 +133,15 @@ TEMPLATE_DIRS = (
# Don't forget to use absolute paths, not relative paths.
)
+TEMPLATE_CONTEXT_PROCESSORS = ('django.contrib.auth.context_processors.auth',
+ 'django.core.context_processors.debug',
+ 'django.core.context_processors.i18n',
+ 'django.core.context_processors.media',
+ 'django.core.context_processors.static',
+ 'django.core.context_processors.tz',
+ 'django.contrib.messages.context_processors.messages',
+ "django.core.context_processors.request")
+
INSTALLED_APPS = (
#'django.contrib.auth',
#'django.contrib.contenttypes',
@@ -144,6 +153,7 @@ INSTALLED_APPS = (
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
+ 'django.contrib.humanize',
'orm',
'toastermain',
'toastergui',