From 025533d90b694ed37278c8f5be85afbd05857971 Mon Sep 17 00:00:00 2001 From: Michael Wood Date: Wed, 14 Jan 2015 12:46:52 +0000 Subject: bitbake: toaster: Add layer details page feature 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 (Bitbake rev: c1442bc68ad8ba20c37b1a7cde1400297f4be811) Signed-off-by: Michael Wood Signed-off-by: Richard Purdie --- .../lib/toaster/toastergui/static/css/default.css | 1 + .../toaster/toastergui/static/js/layerdetails.js | 404 +++++++++++++ .../lib/toaster/toastergui/static/js/libtoaster.js | 35 ++ .../toaster/toastergui/templates/layerdetails.html | 644 ++++++++++++++++----- .../toastergui/templates/layers_dep_modal.html | 9 + bitbake/lib/toaster/toastergui/urls.py | 1 + bitbake/lib/toaster/toastergui/views.py | 94 ++- 7 files changed, 1038 insertions(+), 150 deletions(-) create mode 100644 bitbake/lib/toaster/toastergui/static/js/layerdetails.js (limited to 'bitbake') diff --git a/bitbake/lib/toaster/toastergui/static/css/default.css b/bitbake/lib/toaster/toastergui/static/css/default.css index 199c7531dc..a3fa0ddf6a 100644 --- a/bitbake/lib/toaster/toastergui/static/css/default.css +++ b/bitbake/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/bitbake/lib/toaster/toastergui/static/js/layerdetails.js b/bitbake/lib/toaster/toastergui/static/js/layerdetails.js new file mode 100644 index 0000000000..a5a6330630 --- /dev/null +++ b/bitbake/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 = $("
  • "); + + 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(""); + 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(""); + } 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(""); + } 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(""); + + if (depsList) { + alertMsg.append("You have added "+(depsList.length+1)+" layers: and its dependencies "); + + /* Build the layer deps list */ + depsList.map(function(layer, i){ + var link = $(""); + + 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 1 layer: "); + } + } 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 1 layer: "); + } + + 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/bitbake/lib/toaster/toastergui/static/js/libtoaster.js b/bitbake/lib/toaster/toastergui/static/js/libtoaster.js index a2a0abd45b..04264cd8ba 100644 --- a/bitbake/lib/toaster/toastergui/static/js/libtoaster.js +++ b/bitbake/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/bitbake/lib/toaster/toastergui/templates/layerdetails.html b/bitbake/lib/toaster/toastergui/templates/layerdetails.html index 78dc54bfd1..c69f9e945a 100644 --- a/bitbake/lib/toaster/toastergui/templates/layerdetails.html +++ b/bitbake/lib/toaster/toastergui/templates/layerdetails.html @@ -1,159 +1,505 @@ {% extends "baseprojectpage.html" %} {% load projecttags %} {% load humanize %} - +{% load static %} {% block localbreadcrumb %} -
  • Layer Details
  • +
  • All Layers
  • +
  • + {{layerversion.layer.name}} ({{layerversion.commit|truncatechars:13}}) +
  • {% endblock %} - {% block projectinfomain %} - - -
    - -
    -
    -
    -
    - - Repository URL -
    -
    -
    -
    - - - -
    - Cloning this Git repository failed -
    -
    -
    - - Repository subdirectory -
    -
    - {{layerversion.dirpath}} - - - -
    -
    Brach, tag or commit
    -
    - {{layerversion.up_branch.name}} - -
    -
    - - Yocto Project compatibility -
    -
    - -
    -
    - - Layer dependencies -
    -
    - - - You can only add layers Toaster knows about -
    -
    -
    -
    -
    - There is no target data for {{layerversion.layer.name}} ... yet
    - 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. -
    -
    -
    -
    - There is no machine data for {{layerversion.layer.name}} ... yet
    - 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. -
    -
    + + + + + +{# If this is not an imported layer then hide the edit ui #} +{% if layerversion.layer_source_id != 3 %} + +{% endif %} + +{% include "layers_dep_modal.html" %} +
    +
    +
    +
    -
    -
    -

    About {{layerversion.layer.name}}

    -
    - -
    - Summary - -
    -
    - {{layerversion.layer.summary}} - -
    - -
    - Description -
    -
    - {{layerversion.layer.description}} - -
    - -
    - Maintainer(s) -
    -
    - Not set - -
    -
    -
    +
    + +
    +
    + +
    + + {% if layer_in_project == 0 %} + + {% else %} + + {% endif %} + + + +
    +
    +
    + + Repository URL +
    +
    + {{layerversion.layer.vcs_url}} + {% if layerversion.get_vcs_link_url %} + + {% endif %} + + +
    +
    + + Repository subdirectory +
    +
    + + {{layerversion.dirpath}} + {% if layerversion.get_vcs_dirpath_link_url %} + + {% endif %} + + + +
    +
    Brach, tag or commit
    +
    + {{layerversion.commit}} +
    +
    + + + +
    +
    + +
    +
    + + Yocto Project compatibility +
    +
    + {{layerversion.up_branch.name}} +
    +
    + + + +
    +
    + +
    +
    + + Layer dependencies +
    +
    + + + You can only add layers Toaster knows about +
    +
    +
    + +
    + {% if total_targets == 0 %} +
    + There is no target data for {{layerversion.layer.name}} ... yet
    + 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. +
    + {% else %} + +
    + + {% if targets.paginator.count == 0 %} +
    +

    No targets found

    + {% 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 %} +
    + {% else %} + + {% endif %} + + + {% if request.GET.targets_search %} + + + + {% endif %} + +
    + {% endif %} + + {% if targets.paginator.count == 0 %} + +
    + +
    + {% else %} + +
    + Show rows: + +
    +
    + + + + + + + + + + + + {% for target in targets %} + + + + + + + {% endfor %} + +
    + + Target + {% if request.GET.targets_search %} + {{targets.paginator.count}} + {% endif %} + + + Target version + DescriptionBuild target
    + {{target.name}} + {% if target.up_id %} + + {% endif %} + {{target.version}}{{target.summary}}
    + + + + {% endif %} + {% endif %} +
    + + +
    + {% if total_machines == 0 %} +
    + There is no machine data for {{layerversion.layer.name}} ... yet
    + 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. +
    + {% else %} + +
    + + {% if machines.paginator.count == 0 %} +
    +

    No machines found

    + {% 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 %} +
    + {% else %} + + {% endif %} + + + {% if request.GET.machines_search %} + + + + {% endif %} + +
    + {% endif %} + + {% if machines.paginator.count == 0 %} + +
    + +
    + {% else %} + +
    + Show rows: + +
    +
    + + + + + + + + + + + {% for machine in machines %} + + + + + + {% endfor %} + +
    + + Machine + {% if request.GET.machines_search %} + {{machines.paginator.count}} + {% endif %} + DescriptionSelect machine
    {{machine.name}}{{machine.description}}
    + + + + {% endif %} + {% endif %} +
    +
    +
    +
    +

    About {{layerversion.layer.name}}

    +
    + +
    + Summary + +
    +
    + + {{layerversion.layer.summary}} +
    + + + Cancel +
    + + +
    +
    + Description +
    +
    + + {{layerversion.layer.description}} +
    + + + Cancel +
    + + +
    + + {% if layerversion.layer.up_id %} +
    Layer index
    +
    + layer index link + +
    + {% endif %} +
    +
    +
    + + {% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html b/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html index b03fd0b218..8222027d4f 100644 --- a/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html +++ b/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html @@ -18,7 +18,16 @@