All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] Toaster feature / bugfix patchset
@ 2015-01-14 12:46 Alex DAMIAN
  2015-01-14 12:46 ` [PATCH 1/4] toaster: Add layer details page feature Alex DAMIAN
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Alex DAMIAN @ 2015-01-14 12:46 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

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

This patchset brings in the layer details page, and the managed version
of the "all builds" page.

The patches have been reviewed on the Toaster mailing list.

Can you please pull at your convenience ?


Cheers,
Alex


The following changes since commit 0557d03c170fba8d7efe82be1b9641d0eb229213:

  cooker/cache/parse: Implement pyinofity based reconfigure (2015-01-13 22:23:21 +0000)

are available in the git repository at:

  git://git.yoctoproject.org/poky-contrib adamian/20150114-submission-bb
  http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=adamian/20150114-submission-bb

Alexandru DAMIAN (2):
  toastergui: all builds page lists failed build requests
  toasterui: fix variable data error

Michael Wood (2):
  toaster: Add layer details page feature
  toaster: layerdetails Remove compatibility setting

 lib/bb/ui/buildinfohelper.py                       |   2 +-
 lib/toaster/toastergui/static/css/default.css      |   1 +
 lib/toaster/toastergui/static/js/layerdetails.js   | 398 ++++++++++++
 lib/toaster/toastergui/static/js/libtoaster.js     |  35 +
 lib/toaster/toastergui/templates/layerdetails.html | 625 +++++++++++++-----
 .../toastergui/templates/layers_dep_modal.html     |   9 +
 .../toastergui/templates/managed_builds.html       | 135 ++++
 .../toastergui/templates/managed_mrb_section.html  | 172 +++++
 lib/toaster/toastergui/templatetags/projecttags.py |  19 +
 lib/toaster/toastergui/urls.py                     |   1 +
 lib/toaster/toastergui/views.py                    | 708 +++++++++++++++------
 11 files changed, 1755 insertions(+), 350 deletions(-)
 create mode 100644 lib/toaster/toastergui/static/js/layerdetails.js
 create mode 100644 lib/toaster/toastergui/templates/managed_builds.html
 create mode 100644 lib/toaster/toastergui/templates/managed_mrb_section.html

-- 
1.9.1



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

* [PATCH 1/4] toaster: Add layer details page feature
  2015-01-14 12:46 [PATCH 0/4] Toaster feature / bugfix patchset Alex DAMIAN
@ 2015-01-14 12:46 ` Alex DAMIAN
  2015-01-14 12:46 ` [PATCH 2/4] toaster: layerdetails Remove compatibility setting Alex DAMIAN
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Alex DAMIAN @ 2015-01-14 12:46 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

This commit adds the layer details page which shows the metadata for the
layer such as layer description, machines associated with the layer as well
as the targets provided.
If the layer is an imported layer this page also allows you to update
the layer's configuration.
From this page you can add/remove the layer from the current project

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 lib/toaster/toastergui/static/css/default.css      |   1 +
 lib/toaster/toastergui/static/js/layerdetails.js   | 404 +++++++++++++
 lib/toaster/toastergui/static/js/libtoaster.js     |  35 ++
 lib/toaster/toastergui/templates/layerdetails.html | 644 ++++++++++++++++-----
 .../toastergui/templates/layers_dep_modal.html     |   9 +
 lib/toaster/toastergui/urls.py                     |   1 +
 lib/toaster/toastergui/views.py                    |  94 ++-
 7 files changed, 1038 insertions(+), 150 deletions(-)
 create mode 100644 lib/toaster/toastergui/static/js/layerdetails.js

diff --git a/lib/toaster/toastergui/static/css/default.css b/lib/toaster/toastergui/static/css/default.css
index 199c753..a3fa0dd 100644
--- a/lib/toaster/toastergui/static/css/default.css
+++ b/lib/toaster/toastergui/static/css/default.css
@@ -232,3 +232,4 @@ dd > span { line-height: 20px; }
 .animate-repeat.ng-enter.ng-enter-active {
   opacity:1;
 }
+.tab-pane table { margin-top: 10px; }
diff --git a/lib/toaster/toastergui/static/js/layerdetails.js b/lib/toaster/toastergui/static/js/layerdetails.js
new file mode 100644
index 0000000..a5a6330
--- /dev/null
+++ b/lib/toaster/toastergui/static/js/layerdetails.js
@@ -0,0 +1,404 @@
+"use strict"
+
+function layerDetailsPageInit (ctx) {
+
+  var layerDepInput = $("#layer-dep-input");
+  var layerDepBtn = $("#add-layer-dependency-btn");
+  var layerDepsList = $("#layer-deps-list");
+  var currentLayerDepSelection;
+  var addRmLayerBtn = $("#add-remove-layer-btn");
+
+  /* setup the dependencies typeahead */
+  libtoaster.makeTypeahead(layerDepInput, ctx.xhrDataTypeaheadUrl, { type : "layers", project_id: ctx.projectId, include_added: "true" }, function(item){
+    currentLayerDepSelection = item;
+
+    layerDepBtn.removeAttr("disabled");
+  });
+
+  function addRemoveDep(depLayerId, add, doneCb) {
+    var data = { layer_version_id : ctx.layerVersion.id };
+    if (add)
+      data.add_dep = depLayerId;
+    else
+      data.rm_dep = depLayerId;
+
+    $.ajax({
+        type: "POST",
+        url: ctx.xhrUpdateLayerUrl,
+        data: data,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function (data) {
+          if (data.error != "ok") {
+            console.warn(data.error);
+          } else {
+            doneCb();
+          }
+        },
+        error: function (data) {
+          console.warn("Call failed");
+          console.warn(data);
+        }
+    });
+  }
+
+  function layerRemoveClick() {
+    var toRemove = $(this).parent().data('layer-id');
+    var layerDepItem = $(this);
+
+    addRemoveDep(toRemove, false, function(){
+      layerDepItem.parent().fadeOut(function (){
+        layerDepItem.remove();
+      });
+    });
+  }
+
+  /* Add dependency layer button click handler */
+  layerDepBtn.click(function(){
+    if (currentLayerDepSelection == undefined)
+      return;
+
+    addRemoveDep(currentLayerDepSelection.id, true, function(){
+      /* Make a list item for the new layer dependency */
+      var newLayerDep = $("<li><a></a><span class=\"icon-trash\" data-toggle=\"tooltip\" title=\"Delete\"></span></li>");
+
+      newLayerDep.data('layer-id', currentLayerDepSelection.id);
+      newLayerDep.children("span").tooltip();
+
+      var link = newLayerDep.children("a");
+      link.attr("href", ctx.layerDetailsUrl+String(currentLayerDepSelection.id));
+      link.text(currentLayerDepSelection.name);
+      link.tooltip({title: currentLayerDepSelection.tooltip, placement: "right"});
+
+      /* Connect up the tash icon */
+      var trashItem = newLayerDep.children("span");
+      trashItem.click(layerRemoveClick);
+
+      layerDepsList.append(newLayerDep);
+      /* Clear the current selection */
+      layerDepInput.val("");
+      currentLayerDepSelection = undefined;
+      layerDepBtn.attr("disabled","disabled");
+    });
+  });
+
+  $(".icon-pencil").click(function (){
+    var mParent = $(this).parent("dd");
+    mParent.prev().css("margin-top", "10px");
+    mParent.children("form").slideDown();
+    var currentVal = mParent.children(".current-value");
+    currentVal.hide();
+    /* Set the current value to the input field */
+    mParent.find("textarea,input").val(currentVal.text());
+    /* Hides the "Not set" text */
+    mParent.children(".muted").hide();
+    /* We're editing so hide the delete icon */
+    mParent.children(".delete-current-value").hide();
+    mParent.find(".cancel").show();
+    $(this).hide();
+  });
+
+  $(".delete-current-value").click(function(){
+    var mParent = $(this).parent("dd");
+    mParent.find("input").val("");
+    mParent.find("textarea").val("");
+    mParent.find(".change-btn").click();
+  });
+
+  $(".cancel").click(function(){
+    var mParent = $(this).parents("dd");
+    $(this).hide();
+    mParent.children("form").slideUp(function(){
+      mParent.children(".current-value").show();
+      /* Show the "Not set" text if we ended up with no value */
+      if (!mParent.children(".current-value").html()){
+        mParent.children(".muted").fadeIn();
+        mParent.children(".delete-current-value").hide();
+      } else {
+        mParent.children(".delete-current-value").show();
+      }
+
+      mParent.children(".icon-pencil").show();
+      mParent.prev().css("margin-top", "0px");
+    });
+  });
+
+  $(".build-target-btn").click(function(){
+    /* fire a build */
+    var target = $(this).data('target-name');
+    libtoaster.startABuild(ctx.projectBuildUrl, ctx.projectId, target, null, null);
+    window.location.replace(ctx.projectPageUrl);
+  });
+
+  $(".select-machine-btn").click(function(){
+    var data =  { machineName : $(this).data('machine-name') };
+    libtoaster.editProject(ctx.xhrEditProjectUrl, ctx.projectId, data,
+      function (){
+        window.location.replace(ctx.projectPageUrl);
+    }, null);
+  });
+
+  function defaultAddBtnText(){
+      var text = " Add the "+ctx.layerVersion.name+" layer to your project";
+      addRmLayerBtn.text(text);
+      addRmLayerBtn.prepend("<span class=\"icon-plus\"></span>");
+      addRmLayerBtn.removeClass("btn-danger");
+  }
+
+  $("#details-tab").on('show', function(){
+    if (!ctx.layerVersion.inCurrentPrj)
+      defaultAddBtnText();
+
+    window.location.hash = "details";
+  });
+
+  function targetsTabShow(){
+    if (!ctx.layerVersion.inCurrentPrj){
+      if (ctx.numTargets > 0) {
+        var text = " Add the "+ctx.layerVersion.name+" layer to your project "+
+          "to enable these targets";
+        addRmLayerBtn.text(text);
+        addRmLayerBtn.prepend("<span class=\"icon-plus\"></span>");
+      } else {
+        defaultAddBtnText();
+      }
+    }
+
+    window.location.hash = "targets";
+  }
+
+  $("#targets-tab").on('show', targetsTabShow);
+
+  function machinesTabShow(){
+    if (!ctx.layerVersion.inCurrentPrj) {
+      if (ctx.numMachines > 0){
+        var text = " Add the "+ctx.layerVersion.name+" layer to your project " +
+          "to enable these machines";
+        addRmLayerBtn.text(text);
+        addRmLayerBtn.prepend("<span class=\"icon-plus\"></span>");
+      } else {
+        defaultAddBtnText();
+      }
+    }
+
+    window.location.hash = "machines";
+  }
+
+  $("#machines-tab").on('show', machinesTabShow);
+
+  $(".pagesize").change(function(){
+    var search = libtoaster.parseUrlParams();
+    search.limit = this.value;
+
+    window.location.search = libtoaster.dumpsUrlParams(search);
+  });
+
+  /* Enables the Build target and Select Machine buttons and switches the
+   * add/remove button
+   */
+  function setLayerInCurrentPrj(added, depsList) {
+    ctx.layerVersion.inCurrentPrj = added;
+    var alertMsg = $("#alert-msg");
+    /* Reset alert message */
+    alertMsg.text("");
+
+    if (added){
+      /* enable and switch all the button states */
+      $(".build-target-btn").removeAttr("disabled");
+      $(".select-machine-btn").removeAttr("disabled");
+      addRmLayerBtn.addClass("btn-danger");
+      addRmLayerBtn.data('directive', "remove");
+      addRmLayerBtn.text(" Delete the "+ctx.layerVersion.name+" layer from your project");
+      addRmLayerBtn.prepend("<span class=\"icon-trash\"></span>");
+
+      if (depsList) {
+        alertMsg.append("You have added <strong>"+(depsList.length+1)+"</strong> layers: <span id=\"layer-affected-name\"></span> and its dependencies ");
+
+        /* Build the layer deps list */
+        depsList.map(function(layer, i){
+          var link = $("<a></a>");
+
+          link.attr("href", layer.layerdetailurl);
+          link.text(layer.name);
+          link.tooltip({title: layer.tooltip});
+
+          if (i != 0)
+            alertMsg.append(", ");
+
+          alertMsg.append(link);
+        });
+      } else {
+        alertMsg.append("You have added <strong>1</strong> layer: <span id=\"layer-affected-name\"></span>");
+      }
+    } else {
+      /* disable and switch all the button states */
+      $(".build-target-btn").attr("disabled","disabled");
+      $(".select-machine-btn").attr("disabled", "disabled");
+      addRmLayerBtn.removeClass("btn-danger");
+      addRmLayerBtn.data('directive', "add");
+
+      /* "special" handler so that we get the correct button text which depends
+       * on which tab is currently visible. Unfortunately we can't just call
+       * tab('show') as if it's already visible it doesn't run the event.
+       */
+      switch ($(".nav-pills .active a").prop('id')){
+        case 'machines-tab':
+          machinesTabShow();
+          break;
+        case 'targets-tab':
+          targetsTabShow();
+          break;
+        default:
+          defaultAddBtnText();
+          break;
+      }
+
+      alertMsg.append("You have deleted <strong>1</strong> layer: <span id=\"layer-affected-name\"></span>");
+    }
+
+    alertMsg.children("#layer-affected-name").text(ctx.layerVersion.name);
+    $("#alert-area").show();
+  }
+
+  /* Add or remove this layer from the project */
+  addRmLayerBtn.click(function() {
+    var directive = $(this).data('directive');
+
+    if (directive == 'add') {
+      /* If adding get the deps for this layer */
+      libtoaster.getLayerDepsForProject(ctx.xhrDataTypeaheadUrl, ctx.projectId, ctx.layerVersion.id, function (data) {
+        /* got result for dependencies */
+        if (data.list.length == 0){
+          var editData = { layerAdd : ctx.layerVersion.id };
+          libtoaster.editProject(ctx.xhrEditProjectUrl, ctx.projectId, editData,
+            function() {
+              setLayerInCurrentPrj(true);
+          });
+          return;
+        } else {
+          /* The add deps will include this layer so no need to add it
+           * separately.
+           */
+          show_layer_deps_modal(ctx.projectId, ctx.layerVersion, data.list, null, null, true, function () {
+            /* Success add deps and layer */
+            setLayerInCurrentPrj(true, data.list);
+          });
+        }
+      }, null);
+    } else if (directive == 'remove') {
+      var editData = { layerDel : ctx.layerVersion.id };
+
+      libtoaster.editProject(ctx.xhrEditProjectUrl, ctx.projectId, editData,
+        function () {
+          /* Success removed layer */
+           //window.location.reload();
+           setLayerInCurrentPrj(false);
+        }, function () {
+          console.warn ("Removing layer from project failed");
+      });
+    }
+  });
+
+  /* Handler for all of the Change buttons */
+  $(".change-btn").click(function(){
+    var mParent = $(this).parent();
+    var prop = $(this).data('layer-prop');
+
+    /* We have inputs, select and textareas to potentially grab the value
+     * from.
+     */
+    var entryElement = mParent.find("input");
+    if (entryElement.length == 0)
+      entryElement = mParent.find("textarea");
+    if (entryElement.length == 0)
+      entryElement = mParent.find("select");
+    if (entryElement.length == 0) {
+      console.warn("Could not find element to get data from for this change");
+      return;
+    }
+
+    var data = { layer_version_id: ctx.layerVersion.id };
+    data[prop] = entryElement.val();
+
+    $.ajax({
+        type: "POST",
+        url: ctx.xhrUpdateLayerUrl,
+        data: data,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function (data) {
+          if (data.error != "ok") {
+            console.warn(data.error);
+          } else {
+            /* success layer property changed */
+            var inputArea = mParent.parents("dd");
+            var text;
+            /* We don't actually want the value from the select option we want
+             * the text that represents the value to display
+             */
+            text = entryElement.children("option:selected").text();
+            if (!text)
+              text = entryElement.val();
+
+            /* Hide the "Not set" text if it's visible */
+            inputArea.find(".muted").hide();
+            inputArea.find(".current-value").text(text);
+            /* Same behaviour as cancel in that we hide the form/show current
+             * value.
+             */
+            inputArea.find(".cancel").click();
+          }
+        },
+        error: function (data) {
+          console.warn("Call failed");
+          console.warn(data);
+        }
+    });
+  });
+
+  /* Disable the change button when we have no data in the input */
+  $("dl input, dl textarea").keyup(function() {
+    if ($(this).val().length == 0)
+      $(this).parent().children(".change-btn").attr("disabled", "disabled");
+    else
+      $(this).parent().children(".change-btn").removeAttr("disabled");
+  });
+
+  /* This checks to see if the dt's dd has data in it or if the change data
+   * form is visible, otherwise hide it
+   */
+  $("dl").children().each(function (){
+    if ($(this).is("dt")) {
+      var dd = $(this).next("dd");
+      if (!dd.children("form:visible")|| !dd.find(".current-value").html()){
+        if (ctx.layerVersion.sourceId == 3){
+        /* There's no current value and the layer is editable
+         * so show the "Not set" and hide the delete icon
+         */
+        dd.find(".muted").show();
+        dd.find(".delete-current-value").hide();
+        } else {
+          /* We're not viewing an editable layer so hide the empty dd/dl pair */
+          $(this).hide();
+          dd.hide();
+        }
+      }
+    }
+  });
+
+  /* Clear the current search selection and reload the results */
+  $("#target-search-clear").click(function(){
+    $("#target-search").val("");
+    $(this).parents("form").submit();
+  });
+
+  $("#machine-search-clear").click(function(){
+    $("#machine-search").val("");
+    $(this).parents("form").submit();
+  });
+
+
+  layerDepsList.find(".icon-trash").click(layerRemoveClick);
+  layerDepsList.find("a").tooltip();
+  $(".icon-trash").tooltip();
+  $(".commit").tooltip();
+
+}
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index a2a0abd..04264cd 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -161,6 +161,39 @@ var libtoaster = (function (){
     });
   };
 
+  /* parses the query string of the current window.location to an object */
+  function _parseUrlParams() {
+    string = window.location.search
+    string = string.substr(1);
+    stringArray = string.split ("&");
+    obj = {};
+
+    for (i in stringArray) {
+      keyVal = stringArray[i].split ("=");
+      obj[keyVal[0]] = keyVal[1];
+    }
+
+    return obj;
+  };
+
+  /* takes a flat object and outputs it as a query string
+   * e.g. the output of dumpsUrlParams
+   */
+  function _dumpsUrlParams(obj) {
+    var str = "?";
+
+    for (key in obj){
+      if (!obj[key])
+        continue;
+
+      str += key+ "="+obj[key].toString();
+      str += "&";
+    }
+
+    return str;
+  };
+
+
   return {
     reload_params : reload_params,
     startABuild : _startABuild,
@@ -169,6 +202,8 @@ var libtoaster = (function (){
     getLayerDepsForProject : _getLayerDepsForProject,
     editProject : _editProject,
     debug: false,
+    parseUrlParams : _parseUrlParams,
+    dumpsUrlParams : _dumpsUrlParams,
   }
 })();
 
diff --git a/lib/toaster/toastergui/templates/layerdetails.html b/lib/toaster/toastergui/templates/layerdetails.html
index 78dc54b..c69f9e9 100644
--- a/lib/toaster/toastergui/templates/layerdetails.html
+++ b/lib/toaster/toastergui/templates/layerdetails.html
@@ -1,159 +1,505 @@
 {% extends "baseprojectpage.html" %}
 {% load projecttags %}
 {% load humanize %}
-
+{% load static %}
 {% block localbreadcrumb %}
-<li>Layer Details</li>
+<li><a href="{% url 'layers' %}">All Layers</a></li>
+<li>
+  {{layerversion.layer.name}} ({{layerversion.commit|truncatechars:13}})
+</li>
 {% endblock %}
-
 {% block projectinfomain %}
-<div class="page-header">
-    <h1>Layer Details</h1>
-</div>
-
- <div class="row-fluid span7 tabbable">
-       <ul class="nav nav-pills">
-       <li class="active">
-           <a data-toggle="tab" href="#information">Layer details</a>
-       </li>
-       <li>
-           <a data-toggle="tab" href="#targets">Targets (0)</a>
-       </li>
-       <li>
-           <a data-toggle="tab" href="#machines">Machines (0)</a>
-       </li>
-       <li>
-           <a data-toggle="tab" href="#classes">Classes (0)</a>
-       </li>
-       <li>
-           <a data-toggle="tab" href="#bbappends">bbappends (0)</a>
-       </li>
-       </ul>
-       <div class="tab-content">
-               <div name="information" id="information" class="tab-pane active">
-        <dl class="dl-horizontal">
-        <dt class="">
-            <i class="icon-question-sign get-help" title="Fetch/clone URL of the repository"></i>
-            Repository URL
-        </dt>
-        <dd>
-            <form id="change-repo-form" class="control-group">
-            <div class="input-append">
-            <input type="text" class="input-xlarge" id="type-repo" value="{{layerversion.layer.vcs_url}}">
-            <button id="apply-change-repo" class="btn" type="button">Change</button>
-            <!--a href="#" id="cancel-change-repo" class="btn btn-link">Cancel</a-->
-            </div>
-            <span class="help-block">Cloning this Git repository failed</span>
-            </form>
-        </dd>
-        <dt>
-            <i class="icon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></i>
-            Repository subdirectory
-        </dt>
-        <dd>
-            <span id="subdir">{{layerversion.dirpath}}</span>
-            <i id="change-subdir" class="icon-pencil"></i>
-            <i id="delete-subdir" class="icon-trash"></i>
-            <form id="change-subdir-form" style="display:none;">
-            <div class="input-append">
-            <input type="text" id="type-subdir" value="meta-acer">
-            <button id="apply-change-subdir" class="btn" type="button">Change</button>
-            <a href="#" id="cancel-change-subdir" class="btn btn-link">Cancel</a>
-            </div>
-            </form>
-        </dd>
-        <dt>Brach, tag or commit</dt>
-        <dd>
-            {{layerversion.up_branch.name}}
-            <i class="icon-pencil"></i>
-        </dd>
-        <dt>
-            <i class="icon-question-sign get-help" title="The Yocto Project versions with which this layer is compatible. Currently Toaster supports Yocto Project 1.6 and 1.7"></i>
-            Yocto Project compatibility
-        </dt>
-        <dd>
-            <i class="icon-pencil"></i>
-        </dd>
-        <dt>
-            <i class="icon-question-sign get-help" title="Other layers this layer depends upon"></i>
-            Layer dependencies
-        </dt>
-        <dd>
-            <ul class="unstyled">
-            {% for ld in layer.dependencies.all %}
-            <li>
-                <a href="#">openembedded core (meta)</a>
-                <i class="icon-trash"></i>
-            </li>
-            {% endfor %}
-            </ul>
-            <div class="input-append">
-            <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off"
-            data-provide="typeahead"  data-source='
-            '
-            placeholder="Type a layer name" id="layer-dependency">
-            <a class="btn" type="button" id="add-layer-dependency" disabled>
-            Add layer
-            </a>
-            </div>
-            <span class="help-block">You can only add layers Toaster knows about</span>
-        </dd>
-        </dl>
-        </div>
-        <div name="targets" id="targets" class="tab-pane">
-        <div class="alert alert-info">
-        <strong>There is no target data for {{layerversion.layer.name}} ... yet</strong> <br />
-        Toaster learns about layers as they are built. Once you have used {{layerversion.layer.name}} in a build, Toaster will show you
-        here the targets it provides.
-        </div>
-        </div>
-        <div name="machines" id="machines" class="tab-pane">
-        <div class="alert alert-info">
-        <strong>There is no machine data for {{layerversion.layer.name}} ... yet</strong> <br />
-        Toaster learns about layers as they are built. Once you have used {{layerversion.layer.name}} in a build, Toaster will show you
-        here the machines it provides.
-        </div>
-        </div>
+
+
+<script src="{% static 'js/layerdetails.js' %}"></script>
+<script>
+
+  $(document).ready(function (){
+    var ctx = {
+      projectBuildUrl : "{% url 'xhr_build' %}",
+      layerDetailsUrl : "{% url 'layerdetails' %}",
+      projectPageUrl : "{% url 'project' project.id %}",
+      xhrEditProjectUrl : "{% url 'xhr_projectedit' project.id %}",
+      xhrDataTypeaheadUrl : "{% url 'xhr_datatypeahead' %}",
+      xhrUpdateLayerUrl : "{% url 'xhr_updatelayer' %}",
+      projectId : {{project.id}},
+      numTargets : {{total_targets}},
+      numMachines: {{machines|length}},
+      layerVersion : {
+        name : "{{layerversion.layer.name}}",
+        id : {{layerversion.id}},
+        commit: "{{layerversion.commit}}",
+        inCurrentPrj : {{layer_in_project}},
+        url : "{% url 'layerdetails' layerversion.id  %}",
+        sourceId: {{layerversion.layer_source_id}},
+      }
+    };
+
+    try {
+      layerDetailsPageInit(ctx);
+    } catch (e) {
+      document.write("Sorry, An error has occurred loading this page");
+      console.warn(e);
+    }
+  });
+</script>
+
+{# If this is not an imported layer then hide the edit ui #}
+{% if layerversion.layer_source_id != 3 %}
+<style>
+ .icon-pencil {
+   display:none;
+ }
+.delete-current-value{
+  display: none;
+}
+ li .icon-trash {
+   display:none;
+ }
+ .add-deps {
+   display:none;
+ }
+</style>
+{% endif %}
+
+{% include "layers_dep_modal.html" %}
+ <div class="container-fluid top-padded">
+  <div class="row-fluid">
+    <div class="span11">
+      <div class="page-header">
+        <h1>{{layerversion.layer.name}} <small class="commit" data-toggle="tooltip" title="{{layerversion.commit}}">({{layerversion.commit|truncatechars:13}})</small></h1>
+      </div>
     </div>
-</div>
-<div class="row span4 well">
-<h2>About {{layerversion.layer.name}}</h2>
-<dl>
-
-    <dt>
-    Summary
-    <i class="icon-question-sign get-help" title="One-line description of the layer"></i>
-    </dt>
-    <dd>
-    <span >{{layerversion.layer.summary}}</span>
-    <i class="icon-pencil"></i>
-    </dd>
-    <!--form>
-    <textarea class="span12" rows="2"></textarea>
-    <button class="btn" type="button">Change</button>
-    <a href="#" class="btn btn-link">Cancel</a>
-    </form-->
-    <dt>
-    Description
-    </dt>
-    <dd>
-    <span >{{layerversion.layer.description}}</span>
-    <i class="icon-pencil"></i>
-    </dd>
-    <!--form>
-    <textarea class="span12" rows="6"></textarea>
-    <button class="btn" type="button">Change</button>
-    <a href="#" class="btn btn-link">Cancel</a>
-    </form-->
-    <dt>
-    Maintainer(s)
-    </dt>
-    <dd>
-    <span class="muted">Not set</span>
-    <i class="icon-pencil"></i>
-    </dd>
-</dl>
-</div>
+  </div>
+
+    <div class="row-fluid">
+    <div class="span7">
+      <div class="tabbable">
+        <div class="alert alert-info lead" id="alert-area" style="display:none">
+          <button type="button" class="close" id="dismiss-alert" data-dismiss="alert">&times;</button>
+          <span id="alert-msg"></span>
+          <p style="margin-top:10px;"><a href="{% url 'project' project.id %}">Go to project configuration</a></p>
+        </div>
+        <ul class="nav nav-pills">
+          <li class="active">
+            <a data-toggle="tab" href="#information" id="details-tab">Layer details</a>
+          </li>
+          <li>
+            <a data-toggle="tab" href="#targets" id="targets-tab">Targets ({{total_targets}})</a>
+          </li>
+          <li>
+            <a data-toggle="tab" href="#machines" id="machines-tab">Machines ({{total_machines}})</a>
+          </li>
+        </ul>
+      </div>
+      <div class="tab-content">
+        <span class="button-place">
+          {% if layer_in_project == 0 %}
+          <button id="add-remove-layer-btn" data-directive="add" class="btn btn-large btn-block">
+            <span class="icon-plus"></span>
+            Add the {{layerversion.layer.name}} layer to your project
+          </button>
+          {% else %}
+          <button id="add-remove-layer-btn" data-directive="remove" class="btn btn-block btn-large btn-danger">
+            <span class="icon-trash"></span>
+            Delete the {{layerversion.layer.name}} layer from your project
+          </button>
+          {% endif %}
+        </span>
+
+        <!-- layer details pane -->
+        <div name="information" id="information" class="tab-pane active">
+          <dl class="dl-horizontal">
+            <dt class="">
+              <i class="icon-question-sign get-help" title="Fetch/clone URL of the repository"></i>
+              Repository URL
+            </dt>
+            <dd>
+              <span class="current-value">{{layerversion.layer.vcs_url}}</span>
+              {% if layerversion.get_vcs_link_url %}
+                <a href="{{layerversion.get_vcs_link_url}}/" class="icon-share get-info"></a>
+              {% endif %}
+              <form id="change-repo-form" class="control-group" style="display:none">
+                <div class="input-append">
+                  <input type="text" class="input-xlarge" value="{{layerversion.layer.vcs_url}}">
+                    <button data-layer-prop="vcs_url" class="btn change-btn" type="button">Save</button>
+                    <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
+                  </div>
+                </form>
+                <i class="icon-pencil" ></i>
+              </dd>
+              <dt>
+                <i class="icon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></i>
+                Repository subdirectory
+              </dt>
+              <dd>
+                <span class="muted" style="display:none">Not set</span>
+                <span class="current-value">{{layerversion.dirpath}}</span>
+                {% if layerversion.get_vcs_dirpath_link_url %}
+                  <a href="{{layerversion.get_vcs_dirpath_link_url}}" class="icon-share get-info"></a>
+                {% endif %}
+                <form id="change-subdir-form" style="display:none;">
+                  <div class="input-append">
+                    <input type="text" value="{{layerversion.dirpath}}">
+                      <button data-layer-prop="dirpath" class="btn change-btn" type="button">Save</button>
+                      <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
+                    </div>
+                  </form>
+                  <i id="change-subdir" class="icon-pencil"></i>
+                  <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
+                </dd>
+                <dt>Brach, tag or commit</dt>
+                <dd>
+                  <span class="current-value">{{layerversion.commit}}</span>
+                  <form style="display:none;">
+                    <div class="input-append">
+                      <input type="text" value="{{layerversion.commit}}">
+                        <button  data-layer-prop="commit" class="btn change-btn" type="button">Save</button>
+                        <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
+                      </div>
+                    </form>
+                    <i class="icon-pencil"></i>
+                  </dd>
+                  <dt>
+                    <i class="icon-question-sign get-help" title="The Yocto Project versions with which this layer is compatible. Currently Toaster supports Yocto Project 1.6 and 1.7"></i>
+                    Yocto Project compatibility
+                  </dt>
+                  <dd>
+                    <span class="current-value">{{layerversion.up_branch.name}}</span>
+                    <form style="display:none">
+                      <div class="input-append">
+                        <select name="projectversion" id="projectversion">
+                          {% for compat in yocto_compat %}
+                          <option value="{{compat.id}}" {%if layerversion.up_branch.id == compat.id %} selected{%endif%}>{{compat.name}}</option>
+                          {% endfor %}
+                        </select>
+                        <button  data-layer-prop="up_branch" class="btn change-btn" type="button">Save</button>
+                        <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
+                      </div>
+                    </form>
+                    <i class="icon-pencil"></i>
+                  </dd>
+                  <dt>
+                    <i class="icon-question-sign get-help" title="Other layers this layer depends upon"></i>
+                    Layer dependencies
+                  </dt>
+                  <dd>
+                    <ul class="unstyled" id="layer-deps-list">
+                      {% for ld in layerversion.dependencies.all %}
+                      <span class="current-value">
+                      <li data-layer-id="{{ld.depends_on.id}}">
+                        <!-- TODO use ld.depends_on.get_vcs_reference instead of commit -->
+                        <a data-toggle="tooltip" title="{{ld.depends_on.layer.vcs_url}} | {{ld.depends_on.commit}}" href="{% url 'layerdetails' ld.depends_on.id %}">{{ld.depends_on.layer.name}}</a>
+                        <span class="icon-trash " data-toggle="tooltip" title="Delete"></span>
+                      </li>
+                      </span>
+                      {% endfor %}
+                    </ul>
+                    <div class="input-append add-deps">
+                      <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off"  placeholder="Type a layer name" id="layer-dep-input">
+                        <a class="btn" type="button" id="add-layer-dependency-btn" disabled>
+                          Add layer
+                        </a>
+                      </div>
+                      <span class="help-block add-deps">You can only add layers Toaster knows about</span>
+                    </dd>
+                  </dl>
+                </div>
+                <!-- targets tab -->
+                <div name="targets" id="targets" class="tab-pane">
+                  {% if total_targets == 0 %}
+                  <div class="alert alert-info">
+                    <strong>There is no target data for {{layerversion.layer.name}} ... yet</strong> <br />
+                    Toaster learns about layers as they are built. Once you have used {{layerversion.layer.name}} in a build, Toaster will show you
+                    here the targets it provides.
+                  </div>
+                  {% else %}
+
+                  <div class="row-fluid">
+
+                  {% if targets.paginator.count == 0 %}
+                  <div class="alert">
+                    <h3>No targets found</h3>
+                  {% endif %}
+
+                {# only show the search form if we have more than 10 results #}
+                  {% if targets.paginator.count > 10 or request.GET.targets_search %}
+                    {% if targets.paginator.count == 0 %}
+                      <form class="input-append">
+                    {% else %}
+                      <form class="navbar-search input-append pull-left">
+                    {% endif %}
+
+                      <input type="text" id="target-search" name="targets_search" placeholder="Search targets" class="input-xlarge" value="{{request.GET.targets_search}}">
+                        {% if request.GET.targets_search %}
+                        <a class="add-on btn" id="target-search-clear">
+                          <i class="icon-remove"></i>
+                        </a>
+                        {% endif %}
+                        <button type="submit" class="btn">Search</button>
+                      </form>
+                    {% endif %}
+
+                    {% if targets.paginator.count == 0 %}
+                      <!-- end alert -->
+                     </div>
+                     <!-- end row-fluid -->
+                     </div>
+                    {% else %}
+
+                    <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 "10 25 50 100 150" as list%}
+                        {% for i in list.split %}
+                        {% if request.session.limit == i %}
+                        <option value="{{i}}" selected>{{i}}</option>
+                        {% else %}
+                        <option value="{{i}}">{{i}}</option>
+                        {% endif %}
+                        {% endfor %}
+                      {% endwith %}
+                    </select>
+                  </div>
+                </div>
+
+                <table class="table table-bordered table-hover">
+                  <thead>
+                    <tr>
+                      <th>
+                          <i class="icon-question-sign get-help" title="Information about a single piece of software, including where to download the source, configuration options, how to compile the source files and how to package the compiled output"></i>
+                          Target
+                          {% if request.GET.targets_search %}
+                            <span class="badge badge-info">{{targets.paginator.count}}</span>
+                          {% endif %}
+                        </th>
+                        <th>
+                          <i class="icon-question-sign get-help" title="The recipe version and revision"></i>
+                          Target version
+                        </th>
+                        <th class="span4">Description</th>
+                        <th>Build target</th>
+                      </tr>
+                    </thead>
+                    <tbody>
+                      {% for target in targets %}
+                      <tr>
+                        <td>
+                          {{target.name}}
+                          {% if target.up_id %}
+                          <a href="http://layers.openembedded.org/layerindex/recipe/{{target.up_id}}/" class="icon-share get-info"></a>
+                          {% endif %}
+                        </td>
+                        <td>{{target.version}}</td>
+                        <td>{{target.summary}}</td>
+                        <td><button class="btn btn-block build-target-btn" data-target-name="{{target.name}}" {% if layer_in_project == 0 %}disabled="disabled"{% endif %} >Build Target</button></td>
+                      </tr>
+                      {% endfor %}
+                    </tbody>
+                  </table>
+
+                  <!-- Show pagination controls -->
+                  <div class="pagination">
+                    <ul>
+                      {%if targets.has_previous %}
+                      <li><a href="?tpage={{targets.previous_page_number}}{{request.GET.limit}}#targets">&laquo;</a></li>
+                      {%else%}
+                      <li class="disabled"><a href="#">&laquo;</a></li>
+                      {%endif%}
+                      {% for i in targets.paginator.page_range %}
+                      <li {%if i == targets.number %} class="active" {%endif%}><a href="?tpage={{i}}#targets">{{i}}</a></li>
+                      {% endfor %}
+                      {%if targets.has_next%}
+                      <li><a href="?tpage={{targets.next_page_number}}#targets">&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 "10 25 50 100 150" as list%}
+                        {% for i in list.split %}
+                        {% if request.session.limit == i %}
+                        <option value="{{i}}" selected>{{i}}</option>
+                        {% else %}
+                        <option value="{{i}}">{{i}}</option>
+                        {% endif %}
+                        {% endfor %}
+                        {% endwith %}
+                      </select>
+                    </div>
+                  </div>
+                  {% endif %}
+                  {% endif %}
+                </div>
+
+
+                <div name="machines" id="machines" class="tab-pane">
+                  {% if total_machines == 0 %}
+                  <div class="alert alert-info">
+                    <strong>There is no machine data for {{layerversion.layer.name}} ... yet</strong> <br />
+                    Toaster learns about layers as they are built. Once you have used {{layerversion.layer.name}} in a build, Toaster will show you
+                    here the machines it provides.
+                  </div>
+                  {% else %}
+
+                  <div class="row-fluid">
+
+                  {% if machines.paginator.count == 0 %}
+                  <div class="alert">
+                    <h3>No machines found</h3>
+                  {% endif %}
+
+                {# only show the search form if we have more than 10 results #}
+                  {% if machines.paginator.count > 10 or request.GET.machines_search %}
+                    {% if machines.paginator.count == 0 %}
+                      <form class="input-append">
+                    {% else %}
+                      <form class="navbar-search input-append pull-left">
+                    {% endif %}
+
+                      <input type="text" id="machine-search" name="machines_search" placeholder="Search machines" class="input-xlarge" value="{{request.GET.machines_search}}">
+                        {% if request.GET.machines_search %}
+                        <a class="add-on btn" id="machine-search-clear">
+                          <i class="icon-remove"></i>
+                        </a>
+                        {% endif %}
+                        <button type="submit" class="btn">Search</button>
+                      </form>
+                    {% endif %}
+
+                    {% if machines.paginator.count == 0 %}
+                      <!-- end alert -->
+                     </div>
+                     <!-- end row-fluid -->
+                     </div>
+                    {% else %}
+
+                    <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 "10 25 50 100 150" as list%}
+                        {% for i in list.split %}
+                        {% if request.session.limit == i %}
+                        <option value="{{i}}" selected>{{i}}</option>
+                        {% else %}
+                        <option value="{{i}}">{{i}}</option>
+                        {% endif %}
+                        {% endfor %}
+                      {% endwith %}
+                    </select>
+                  </div>
+                </div>
+
+                <table class="table table-bordered table-hover">
+                  <thead>
+                      <tr>
+                        <th>
+                          <i class="icon-question-sign get-help" title="The machine is the hardware for which you are building"></i>
+                          Machine
+                          {% if request.GET.machines_search %}
+                          <span class="badge badge-info">{{machines.paginator.count}}</span>
+                          {% endif %}
+                        </th>
+                        <th>Description</th>
+                        <th>Select machine</th>
+                      </tr>
+                    </thead>
+                    <tbody>
+                      {% for machine in machines %}
+                      <tr>
+                        <td>{{machine.name}}</td>
+                        <td>{{machine.description}}</td>
+                        <td><button class="btn btn-block select-machine-btn" data-machine-name="{{machine.name}}" {% if layer_in_project == 0 %}disabled="disabled"{% endif %}}>Select machine</button></td>
+                      </tr>
+                      {% endfor %}
+                    </tbody>
+                  </table>
+
+                  <!-- Show pagination controls -->
+                  <div class="pagination">
+                    <ul>
+                      {%if machines.has_previous %}
+                      <li><a href="?mpage={{machines.previous_page_number}}{{request.GET.limit}}#machines">&laquo;</a></li>
+                      {%else%}
+                      <li class="disabled"><a href="#">&laquo;</a></li>
+                      {%endif%}
+                      {% for i in machines.paginator.page_range %}
+                      <li {%if i == machines.number %} class="active" {%endif%}><a href="?mpage={{i}}#machines">{{i}}</a></li>
+                      {% endfor %}
+                      {%if machines.has_next%}
+                      <li><a href="?mpage={{machines.next_page_number}}#machines">&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 "10 25 50 100 150" as list%}
+                        {% for i in list.split %}
+                        {% if request.session.limit == i %}
+                        <option value="{{i}}" selected>{{i}}</option>
+                        {% else %}
+                        <option value="{{i}}">{{i}}</option>
+                        {% endif %}
+                        {% endfor %}
+                        {% endwith %}
+                      </select>
+                    </div>
+                  </div>
+                  {% endif %}
+                  {% endif %}
+                  </div>
+              </div>
+            </div>
+            <div class="row span4 well">
+              <h2>About {{layerversion.layer.name}}</h2>
+              <dl>
+
+                <dt>
+                  Summary
+                  <i class="icon-question-sign get-help" title="One-line description of the layer"></i>
+                </dt>
+                <dd>
+                  <span class="muted" style="display:none">Not set</span>
+                  <span class="current-value">{{layerversion.layer.summary}}</span>
+                  <form style="display:none; margin-bottom:20px">
+                    <textarea class="span12" rows="2">{% if layerversion.layer.summary %}{{layerversion.layer.summary}}{% endif %}</textarea>
+                    <button class="btn change-btn" data-layer-prop="summary" type="button">Save</button>
+                    <a href="#" class="btn btn-link cancel">Cancel</a>
+                  </form>
+                  <i class="icon-pencil"></i>
+                  <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
+                </dd>
+                <dt>
+                  Description
+                </dt>
+                <dd>
+                  <span class="muted" style="display:none">Not set</span>
+                  <span class="current-value">{{layerversion.layer.description}}</span>
+                  <form style="display:none; margin-bottom:20px">
+                    <textarea class="span12" rows="6">{% if layerversion.layer.description %}{{layerversion.layer.description}}{% endif %}</textarea>
+                    <button class="btn change-btn" data-layer-prop="description" type="button" >Save</button>
+                    <a href="#" class="btn btn-link cancel">Cancel</a>
+                  </form>
+                  <i class="icon-pencil"></i>
+                  <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
+                </dd>
+                </dd>
+                {% if layerversion.layer.up_id %}
+                <dt>Layer index</dt>
+                <dd>
+                  <a href="http://layers.openembedded.org/layerindex/branch/{{layerversion.up_branch.name}}/layer/{{layerversion.layer.name}}"/>layer index link</a>
+
+                </dd>
+                {% endif %}
 
+           </dl>
+         </div>
 
+          </div>
+       </div>
+   </div>
 {% endblock %}
diff --git a/lib/toaster/toastergui/templates/layers_dep_modal.html b/lib/toaster/toastergui/templates/layers_dep_modal.html
index b03fd0b..8222027 100644
--- a/lib/toaster/toastergui/templates/layers_dep_modal.html
+++ b/lib/toaster/toastergui/templates/layers_dep_modal.html
@@ -18,7 +18,16 @@
     </div>
 
 <script>
+  /* projectId: current project
+   * layer: Object representing the parent layer { id: .. name: ... url }
+   * dependencies: array of dependency layer objects { id: .. name: ..}
+   * title: optional override for title
+   * body: optional override for body
+   * addToProject: Whether to add layers to project on accept
+   * successAdd: function to run on success
+   */
 function show_layer_deps_modal(projectId, layer, dependencies, title, body, addToProject, successAdd) {
+
     // update layer name
     if (title) {
       $('#dependencies_modal #title').text(title);
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index 6e1b0ab..5c969f8 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -94,6 +94,7 @@ urlpatterns = patterns('toastergui.views',
 
         url(r'^xhr_datatypeahead/$', 'xhr_datatypeahead', name='xhr_datatypeahead'),
         url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'),
+        url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'),
 
 
         # default redirection
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 8c21ca4..7a9d662 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -2336,6 +2336,50 @@ if toastermain.settings.MANAGED:
 
         return HttpResponse(jsonfilter({"error": "ok", "imported_layer" : { "name" : layer.name, "id": layer_version.id },  "deps_added": layers_added }), content_type = "application/json")
 
+    def xhr_updatelayer(request):
+
+        def error_response(error):
+            return HttpResponse(jsonfilter({"error": error}), content_type = "application/json")
+
+        if not request.POST.has_key("layer_version_id"):
+            return error_response("Please specify a layer version id")
+        try:
+            layer_version_id = request.POST["layer_version_id"]
+            layer_version = Layer_Version.objects.get(id=layer_version_id)
+        except:
+            return error_response("Cannot find layer to update")
+
+
+        if request.POST.has_key("vcs_url"):
+            layer_version.layer.vcs_url = request.POST["vcs_url"]
+        if request.POST.has_key("dirpath"):
+            layer_version.dirpath = request.POST["dirpath"]
+        if request.POST.has_key("commit"):
+            layer_version.commit = request.POST["commit"]
+        if request.POST.has_key("up_branch"):
+            layer_version.up_branch_id = int(request.POST["up_branch"])
+
+        if request.POST.has_key("add_dep"):
+            lvd = LayerVersionDependency(layer_version=layer_version, depends_on_id=request.POST["add_dep"])
+            lvd.save()
+
+        if request.POST.has_key("rm_dep"):
+            rm_dep = LayerVersionDependency.objects.get(layer_version=layer_version, depends_on_id=request.POST["rm_dep"])
+            rm_dep.delete()
+
+        if request.POST.has_key("summary"):
+            layer_version.layer.summary = request.POST["summary"]
+        if request.POST.has_key("description"):
+            layer_version.layer.description = request.POST["description"]
+
+        try:
+            layer_version.layer.save()
+            layer_version.save()
+        except:
+            return error_response("Could not update layer version entry")
+
+        return HttpResponse(jsonfilter({"error": "ok",}), content_type = "application/json")
+
 
 
     def importlayer(request):
@@ -2439,8 +2483,53 @@ if toastermain.settings.MANAGED:
 
     def layerdetails(request, layerid):
         template = "layerdetails.html"
+        limit = 10
+
+        if request.GET.has_key("limit"):
+            request.session['limit'] = request.GET['limit']
+
+        if request.session.has_key('limit'):
+            limit = request.session['limit']
+
+        layer_version = Layer_Version.objects.get(pk = layerid)
+
+        # Targets tab query functionality
+        if request.GET.has_key('targets_search'):
+            targets = Paginator(Recipe.objects.filter(layer_version=layer_version,name__icontains=request.GET['targets_search']).order_by("name"), limit)
+        else:
+            targets = Paginator(Recipe.objects.filter(layer_version=layer_version).order_by("name"), limit)
+
+        if request.GET.has_key("tpage"):
+          try:
+              targets = targets.page(request.GET['tpage'])
+          except EmptyPage:
+              targets = targets.page(targets.num_pages)
+        else:
+            targets = targets.page(1)
+
+        # Machines tab query functionality
+        if request.GET.has_key('machines_search'):
+          machines = Paginator(Machine.objects.filter(layer_version=layer_version,name__icontains=request.GET['machines_search']).order_by("name"), limit)
+        else:
+          machines = Paginator(Machine.objects.filter(layer_version=layer_version).order_by("name"), limit)
+
+        if request.GET.has_key("mpage"):
+          try:
+            machines = machines.page(request.GET['mpage'])
+          except EmptyPage:
+            machines = machines.page(machines.num_pages)
+        else:
+            machines = machines.page(1)
+
         context = {
-            'layerversion': Layer_Version.objects.get(pk = layerid),
+            'layerversion': layer_version,
+            'layer_in_project' : ProjectLayer.objects.filter(project_id=request.session['project_id'],layercommit=layerid).count(),
+            'yocto_compat': Branch.objects.filter(layer_source=layer_version.layer_source),
+            'machines': machines,
+            'targets': targets,
+            'total_targets': Recipe.objects.filter(layer_version=layer_version).count(),
+
+            'total_machines': Machine.objects.filter(layer_version=layer_version).count(),
         }
         return render(request, template, context)
 
@@ -2972,3 +3061,6 @@ else:
 
     def xhr_importlayer(request):
         raise Exception("page not available in interactive mode")
+
+    def xhr_updatelayer(request):
+        raise Exception("page not available in interactive mode")
-- 
1.9.1



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

* [PATCH 2/4] toaster: layerdetails Remove compatibility setting
  2015-01-14 12:46 [PATCH 0/4] Toaster feature / bugfix patchset Alex DAMIAN
  2015-01-14 12:46 ` [PATCH 1/4] toaster: Add layer details page feature Alex DAMIAN
@ 2015-01-14 12:46 ` Alex DAMIAN
  2015-01-14 12:46 ` [PATCH 3/4] toastergui: all builds page lists failed build requests Alex DAMIAN
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Alex DAMIAN @ 2015-01-14 12:46 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

Remove the layer compatibility configuration option from the layer
details as this is not in the design.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 lib/toaster/toastergui/static/js/layerdetails.js   | 10 ++--------
 lib/toaster/toastergui/templates/layerdetails.html | 21 +--------------------
 lib/toaster/toastergui/views.py                    |  1 -
 3 files changed, 3 insertions(+), 29 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/layerdetails.js b/lib/toaster/toastergui/static/js/layerdetails.js
index a5a6330..41cbf4b 100644
--- a/lib/toaster/toastergui/static/js/layerdetails.js
+++ b/lib/toaster/toastergui/static/js/layerdetails.js
@@ -309,8 +309,6 @@ function layerDetailsPageInit (ctx) {
     var entryElement = mParent.find("input");
     if (entryElement.length == 0)
       entryElement = mParent.find("textarea");
-    if (entryElement.length == 0)
-      entryElement = mParent.find("select");
     if (entryElement.length == 0) {
       console.warn("Could not find element to get data from for this change");
       return;
@@ -331,12 +329,8 @@ function layerDetailsPageInit (ctx) {
             /* success layer property changed */
             var inputArea = mParent.parents("dd");
             var text;
-            /* We don't actually want the value from the select option we want
-             * the text that represents the value to display
-             */
-            text = entryElement.children("option:selected").text();
-            if (!text)
-              text = entryElement.val();
+
+            text = entryElement.val();
 
             /* Hide the "Not set" text if it's visible */
             inputArea.find(".muted").hide();
diff --git a/lib/toaster/toastergui/templates/layerdetails.html b/lib/toaster/toastergui/templates/layerdetails.html
index c69f9e9..5bed0f8 100644
--- a/lib/toaster/toastergui/templates/layerdetails.html
+++ b/lib/toaster/toastergui/templates/layerdetails.html
@@ -160,26 +160,7 @@
                     </form>
                     <i class="icon-pencil"></i>
                   </dd>
-                  <dt>
-                    <i class="icon-question-sign get-help" title="The Yocto Project versions with which this layer is compatible. Currently Toaster supports Yocto Project 1.6 and 1.7"></i>
-                    Yocto Project compatibility
-                  </dt>
-                  <dd>
-                    <span class="current-value">{{layerversion.up_branch.name}}</span>
-                    <form style="display:none">
-                      <div class="input-append">
-                        <select name="projectversion" id="projectversion">
-                          {% for compat in yocto_compat %}
-                          <option value="{{compat.id}}" {%if layerversion.up_branch.id == compat.id %} selected{%endif%}>{{compat.name}}</option>
-                          {% endfor %}
-                        </select>
-                        <button  data-layer-prop="up_branch" class="btn change-btn" type="button">Save</button>
-                        <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
-                      </div>
-                    </form>
-                    <i class="icon-pencil"></i>
-                  </dd>
-                  <dt>
+                 <dt>
                     <i class="icon-question-sign get-help" title="Other layers this layer depends upon"></i>
                     Layer dependencies
                   </dt>
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 7a9d662..e414b66 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -2524,7 +2524,6 @@ if toastermain.settings.MANAGED:
         context = {
             'layerversion': layer_version,
             'layer_in_project' : ProjectLayer.objects.filter(project_id=request.session['project_id'],layercommit=layerid).count(),
-            'yocto_compat': Branch.objects.filter(layer_source=layer_version.layer_source),
             'machines': machines,
             'targets': targets,
             'total_targets': Recipe.objects.filter(layer_version=layer_version).count(),
-- 
1.9.1



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

* [PATCH 3/4] toastergui: all builds page lists failed build requests
  2015-01-14 12:46 [PATCH 0/4] Toaster feature / bugfix patchset Alex DAMIAN
  2015-01-14 12:46 ` [PATCH 1/4] toaster: Add layer details page feature Alex DAMIAN
  2015-01-14 12:46 ` [PATCH 2/4] toaster: layerdetails Remove compatibility setting Alex DAMIAN
@ 2015-01-14 12:46 ` Alex DAMIAN
  2015-01-14 12:46 ` [PATCH 4/4] toasterui: fix variable data error Alex DAMIAN
  2015-01-14 18:04 ` [PATCH 0/4] Toaster feature / bugfix patchset akuster808
  4 siblings, 0 replies; 6+ messages in thread
From: Alex DAMIAN @ 2015-01-14 12:46 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

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

This patch modifies the all builds page by splitting the page
into two variants - the "interactive" (default) and "managed" mode
versions.

In the "managed" mode version, we display build requests instead of
builds, including the failed build requests that have no build
associated with them.

[YOCTO #6671]

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 .../toastergui/templates/managed_builds.html       | 135 +++++
 .../toastergui/templates/managed_mrb_section.html  | 172 ++++++
 lib/toaster/toastergui/templatetags/projecttags.py |  19 +
 lib/toaster/toastergui/views.py                    | 615 ++++++++++++++-------
 4 files changed, 742 insertions(+), 199 deletions(-)
 create mode 100644 lib/toaster/toastergui/templates/managed_builds.html
 create mode 100644 lib/toaster/toastergui/templates/managed_mrb_section.html

diff --git a/lib/toaster/toastergui/templates/managed_builds.html b/lib/toaster/toastergui/templates/managed_builds.html
new file mode 100644
index 0000000..5944dc4
--- /dev/null
+++ b/lib/toaster/toastergui/templates/managed_builds.html
@@ -0,0 +1,135 @@
+{% extends "base.html" %}
+
+{% load static %}
+{% load projecttags %}
+{% load humanize %}
+
+{% block pagecontent %}
+<div class="row-fluid">
+
+  {% include "managed_mrb_section.html" %}
+
+
+  {% if 1 %}
+  <div class="page-header top-air">
+     <h1>
+      {% if request.GET.filter and objects.paginator.count > 0 or request.GET.search and objects.paginator.count > 0 %}
+          {{objects.paginator.count}} build{{objects.paginator.count|pluralize}} found
+      {%elif request.GET.filter and objects.paginator.count == 0 or request.GET.search and objects.paginator.count == 0 %}
+          No builds found
+      {%else%}
+          All builds
+      {%endif%}
+     </h1>
+  </div>
+
+  {% if objects.paginator.count == 0 %}
+    <div class="row-fluid">
+      <div class="alert">
+        <form class="no-results input-append" id="searchform">
+            <input id="search" name="search" class="input-xxlarge" type="text" value="{{request.GET.search}}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %}
+            <button class="btn" type="submit" value="Search">Search</button>
+            <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all builds</button>
+        </form>
+      </div>
+    </div>
+
+
+  {% else %}
+  {% include "basetable_top_buildprojects.html" %}
+        <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work -->
+        {% for br in objects %}{% if br.build %}  {% with build=br.build %} {# if we have a build, just display it #}
+        <tr class="data">
+            <td class="outcome"><a href="{% url "builddashboard" 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 %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
+            <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
+            <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
+            <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td>
+            <td class="failed_tasks error">
+                {% query build.task_build outcome=4 order__gt=0 as exectask%}
+                    {% if exectask.count == 1 %}
+                        <a href="{% url "task" build.id exectask.0.id %}">{{exectask.0.recipe.name}}.{{exectask.0.task_name}}</a>
+                      {% if MANAGED and build.project %}
+                        <a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}">
+                            <i class="icon-download-alt" title="" data-original-title="Download task log file"></i>
+                        </a>
+                      {% endif %}
+                    {% elif exectask.count > 1%}
+                        <a href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}} task{{exectask.count|pluralize}}</a>
+                    {%endif%}
+            </td>
+            <td class="errors_no">
+                {% if  build.errors_no %}
+                    <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
+                    {% if MANAGED and build.project %}
+                        <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
+                            <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
+                        </a>
+                    {% endif %}
+                {%endif%}
+            </td>
+            <td class="warnings_no">{% if  build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
+            <td class="time"><a href="{% url "buildtime" build.id %}">{{build.timespent|sectohms}}</a></td>
+            {% if not MANAGED or not build.project %}
+                <td class="log">{{build.cooker_log_path}}</td>
+            {% endif %}
+            <td class="output">
+              {% if build.outcome == build.SUCCEEDED %}
+              <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a>
+              {% endif %}
+            </td>
+   {% if MANAGED %}
+            <td class="project">
+            {% if build.project %}
+                <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a>
+            {% endif %}
+            </td>
+   {% endif %}
+        </tr>
+
+
+              {%endwith%}
+      {% else %} {# we don't have a build for this build request, mask the data with build request data #}
+
+
+
+        <tr class="data">
+            <td class="outcome">{% if buildrequest.state == buildrequest.REQ_FAILED %}<i class="icon-minus-sign error"></i>{%else%}FIXME_build_request_state{%endif%}</td>
+            <td class="target">
+                <span data-toggle="tooltip" {%if br.brtarget_set.all.count > 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
+            </td>
+            <td class="machine">
+                {{br.machine}}
+            </td>
+            <td class="started_on">
+                {{br.created|date:"d/m/y H:i"}}
+            </td>
+            <td class="completed_on">
+                {{br.updated|date:"d/m/y H:i"}}
+            </td>
+            <td class="failed_tasks error">
+                {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}}
+            </td>
+            <td class="errors_no">
+            </td>
+            <td class="warnings_no">
+            </td>
+            <td class="time">
+                {{br.timespent.total_seconds|sectohms}}
+            </td>
+            <td class="output"> {# we have no output here #}
+            </td>
+            <td class="project">
+                <a href="{% url 'project' br.project.id %}">{{br.project.name}}</a>
+            </td>
+        </tr>
+          {%endif%}
+        {% endfor %}
+
+
+  {% include "basetable_bottom.html" %}
+  {% endif %} {# objects.paginator.count #}
+{% endif %} {# empty #}
+</div><!-- end row-fluid-->
+
+{% endblock %}
diff --git a/lib/toaster/toastergui/templates/managed_mrb_section.html b/lib/toaster/toastergui/templates/managed_mrb_section.html
new file mode 100644
index 0000000..d4959a0
--- /dev/null
+++ b/lib/toaster/toastergui/templates/managed_mrb_section.html
@@ -0,0 +1,172 @@
+{% load static %}
+{% load projecttags %}
+{% load humanize %}
+
+
+{%if mru.count > 0%}
+
+  <div class="page-header top-air">
+      <h1>
+          Latest builds
+       </h1>
+  </div>
+  <div id="latest-builds">
+  {% for buildrequest in mru %}{% with build=buildrequest.build %}
+
+  {% if build %} {# if we have a build, just display it #}
+
+    <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%} {% if MANAGED and build.project %}project-name{% endif %} ">
+    {% if MANAGED and build.project %}
+       <span class="label {%if build.outcome == build.SUCCEEDED%}label-success{%elif build.outcome == build.FAILED%}label-danger{%else%}label-info{%endif%}"> {{build.project.name}} </span>
+    {% endif %}
+
+        <div class="row-fluid">
+            <div class="span3 lead">
+    {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
+                <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}">
+    {% endif %}
+                <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%}
+		        </span>
+    {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
+                </a>
+    {% endif %}
+            </div>
+            <div class="span2 lead">
+                {% if build.completed_on|format_build_date  %}
+                    {{ build.completed_on|date:'d/m/y H:i' }}
+                {% else %}
+                    {{ build.completed_on|date:'H:i' }}
+                {% endif %}
+            </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%}#errors" 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%}#warnings" class="warning">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>
+      {% endif %}
+            </div>
+            <div class="lead ">
+              <span class="lead{%if not MANAGED or not build.project%} pull-right{%endif%}">
+                Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a>
+              </span>
+          {% if MANAGED and build.project %}
+              <a class="btn {%if build.outcome == build.SUCCEEDED%}btn-success{%elif build.outcome == build.FAILED%}btn-danger{%else%}btn-info{%endif%} pull-right" onclick="scheduleBuild({% url 'xhr_projectbuild' build.project.id as bpi%}{{bpi|json}}, {{build.project.name|json}}, {{build.get_sorted_target_list|mapselect:'target'|json}})">Run again</a>
+          {% endif %}
+            </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>
+
+  {% else %}  {# we use the project's page recent build design #}
+
+  <div class="alert {% if buildrequest.state  == buildrequest.REQ_FAILED %}alert-error{% else %}alert-info{% endif %}">
+    <div class="row-fluid">
+
+
+          {% if buildrequest.state == buildrequest.REQ_FAILED %}
+            <div class="lead span3">
+                <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
+            </div>
+            <div >
+            </div>
+            <div class="row-fluid">
+              {% for e in buildrequest.brerror_set.all|slice:":3" %}
+              <div class="air well">
+                    <pre>{{e.errmsg|whitespace_slice:":150"}}</pre>
+              </div>
+              {% endfor %}
+            </div>
+
+         {% elif buildrequest.state == buildrequest.REQ_QUEUED %}
+
+            <div class="lead span5">
+
+                <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
+            </div>
+            <div class="span4 lead" >Build queued
+              <i title="This build will start as soon as a build server is available" class="icon-question-sign get-help get-help-blue heading-help" data-toggle="tooltip"></i>
+            </div>
+
+         {% elif buildrequest.state == buildrequest.REQ_CREATED %}
+
+            <div class="lead span3">
+                <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
+            </div>
+            <div class="span6" >
+              <span class="lead">Creating build</span>
+            </div>
+
+         {% elif buildrequest.state == buildrequest.REQ_INPROGRESS %}
+
+                <div class="lead span5">
+                  <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
+                </div>
+                <div class="span4 lead">
+                  Checking out layers
+                </div>
+          {% else %}
+
+          <div>FIXME!</div>
+
+          {% endif %}
+      <div class="lead pull-right">
+      </div>
+    </div>
+  </div>
+
+
+
+  {% endif %} {# this ends the build request most recent build section #}
+
+{%endwith%}{% endfor %}
+  </div>
+
+<script>
+
+function _makeXHRBuildCall(url, data, onsuccess, onfail) {
+    $.ajax( {
+        type: "POST",
+        url: url,
+        data: data,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function (_data) {
+            if (_data.error != "ok") {
+                alert(_data.error);
+            } else {
+                if (onsuccess != undefined) onsuccess(_data);
+            }
+        },
+        error: function (_data) {
+            alert("Call failed");
+            console.log(_data);
+            if (onfail) onfail(data);
+        } });
+}
+
+
+function scheduleBuild(url, projectName, buildlist) {
+  console.log("scheduleBuild");
+  _makeXHRBuildCall(url, {targets: buildlist.join(" ")}, function (_data) {
+
+      $('#latest-builds').prepend('<div class="alert alert-info" style="padding-top:0px">' + '<span class="label label-info" style="font-weight: normal; margin-bottom: 5px; margin-left:-15px; padding-top:5px;">'+projectName+'</span><div class="row-fluid">' +
+  '<div class="span4 lead">' + buildlist.join(" ") +
+  '</div><div class="span4 lead pull-right">Build queued. Your build will start shortly.</div></div></div>');
+  });
+}
+
+</script>
+
+{%endif%}
+
diff --git a/lib/toaster/toastergui/templatetags/projecttags.py b/lib/toaster/toastergui/templatetags/projecttags.py
index f564edf..276c6eb 100644
--- a/lib/toaster/toastergui/templatetags/projecttags.py
+++ b/lib/toaster/toastergui/templatetags/projecttags.py
@@ -65,6 +65,25 @@ def query(qs, **kwargs):
     """
     return qs.filter(**kwargs)
 
+
+@register.filter("whitespace_slice")
+def whitespace_space_filter(value, arg):
+    try:
+        bits = []
+        for x in arg.split(":"):
+            if len(x) == 0:
+                bits.append(None)
+            else:
+                # convert numeric value to the first whitespace after
+                first_whitespace = value.find(" ", int(x))
+                if first_whitespace == -1:
+                    bits.append(int(x))
+                else:
+                    bits.append(first_whitespace)
+        return value[slice(*bits)]
+    except (ValueError, TypeError):
+        raise
+
 @register.filter
 def divide(value, arg):
     if int(arg) == 0:
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index e414b66..e8e4927 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -255,197 +255,6 @@ def _save_parameters_cookies(response, pagesize, orderby, request):
     return response
 
 
-
-# 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.
-    (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-')
-    mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
-    retval = _verify_parameters( request.GET, mandatory_parameters )
-    if retval:
-        return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
-
-    # boilerplate code that takes a request for an object type and returns a queryset
-    # for that object type. copypasta for all needed table searches
-    (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
-    queryset_all = Build.objects.exclude(outcome = Build.IN_PROGRESS)
-    queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
-    queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
-
-    # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
-    build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
-
-    # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
-    build_mru = Build.objects.order_by("-started_on")[:3]
-
-    # set up list of fstypes for each build
-    fstypes_map = {};
-    for build in build_info:
-        targets = Target.objects.filter( build_id = build.id )
-        comma = "";
-        extensions = "";
-        for t in targets:
-            if ( not t.is_image ):
-                continue
-            tif = Target_Image_File.objects.filter( target_id = t.id )
-            for i in tif:
-                s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
-                if s == i.file_name:
-                    s=re.sub('.*\.', '', i.file_name)
-                if None == re.search(s,extensions):
-                    extensions += comma + s
-                    comma = ", "
-        fstypes_map[build.id]=extensions
-
-    # 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,
-                'objectname' : "builds",
-                'default_orderby' : 'completed_on:-',
-                'fstypes' : fstypes_map,
-                'search_term' : search_term,
-                'total_count' : queryset_with_search.count(),
-            # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
-                'tablecols' : [
-                {'name': 'Outcome',                                                # column with a single filter
-                 'qhelp' : "The outcome tells you if a build successfully completed or failed",     # the help button content
-                 'dclass' : "span2",                                                # indication about column width; comes from the design
-                 'orderfield': _get_toggle_order(request, "outcome"),               # adds ordering by the field value; default ascending unless clicked from ascending into descending
-                 'ordericon':_get_toggle_order_icon(request, "outcome"),
-                  # filter field will set a filter on that column with the specs in the filter description
-                  # the class field in the filter has no relation with clclass; the control different aspects of the UI
-                  # still, it is recommended for the values to be identical for easy tracking in the generated HTML
-                 'filter' : {'class' : 'outcome',
-                             'label': 'Show:',
-                             'options' : [
-                                         ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()),  # this is the field search expression
-                                         ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()),
-                                         ]
-                            }
-                },
-                {'name': 'Target',                                                 # default column, disabled box, with just the name in the list
-                 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)",
-                 'orderfield': _get_toggle_order(request, "target__target"),
-                 'ordericon':_get_toggle_order_icon(request, "target__target"),
-                },
-                {'name': 'Machine',
-                 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
-                 'orderfield': _get_toggle_order(request, "machine"),
-                 'ordericon':_get_toggle_order_icon(request, "machine"),
-                 'dclass': 'span3'
-                },                           # a slightly wider column
-                {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1,      # this is an unchecked box, which hides the column
-                 'qhelp': "The date and time you started the build",
-                 'orderfield': _get_toggle_order(request, "started_on", True),
-                 'ordericon':_get_toggle_order_icon(request, "started_on"),
-                 'filter' : {'class' : 'started_on',
-                             'label': 'Show:',
-                             'options' : [
-                                         ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()),
-                                         ("Yesterday's builds", 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(hours=24))).count()),
-                                         ("This week's builds", 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(days=7))).count()),
-                                         ]
-                            }
-                },
-                {'name': 'Completed on',
-                 'qhelp': "The date and time the build finished",
-                 'orderfield': _get_toggle_order(request, "completed_on", True),
-                 'ordericon':_get_toggle_order_icon(request, "completed_on"),
-                 'orderkey' : 'completed_on',
-                 'filter' : {'class' : 'completed_on',
-                             'label': 'Show:',
-                             'options' : [
-                                         ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()),
-                                         ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24))).count()),
-                                         ("This week's builds", 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(days=7))).count()),
-                                         ]
-                            }
-                },
-                {'name': 'Failed tasks', 'clclass': 'failed_tasks',                # specifing a clclass will enable the checkbox
-                 'qhelp': "How many tasks failed during the build",
-                 'filter' : {'class' : 'failed_tasks',
-                             'label': 'Show:',
-                             'options' : [
-                                         ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()),
-                                         ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()),
-                                         ]
-                            }
-                },
-                {'name': 'Errors', 'clclass': 'errors_no',
-                 'qhelp': "How many errors were encountered during the build (if any)",
-                 'orderfield': _get_toggle_order(request, "errors_no", True),
-                 'ordericon':_get_toggle_order_icon(request, "errors_no"),
-                 'orderkey' : 'errors_no',
-                 'filter' : {'class' : 'errors_no',
-                             'label': 'Show:',
-                             'options' : [
-                                         ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()),
-                                         ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()),
-                                         ]
-                            }
-                },
-                {'name': 'Warnings', 'clclass': 'warnings_no',
-                 'qhelp': "How many warnings were encountered during the build (if any)",
-                 'orderfield': _get_toggle_order(request, "warnings_no", True),
-                 'ordericon':_get_toggle_order_icon(request, "warnings_no"),
-                 'orderkey' : 'warnings_no',
-                 'filter' : {'class' : 'warnings_no',
-                             'label': 'Show:',
-                             'options' : [
-                                         ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()),
-                                         ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()),
-                                         ]
-                            }
-                },
-                {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
-                 'qhelp': "How long it took the build to finish",
-                 'orderfield': _get_toggle_order(request, "timespent", True),
-                 'ordericon':_get_toggle_order_icon(request, "timespent"),
-                 'orderkey' : 'timespent',
-                },
-                {'name': 'Image files', 'clclass': 'output',
-                 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
-                    # TODO: compute image fstypes from Target_Image_File
-                },
-                ]
-            }
-
-    if not toastermain.settings.MANAGED:
-        context['tablecols'].insert(-2,
-                {'name': 'Log1',
-                 'dclass': "span4",
-                 'qhelp': "Path to the build main log file",
-                 'clclass': 'log', 'hidden': 1,
-                 'orderfield': _get_toggle_order(request, "cooker_log_path"),
-                 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
-                 'orderkey' : 'cooker_log_path',
-                }
-        )
-
-
-    if toastermain.settings.MANAGED:
-        context['tablecols'].append(
-                {'name': 'Project', 'clclass': 'project',
-                 'filter': {'class': 'project',
-                        'label': 'Project:',
-                        'options':  map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()),
-
-                       }
-                }
-        )
-
-
-    response = render(request, template, context)
-    _save_parameters_cookies(response, pagesize, orderby, request)
-    return response
-
-
 ##
 # build dashboard for a single build, coming in as argument
 # Each build may contain multiple targets and each target
@@ -1895,6 +1704,221 @@ if toastermain.settings.MANAGED:
                 del request.session['project_id']
         return ret
 
+
+    # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds
+    def builds(request):
+        template = 'managed_builds.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.
+
+        # ATTN: we use here the ordering parameters for interactive mode; the translation for BuildRequest fields will happen below
+        (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-')
+        mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
+        retval = _verify_parameters( request.GET, mandatory_parameters )
+        if retval:
+            return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
+
+        # translate interactive mode ordering to managed mode ordering
+        ordering_params = orderby.split(":")
+        if ordering_params[0] == "completed_on":
+            ordering_params[0] = "updated"
+        if ordering_params[0] == "started_on":
+            ordering_params = "created"
+
+        request.GET = request.GET.copy()        # get a mutable copy of the GET QueryDict
+        request.GET['orderby'] = ":".join(ordering_params)
+
+        # boilerplate code that takes a request for an object type and returns a queryset
+        # for that object type. copypasta for all needed table searches
+        (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest)
+        # we don't display in-progress or deleted builds
+        queryset_all = BuildRequest.objects.exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED)
+        queryset_with_search = _get_queryset(BuildRequest, queryset_all, None, search_term, ordering_string, '-updated')
+        queryset = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated')
+
+        # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
+        build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
+
+        # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
+        # most recent build is like projects' most recent builds, but across all projects
+        build_mru = BuildRequest.objects.all()
+        build_mru = list(build_mru.filter(Q(state__lt=BuildRequest.REQ_COMPLETED) or Q(state=BuildRequest.REQ_DELETED)).order_by("-pk")) + list(build_mru.filter(state__in=[BuildRequest.REQ_COMPLETED, BuildRequest.REQ_FAILED]).order_by("-pk")[:3])
+
+        fstypes_map = {};
+        for build_request in build_info:
+            # set display variables for build request
+            build_request.machine = build_request.brvariable_set.get(name="MACHINE").value
+            build_request.timespent = build_request.updated - build_request.created
+
+            # set up list of fstypes for each build
+            if build_request.build is None:
+                continue
+            targets = Target.objects.filter( build_id = build_request.build.id )
+            comma = "";
+            extensions = "";
+            for t in targets:
+                if ( not t.is_image ):
+                    continue
+                tif = Target_Image_File.objects.filter( target_id = t.id )
+                for i in tif:
+                    s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
+                    if s == i.file_name:
+                        s=re.sub('.*\.', '', i.file_name)
+                    if None == re.search(s,extensions):
+                        extensions += comma + s
+                        comma = ", "
+            fstypes_map[build_request.build.id]=extensions
+
+
+        # 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,
+                    'objectname' : "builds",
+                    'default_orderby' : 'updated:-',
+                    'fstypes' : fstypes_map,
+                    'search_term' : search_term,
+                    'total_count' : queryset_with_search.count(),
+                # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
+                    'tablecols' : [
+                    {'name': 'Outcome',                                                # column with a single filter
+                     'qhelp' : "The outcome tells you if a build successfully completed or failed",     # the help button content
+                     'dclass' : "span2",                                                # indication about column width; comes from the design
+                     'orderfield': _get_toggle_order(request, "state"),               # adds ordering by the field value; default ascending unless clicked from ascending into descending
+                     'ordericon':_get_toggle_order_icon(request, "state"),
+                      # filter field will set a filter on that column with the specs in the filter description
+                      # the class field in the filter has no relation with clclass; the control different aspects of the UI
+                      # still, it is recommended for the values to be identical for easy tracking in the generated HTML
+                     'filter' : {'class' : 'outcome',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ('Successful builds', 'state:' + str(BuildRequest.REQ_COMPLETED), queryset_with_search.filter(state=str(BuildRequest.REQ_COMPLETED)).count()),  # this is the field search expression
+                                             ('Failed builds', 'state:'+ str(BuildRequest.REQ_FAILED), queryset_with_search.filter(state=str(BuildRequest.REQ_FAILED)).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Target',                                                 # default column, disabled box, with just the name in the list
+                     'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)",
+                     'orderfield': _get_toggle_order(request, "target__target"),
+                     'ordericon':_get_toggle_order_icon(request, "target__target"),
+                    },
+                    {'name': 'Machine',
+                     'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
+                     'orderfield': _get_toggle_order(request, "machine"),
+                     'ordericon':_get_toggle_order_icon(request, "machine"),
+                     'dclass': 'span3'
+                    },                           # a slightly wider column
+                    {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1,      # this is an unchecked box, which hides the column
+                     'qhelp': "The date and time you started the build",
+                     'orderfield': _get_toggle_order(request, "created", True),
+                     'ordericon':_get_toggle_order_icon(request, "created"),
+                     'filter' : {'class' : 'created',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=timezone.now()).count()),
+                                             ("Yesterday's builds", 'created__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=(timezone.now()-timedelta(hours=24))).count()),
+                                             ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=(timezone.now()-timedelta(days=7))).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Completed on',
+                     'qhelp': "The date and time the build finished",
+                     'orderfield': _get_toggle_order(request, "updated", True),
+                     'ordericon':_get_toggle_order_icon(request, "updated"),
+                     'orderkey' : 'updated',
+                     'filter' : {'class' : 'updated',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=timezone.now()).count()),
+                                             ("Yesterday's builds", 'updated__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=(timezone.now()-timedelta(hours=24))).count()),
+                                             ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Failed tasks', 'clclass': 'failed_tasks',                # specifing a clclass will enable the checkbox
+                     'qhelp': "How many tasks failed during the build",
+                     'filter' : {'class' : 'failed_tasks',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ('BuildRequests with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()),
+                                             ('BuildRequests without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Errors', 'clclass': 'errors_no',
+                     'qhelp': "How many errors were encountered during the build (if any)",
+                     'orderfield': _get_toggle_order(request, "errors_no", True),
+                     'ordericon':_get_toggle_order_icon(request, "errors_no"),
+                     'orderkey' : 'errors_no',
+                     'filter' : {'class' : 'errors_no',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ('BuildRequests with errors', 'errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()),
+                                             ('BuildRequests without errors', 'errors_no:0', queryset_with_search.filter(build__errors_no=0).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Warnings', 'clclass': 'warnings_no',
+                     'qhelp': "How many warnings were encountered during the build (if any)",
+                     'orderfield': _get_toggle_order(request, "build__warnings_no", True),
+                     'ordericon':_get_toggle_order_icon(request, "build__warnings_no"),
+                     'orderkey' : 'build__warnings_no',
+                     'filter' : {'class' : 'build__warnings_no',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ('BuildRequests with warnings','build__warnings_no__gte:1', queryset_with_search.filter(build__warnings_no__gte=1).count()),
+                                             ('BuildRequests without warnings','build__warnings_no:0', queryset_with_search.filter(build__warnings_no=0).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
+                     'qhelp': "How long it took the build to finish",
+                     'orderfield': _get_toggle_order(request, "timespent", True),
+                     'ordericon':_get_toggle_order_icon(request, "timespent"),
+                     'orderkey' : 'timespent',
+                    },
+                    {'name': 'Image files', 'clclass': 'output',
+                     'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
+                        # TODO: compute image fstypes from Target_Image_File
+                    },
+                    ]
+                }
+
+        if not toastermain.settings.MANAGED:
+            context['tablecols'].insert(-2,
+                    {'name': 'Log1',
+                     'dclass': "span4",
+                     'qhelp': "Path to the build main log file",
+                     'clclass': 'log', 'hidden': 1,
+                     'orderfield': _get_toggle_order(request, "cooker_log_path"),
+                     'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
+                     'orderkey' : 'cooker_log_path',
+                    }
+            )
+
+
+        if toastermain.settings.MANAGED:
+            context['tablecols'].append(
+                    {'name': 'Project', 'clclass': 'project',
+                     'filter': {'class': 'project',
+                            'label': 'Project:',
+                            'options':  map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=BuildRequest.REQ_INPROGRESS).count()), Project.objects.all()),
+
+                           }
+                    }
+            )
+
+
+        response = render(request, template, context)
+        _save_parameters_cookies(response, pagesize, orderby, request)
+        return response
+
+
+
+
     # new project
     def newproject(request):
         template = "newproject.html"
@@ -2819,21 +2843,21 @@ if toastermain.settings.MANAGED:
                      'filter' : {'class' : 'failed_tasks',
                                  'label': 'Show:',
                                  'options' : [
-                                             ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()),
-                                             ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()),
+                                             ('Builds with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()),
+                                             ('Builds without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()),
                                              ]
                                 }
                     },
                     {'name': 'Errors', 'clclass': 'errors_no',
                      'qhelp': "How many errors were encountered during the build (if any)",
-                     'orderfield': _get_toggle_order(request, "errors_no", True),
-                     'ordericon':_get_toggle_order_icon(request, "errors_no"),
-                     'orderkey' : 'errors_no',
-                     'filter' : {'class' : 'errors_no',
+                     'orderfield': _get_toggle_order(request, "build__errors_no", True),
+                     'ordericon':_get_toggle_order_icon(request, "build__errors_no"),
+                     'orderkey' : 'build__errors_no',
+                     'filter' : {'class' : 'build__errors_no',
                                  'label': 'Show:',
                                  'options' : [
-                                             ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()),
-                                             ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()),
+                                             ('Builds with errors', 'build__errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()),
+                                             ('Builds without errors', 'build__errors_no:0', queryset_with_search.filter(build__errors_no=0).count()),
                                              ]
                                 }
                     },
@@ -3007,6 +3031,199 @@ else:
             "DEBUG" : toastermain.settings.DEBUG
         }
 
+
+    # shows the "all builds" page for interactive mode; this is the old code, simply moved
+    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.
+        (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-')
+        mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
+        retval = _verify_parameters( request.GET, mandatory_parameters )
+        if retval:
+            return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
+
+        # boilerplate code that takes a request for an object type and returns a queryset
+        # for that object type. copypasta for all needed table searches
+        (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
+        queryset_all = Build.objects.exclude(outcome = Build.IN_PROGRESS)
+        queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
+        queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
+
+        # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
+        build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
+
+        # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
+        build_mru = Build.objects.order_by("-started_on")[:3]
+
+        # set up list of fstypes for each build
+        fstypes_map = {};
+        for build in build_info:
+            targets = Target.objects.filter( build_id = build.id )
+            comma = "";
+            extensions = "";
+            for t in targets:
+                if ( not t.is_image ):
+                    continue
+                tif = Target_Image_File.objects.filter( target_id = t.id )
+                for i in tif:
+                    s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
+                    if s == i.file_name:
+                        s=re.sub('.*\.', '', i.file_name)
+                    if None == re.search(s,extensions):
+                        extensions += comma + s
+                        comma = ", "
+            fstypes_map[build.id]=extensions
+
+        # 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,
+                    'objectname' : "builds",
+                    'default_orderby' : 'completed_on:-',
+                    'fstypes' : fstypes_map,
+                    'search_term' : search_term,
+                    'total_count' : queryset_with_search.count(),
+                # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
+                    'tablecols' : [
+                    {'name': 'Outcome',                                                # column with a single filter
+                     'qhelp' : "The outcome tells you if a build successfully completed or failed",     # the help button content
+                     'dclass' : "span2",                                                # indication about column width; comes from the design
+                     'orderfield': _get_toggle_order(request, "outcome"),               # adds ordering by the field value; default ascending unless clicked from ascending into descending
+                     'ordericon':_get_toggle_order_icon(request, "outcome"),
+                      # filter field will set a filter on that column with the specs in the filter description
+                      # the class field in the filter has no relation with clclass; the control different aspects of the UI
+                      # still, it is recommended for the values to be identical for easy tracking in the generated HTML
+                     'filter' : {'class' : 'outcome',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()),  # this is the field search expression
+                                             ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Target',                                                 # default column, disabled box, with just the name in the list
+                     'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)",
+                     'orderfield': _get_toggle_order(request, "target__target"),
+                     'ordericon':_get_toggle_order_icon(request, "target__target"),
+                    },
+                    {'name': 'Machine',
+                     'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
+                     'orderfield': _get_toggle_order(request, "machine"),
+                     'ordericon':_get_toggle_order_icon(request, "machine"),
+                     'dclass': 'span3'
+                    },                           # a slightly wider column
+                    {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1,      # this is an unchecked box, which hides the column
+                     'qhelp': "The date and time you started the build",
+                     'orderfield': _get_toggle_order(request, "started_on", True),
+                     'ordericon':_get_toggle_order_icon(request, "started_on"),
+                     'filter' : {'class' : 'started_on',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()),
+                                             ("Yesterday's builds", 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(hours=24))).count()),
+                                             ("This week's builds", 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(days=7))).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Completed on',
+                     'qhelp': "The date and time the build finished",
+                     'orderfield': _get_toggle_order(request, "completed_on", True),
+                     'ordericon':_get_toggle_order_icon(request, "completed_on"),
+                     'orderkey' : 'completed_on',
+                     'filter' : {'class' : 'completed_on',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()),
+                                             ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24))).count()),
+                                             ("This week's builds", 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(days=7))).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Failed tasks', 'clclass': 'failed_tasks',                # specifing a clclass will enable the checkbox
+                     'qhelp': "How many tasks failed during the build",
+                     'filter' : {'class' : 'failed_tasks',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()),
+                                             ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Errors', 'clclass': 'errors_no',
+                     'qhelp': "How many errors were encountered during the build (if any)",
+                     'orderfield': _get_toggle_order(request, "errors_no", True),
+                     'ordericon':_get_toggle_order_icon(request, "errors_no"),
+                     'orderkey' : 'errors_no',
+                     'filter' : {'class' : 'errors_no',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()),
+                                             ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Warnings', 'clclass': 'warnings_no',
+                     'qhelp': "How many warnings were encountered during the build (if any)",
+                     'orderfield': _get_toggle_order(request, "warnings_no", True),
+                     'ordericon':_get_toggle_order_icon(request, "warnings_no"),
+                     'orderkey' : 'warnings_no',
+                     'filter' : {'class' : 'warnings_no',
+                                 'label': 'Show:',
+                                 'options' : [
+                                             ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()),
+                                             ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()),
+                                             ]
+                                }
+                    },
+                    {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
+                     'qhelp': "How long it took the build to finish",
+                     'orderfield': _get_toggle_order(request, "timespent", True),
+                     'ordericon':_get_toggle_order_icon(request, "timespent"),
+                     'orderkey' : 'timespent',
+                    },
+                    {'name': 'Image files', 'clclass': 'output',
+                     'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
+                        # TODO: compute image fstypes from Target_Image_File
+                    },
+                    ]
+                }
+
+        if not toastermain.settings.MANAGED:
+            context['tablecols'].insert(-2,
+                    {'name': 'Log1',
+                     'dclass': "span4",
+                     'qhelp': "Path to the build main log file",
+                     'clclass': 'log', 'hidden': 1,
+                     'orderfield': _get_toggle_order(request, "cooker_log_path"),
+                     'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
+                     'orderkey' : 'cooker_log_path',
+                    }
+            )
+
+
+        if toastermain.settings.MANAGED:
+            context['tablecols'].append(
+                    {'name': 'Project', 'clclass': 'project',
+                     'filter': {'class': 'project',
+                            'label': 'Project:',
+                            'options':  map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()),
+
+                           }
+                    }
+            )
+
+
+        response = render(request, template, context)
+        _save_parameters_cookies(response, pagesize, orderby, request)
+        return response
+
+
+
+
     def newproject(request):
         raise Exception("page not available in interactive mode")
 
-- 
1.9.1



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

* [PATCH 4/4] toasterui: fix variable data error
  2015-01-14 12:46 [PATCH 0/4] Toaster feature / bugfix patchset Alex DAMIAN
                   ` (2 preceding siblings ...)
  2015-01-14 12:46 ` [PATCH 3/4] toastergui: all builds page lists failed build requests Alex DAMIAN
@ 2015-01-14 12:46 ` Alex DAMIAN
  2015-01-14 18:04 ` [PATCH 0/4] Toaster feature / bugfix patchset akuster808
  4 siblings, 0 replies; 6+ messages in thread
From: Alex DAMIAN @ 2015-01-14 12:46 UTC (permalink / raw)
  To: bitbake-devel; +Cc: Alexandru DAMIAN

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

A bug slipped in the toaster ui that prevented saving of
build configuration despite the data being retrieved
from the server. This patch fixes the shaming mistake.

[YOCTO #7117]

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

diff --git a/lib/bb/ui/buildinfohelper.py b/lib/bb/ui/buildinfohelper.py
index f825b57..9e801ac 100644
--- a/lib/bb/ui/buildinfohelper.py
+++ b/lib/bb/ui/buildinfohelper.py
@@ -757,7 +757,7 @@ class BuildInfoHelper(object):
 
         # Save build configuration
         data = self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0]
-        self.orm_wrapper.save_build_variables(build_obj, [])
+        self.orm_wrapper.save_build_variables(build_obj, data)
 
         return self.brbe
 
-- 
1.9.1



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

* Re: [PATCH 0/4] Toaster feature / bugfix patchset
  2015-01-14 12:46 [PATCH 0/4] Toaster feature / bugfix patchset Alex DAMIAN
                   ` (3 preceding siblings ...)
  2015-01-14 12:46 ` [PATCH 4/4] toasterui: fix variable data error Alex DAMIAN
@ 2015-01-14 18:04 ` akuster808
  4 siblings, 0 replies; 6+ messages in thread
From: akuster808 @ 2015-01-14 18:04 UTC (permalink / raw)
  To: Alex DAMIAN, bitbake-devel

are any of these needed for dizzy?

- armin

On 01/14/2015 04:46 AM, Alex DAMIAN wrote:
> From: Alexandru DAMIAN <alexandru.damian@intel.com>
>
> This patchset brings in the layer details page, and the managed version
> of the "all builds" page.
>
> The patches have been reviewed on the Toaster mailing list.
>
> Can you please pull at your convenience ?
>
>
> Cheers,
> Alex
>
>
> The following changes since commit 0557d03c170fba8d7efe82be1b9641d0eb229213:
>
>    cooker/cache/parse: Implement pyinofity based reconfigure (2015-01-13 22:23:21 +0000)
>
> are available in the git repository at:
>
>    git://git.yoctoproject.org/poky-contrib adamian/20150114-submission-bb
>    http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=adamian/20150114-submission-bb
>
> Alexandru DAMIAN (2):
>    toastergui: all builds page lists failed build requests
>    toasterui: fix variable data error
>
> Michael Wood (2):
>    toaster: Add layer details page feature
>    toaster: layerdetails Remove compatibility setting
>
>   lib/bb/ui/buildinfohelper.py                       |   2 +-
>   lib/toaster/toastergui/static/css/default.css      |   1 +
>   lib/toaster/toastergui/static/js/layerdetails.js   | 398 ++++++++++++
>   lib/toaster/toastergui/static/js/libtoaster.js     |  35 +
>   lib/toaster/toastergui/templates/layerdetails.html | 625 +++++++++++++-----
>   .../toastergui/templates/layers_dep_modal.html     |   9 +
>   .../toastergui/templates/managed_builds.html       | 135 ++++
>   .../toastergui/templates/managed_mrb_section.html  | 172 +++++
>   lib/toaster/toastergui/templatetags/projecttags.py |  19 +
>   lib/toaster/toastergui/urls.py                     |   1 +
>   lib/toaster/toastergui/views.py                    | 708 +++++++++++++++------
>   11 files changed, 1755 insertions(+), 350 deletions(-)
>   create mode 100644 lib/toaster/toastergui/static/js/layerdetails.js
>   create mode 100644 lib/toaster/toastergui/templates/managed_builds.html
>   create mode 100644 lib/toaster/toastergui/templates/managed_mrb_section.html
>


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

end of thread, other threads:[~2015-01-14 18:04 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-01-14 12:46 [PATCH 0/4] Toaster feature / bugfix patchset Alex DAMIAN
2015-01-14 12:46 ` [PATCH 1/4] toaster: Add layer details page feature Alex DAMIAN
2015-01-14 12:46 ` [PATCH 2/4] toaster: layerdetails Remove compatibility setting Alex DAMIAN
2015-01-14 12:46 ` [PATCH 3/4] toastergui: all builds page lists failed build requests Alex DAMIAN
2015-01-14 12:46 ` [PATCH 4/4] toasterui: fix variable data error Alex DAMIAN
2015-01-14 18:04 ` [PATCH 0/4] Toaster feature / bugfix patchset akuster808

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.