diff options
author | Alexandru DAMIAN <alexandru.damian@intel.com> | 2014-09-29 20:20:33 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2014-10-30 13:39:37 +0000 |
commit | e690080f83ad53c5e4a31e8c0fba2cc744eea1eb (patch) | |
tree | c33b6201c63236a7af788847b6342a5dd9529a2f | |
parent | c60e540dc532e3625a53854d7b02bf1e2568c3ca (diff) | |
download | bitbake-e690080f83ad53c5e4a31e8c0fba2cc744eea1eb.tar.gz |
toastergui: project edit capabilities in all layers page
This patch definitivates the all layers page, providing interactivity
for adding and removing layers inside the project from this page.
[YOCTO #6590]
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
-rw-r--r-- | lib/toaster/orm/models.py | 9 | ||||
-rw-r--r-- | lib/toaster/toastergui/static/css/default.css | 3 | ||||
-rw-r--r-- | lib/toaster/toastergui/templates/basetable_top.html | 1 | ||||
-rw-r--r-- | lib/toaster/toastergui/templates/basetable_top_layers.html | 5 | ||||
-rw-r--r-- | lib/toaster/toastergui/templates/layers.html | 282 | ||||
-rwxr-xr-x | lib/toaster/toastergui/views.py | 28 |
6 files changed, 209 insertions, 119 deletions
diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py index 152171748..3c7f6611d 100644 --- a/lib/toaster/orm/models.py +++ b/lib/toaster/orm/models.py @@ -737,6 +737,15 @@ class Layer_Version(models.Model): dirpath = models.CharField(max_length=255, null = True, default = None) # LayerBranch.vcs_subdir priority = models.IntegerField(default = 0) # if -1, this is a default layer + def get_vcs_link_url(self, file_path="/"): + if self.layer.vcs_web_file_base_url is None: + return None + return self.layer.vcs_web_file_base_url.replace('%path%', file_path).replace('%branch%', self.up_branch.name) + + def get_vcs_link_url_dirpath(self): + return self.get_vcs_link_url(self.dirpath) + + def __unicode__(self): return "LV " + str(self.layer) + " " + self.commit diff --git a/lib/toaster/toastergui/static/css/default.css b/lib/toaster/toastergui/static/css/default.css index 8780c4f23..9e62c6c8e 100644 --- a/lib/toaster/toastergui/static/css/default.css +++ b/lib/toaster/toastergui/static/css/default.css @@ -60,7 +60,8 @@ dd p { line-height: 20px; } .tooltip { z-index: 2000 !important; } /* Override default Twitter Boostrap styles for anchor tags inside tables */ -td a { color: #333333; } +td a, td a > code { color: #333333; } +td a > code { white-space: normal; } td a:hover { color: #000000; text-decoration: underline; } /* Override default Twitter Bootstrap styles for tr.error */ diff --git a/lib/toaster/toastergui/templates/basetable_top.html b/lib/toaster/toastergui/templates/basetable_top.html index bffa731e1..e3f6a4ee2 100644 --- a/lib/toaster/toastergui/templates/basetable_top.html +++ b/lib/toaster/toastergui/templates/basetable_top.html @@ -176,6 +176,7 @@ </form> <div class="pull-right"> {% if tablecols %} + {% block custombuttons%} {% endblock %} <div class="btn-group"> <button class="btn dropdown-toggle" data-toggle="dropdown">Edit columns <span class="caret"></span> diff --git a/lib/toaster/toastergui/templates/basetable_top_layers.html b/lib/toaster/toastergui/templates/basetable_top_layers.html new file mode 100644 index 000000000..bd6e7dd2d --- /dev/null +++ b/lib/toaster/toastergui/templates/basetable_top_layers.html @@ -0,0 +1,5 @@ +{% extends "basetable_top.html" %} + +{%block custombuttons %} + <a class="btn" href="{% url 'importlayer' %}" style="margin-right:5px;">Import layer</a> +{%endblock%} diff --git a/lib/toaster/toastergui/templates/layers.html b/lib/toaster/toastergui/templates/layers.html index b32a7ed2e..52eed8665 100644 --- a/lib/toaster/toastergui/templates/layers.html +++ b/lib/toaster/toastergui/templates/layers.html @@ -9,43 +9,63 @@ {% block projectinfomain %} <div class="page-header"> <h1> - All layers - <i class="icon-question-sign get-help heading-help" title="This page lists all the layers compatible with Yocto Project 1.7 'Dxxxx' that Toaster knows about. They include community-created layers suitable for use on top of OpenEmbedded Core and any layers you have imported"></i> + {% if request.GET.search and objects.paginator.count > 0 %} + {{objects.paginator.count}} layer{{objects.paginator.count|pluralize}} found + {%elif request.GET.search and objects.paginator.count == 0%} + No layer found + {%else%} + All layers + {%endif%} + <i class="icon-question-sign get-help heading-help" title="This page lists all the layers compatible with " + {{project.release.name}} + " that Toaster knows about."></i> </h1> </div> - <!--div class="alert"> - <div class="input-append" style="margin-bottom:0px;"> - <input class="input-xxlarge" type="text" placeholder="Search layers" value="browser" /> - <a class="add-on btn"> - <i class="icon-remove"></i> - </a> - <button class="btn" type="button">Search</button> - <a class="btn btn-link" href="#">Show all layers</a> - </div> - </div--> + + <div id="zone1alerts"> + + </div> + + <div id="layer-added" class="alert alert-info lead" style="display:none;"></div> - <div id="layer-removed" class="alert alert-info lead" style="display:none;"> - <button type="button" class="close" data-dismiss="alert">×</button> - <strong>1</strong> layer deleted from <a href="project-with-targets.html">your project</a>: <a href="#">meta-aarch64</a> - </div> -{% include "basetable_top.html" %} +{% include "basetable_top_layers.html" %} {% for lv in objects %} <tr class="data"> <td class="layer"><a href="{% url 'layerdetails' lv.id %}">{{lv.layer.name}}</a></td> <td class="description">{{lv.layer.summary}}</td> <td class="source"><a href="{% url 'layerdetails' lv.pk %}">{{lv.layer_source.name}}</a></td> - <td class="git-repo"><a href="{% url 'layerdetails' lv.pk %}"><code>{{lv.layer.layer_index_url}}</code></a></td> - <td class="git-subdir" style="display: table-cell;"><a href="{% url 'layerdetails' lv.pk %}"><code>{{lv.dirpath}}</code></a></td> + <td class="git-repo"><a href="{% url 'layerdetails' lv.pk %}"><code>{{lv.layer.vcs_url}}</code></a> + {% if lv.get_vcs_link_url %} + <a target="_blank" href="{{ lv.get_vcs_link_url }}"><i class="icon-share get-info"></i></a> + {% endif %} + </td> + <td class="git-subdir" style="display: table-cell;"><a href="{% url 'layerdetails' lv.pk %}"><code>{{lv.dirpath}}</code></a> + {% if lv.get_vcs_link_url %} + <a target="_blank" href="{{ lv.get_vcs_link_url_dirpath }}"><i class="icon-share get-info"></i></a> + {% endif %} + </td> <td class="branch">{% if lv.branch %}{{lv.branch}}{% else %}{{lv.up_branch.name}}{% endif %}</td> - <td class="dependencies">{% for lvs in lv.dependencies.all %}{{lvs.layer.name}}<br/>{%endfor%}</td> - <td class="add-layers"> - <button id="remove-layer-{{lv.pk}}" class="btn btn-danger btn-block remove-layer" title="1 layer deleted" style="display:none;"> + <td class="dependencies"> + {% with lvds=lv.dependencies.all%} + {% if lvds.count %} + <a class="btn" + title="<a href='{% url "layerdetails" lv.pk %}'>{{lv.layer.name}}</a> dependencies" + data-content="<ul class='unstyled'> + {% for i in lvds%} + <li><a href='{% url "layerdetails" i.depends_on.pk %}'>{{i.depends_on.layer.name}}</a></li> + {% endfor %} + </ul>"> + {{lvds.count}} + </a> + {% endif %} + {% endwith %} + </td> + <td class="add-del-layers" value="{{lv.pk}}"> + <button id="layer-del-{{lv.pk}}" class="btn btn-danger btn-block remove-layer" style="display:none;" onclick="layerDel({{lv.pk}}, '{{lv.layer.name}}', '{%url 'layerdetails' lv.pk%}')"> <i class="icon-trash"></i> Delete layer </button> - <button id="add-layer-{{lv.pk}}" class="btn btn-block add-layer" title="1 layer added"> + <button id="layer-add-{{lv.pk}}" class="btn btn-block" style="display:none;" onclick="layerAdd({{lv.pk}}, '{{lv.layer.name}}', '{%url 'layerdetails' lv.pk%}')" > <i class="icon-plus"></i> Add layer </button> @@ -57,113 +77,145 @@ <!-- Modals --> <!-- 'Layer dependencies modal' --> - <div id="dependencies-message" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true"> + <div id="dependencies_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true"> + <form id="dependencies_modal_form"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h3>meta-acer dependencies</h3> + <h3><span class="layer-name"></span> dependencies</h3> </div> <div class="modal-body"> - <p><strong>meta-acer</strong> depends on some layers that are not added to your project. Select the ones you want to add:</p> - <ul class="unstyled"> - <li> - <label class="checkbox"> - <input type="checkbox" checked="checked"> - meta-android - </label> - </li> - <li> - <label class="checkbox"> - <input type="checkbox" checked="checked"> - meta-oe - </label> - </li> + <p><strong class="layer-name"></strong> depends on some layers that are not added to your project. Select the ones you want to add:</p> + <ul class="unstyled" id="dependencies_list"> </ul> </div> <div class="modal-footer"> - <button id="add-layer-dependencies" type="submit" class="btn btn-primary" data-dismiss="modal" >Add layers</button> - <button class="btn" data-dismiss="modal">Cancel</button> + <button class="btn btn-primary" type="submit">Add layers</button> + <button class="btn" type="reset" data-dismiss="modal">Cancel</button> </div> + </form> </div> - <script> - $(document).ready(function() { - - //show or hide selected columns on load - $("input:checkbox").each(function(){ - var selectedType = $(this).val(); - if($(this).is(":checked")){ - $("."+selectedType).show(); +<script> + +function _makeXHREditCall(data, onsuccess, onfail) { + $.ajax( { + type: "POST", + url: "{% url 'xhr_projectedit' project.id %}", + data: data, + headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, + success: function (_data) { + if (_data.error != "ok") { + alert(_data.error); + } else { + updateButtons(_data.layers.map(function (e) {return e.id})); + if (onsuccess != undefined) onsuccess(_data); } - else{ - $("."+selectedType).hide(); + }, + error: function (_data) { + alert("Call failed"); + console.log(_data); + } + }); +} + + +function layerDel(layerId, layerName, layerURL) { + _makeXHREditCall({ 'layerDel': layerId }, function () { + show_alert("<strong>1</strong> layer deleted from <a href=\"{% url 'project' project.id%}\">{{project.name}}</a>: <a href=\""+layerURL+"\">" + layerName +"</a>"); + }); +} + +function show_alert(text, cls) { + $("#zone1alerts").html("<div class=\"alert alert-info\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\">×</button>" + text + "</div>"); +} + +function show_dependencies_modal(layerId, layerName, layerURL, dependencies) { + // update layer name + $('.layer-name').text(layerName); + var deplistHtml = ""; + for (var i = 0; i < dependencies.length; i++) { + deplistHtml += "<li><label class=\"checkbox\"><input name=\"dependencies\" value=\"" + deplistHtml += dependencies[i].id; + deplistHtml +="\" type=\"checkbox\" checked=\"checked\"/>"; + deplistHtml += dependencies[i].name; + deplistHtml += "</label></li>"; + } + $('#dependencies_list').html(deplistHtml); + + $("#dependencies_modal_form").submit(function (e) { + e.preventDefault(); + var selected = [layerId]; + $("input[name='dependencies']:checked").map(function () { selected.push(parseInt($(this).val()))}); + + _makeXHREditCall({ 'layerAdd': selected.join(",") }, function () { + var layer_link_list = "<a href='"+layerURL+"'>"+layerName+"</a>"; + for (var i = 0; i < selected.length; i++) { + for (var j = 0; j < dependencies.length; i++) { + if (dependencies[j].id == selected[i]) { + layer_link_list+= ", <a href='"+dependencies[j].layerdetailurl+"'>"+dependencies[j].name+"</a>" + break; + } + } } - }); - // enable add layer button - $('#add-layer-with-deps').removeAttr('disabled'); - - //edit columns functionality (show / hide table columns) - $("input:checkbox").change(); - $("input:checkbox").change(function(){ - var selectedType = $(this).val(); - if($(this).is(":checked")){ - $("."+selectedType).show(); + $('#dependencies_modal').modal('hide'); + show_alert("<strong>"+selected.length+"</strong> layers added to <a href=\"{% url 'project' project.id%}\">{{project.name}}</a>:" + layer_link_list); + }); + }); + $('#dependencies_modal').modal('show'); +} + + +function layerAdd(layerId, layerName, layerURL) { + $.ajax({ + url: '{% url "xhr_datatypeahead" %}', + data: {'type': 'layerdeps','value':layerId}, + success: function(_data) { + if (_data.error != "ok") { + alert(_data.error); + } else { + if (_data.list.length > 0) { + show_dependencies_modal(layerId, layerName, layerURL, _data.list); } - else{ - $("."+selectedType).hide(); + else { + _makeXHREditCall({ 'layerAdd': layerId }, function () { + show_alert("<strong>1</strong> layer added to <a href=\"{% url 'project' project.id%}\">{{project.name}}</a>: <a href=\""+layerURL+"\">" + layerName +"</a>"); + }); } - }); - - //turn edit columns dropdown into a multi-select menu - $('.dropdown-menu input, .dropdown-menu label').click(function(e) { - e.stopPropagation(); - }); - - //show tooltip with applied filter - $('#filtered').tooltip({container:'table', placement:'bottom', delay:{hide:1500}, html:true}); - - $('#filtered').click(function() { - $(this).tooltip('hide'); - }); - - //show layer added tooltip - $("#remove-layer, #add-layer, #add-layer-with-deps2").tooltip({ trigger: 'manual' }); - - // add layer without dependencies - $("#add-layer").click(function(){ - $('#layer-removed').hide(); - $('#layer-added').html('<button type="button" class="close" data-dismiss="alert">×</button><strong>1</strong> layer added to <a href="project-with-targets.html">your project</a>: <a href="#">meta-aarch64</a>').fadeIn(); - $('#add-layer').tooltip('show'); - $("#add-layer").hide(); - $(".add-layers .tooltip").delay(2000).fadeOut(function(){ - $("#remove-layer").delay(300).fadeIn(); - }); - }); - - // add layer with dependencies - $(document).on("click", "#add-layer-dependencies", function() { - $('#layer-removed').hide(); - $('#layer-added').html('<button type="button" class="close" data-dismiss="alert">×</button><strong>3</strong> layers added to <a href="project-with-targets.html">your project</a>: <a href="#">meta-acer</a> and its dependencies <a href="#">meta-android</a> and <a href="#">meta-oe</a>').delay(400).fadeIn(function(){ - $('#add-layer-with-deps').tooltip('show'); - $("#add-layer-with-deps, #add-layer-with-deps").hide(); - $(".add-layers .tooltip").delay(2000).fadeOut(function(){ - $("#remove-layer-with-deps").delay(300).fadeIn(); - }); - }); - }); - - // delete layer - $("#remove-layer").click(function(){ - $('#layer-added').hide(); - $('#layer-removed').show(); - $('#remove-layer').tooltip('show'); - $("#remove-layer").hide(); - $(".add-layers .tooltip").delay(2000).fadeOut(function(){ - $("#add-layer").delay(300).fadeIn(); - }); - }); - - }); + } + } + }) +} + +function button_set(id, state) { + if (state == "add") + { + $("#layer-add-" + id).show(); + $("#layer-del-" + id).hide(); + } + else if (state == "del") + { + $("#layer-add-" + id).hide(); + $("#layer-del-" + id).show(); + } +}; + +function updateButtons(projectLayers) { + var displayedLayers = []; + $(".add-del-layers").map(function () { displayedLayers.push(parseInt($(this).attr('value')))}); + for (var i=0; i < displayedLayers.length; i++) { + if (projectLayers.indexOf(displayedLayers[i]) > -1) { + button_set(displayedLayers[i], "del"); + } + else { + button_set(displayedLayers[i], "add"); + } + } +} + +$(document).ready(function (){ + updateButtons({{projectlayerset}}); +}); </script> diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py index 5fe4a9d86..53f46ff53 100755 --- a/lib/toaster/toastergui/views.py +++ b/lib/toaster/toastergui/views.py @@ -2062,7 +2062,8 @@ if toastermain.settings.MANAGED: return HttpResponse(json.dumps( { "error":"ok", "list" : map( - lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")")}, + lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")"), + "layerdetailurl" : reverse('layerdetails', args=(x.pk,))}, map(lambda x: x.depends_on, queryset_all)) }), content_type = "application/json") @@ -2131,8 +2132,18 @@ if toastermain.settings.MANAGED: (filter_string, search_term, ordering_string) = _search_tuple(request, Layer_Version) queryset_all = Layer_Version.objects.all() + # mock an empty Project if we are outside project context + class _mockProject(object): + id = -1 + class _mockManager(object): + def all(self): + return [] + projectlayer_set = _mockManager() + prj = _mockProject() + if 'project_id' in request.session: - queryset_all = queryset_all.filter(up_branch__in = Branch.objects.filter(name = Project.objects.get(pk = request.session['project_id']).release.name)) + prj = Project.objects.get(pk = request.session['project_id']) + queryset_all = queryset_all.filter(up_branch__in = Branch.objects.filter(name = prj.release.name)) queryset_with_search = _get_queryset(Layer_Version, queryset_all, None, search_term, ordering_string, '-layer__name') queryset = _get_queryset(Layer_Version, queryset_all, filter_string, search_term, ordering_string, '-layer__name') @@ -2142,6 +2153,8 @@ if toastermain.settings.MANAGED: context = { + 'prj' : prj, + 'projectlayerset' : json.dumps(map(lambda x: x.layercommit.id, prj.projectlayer_set.all())), 'objects' : layer_info, 'objectname' : "layers", 'default_orderby' : 'layer__name:+', @@ -2164,7 +2177,7 @@ if toastermain.settings.MANAGED: 'filter': { 'class': 'layer', 'label': 'Show:', - 'options': map(lambda x: (x.name, 'layer_source__pk:' + str(x.id), queryset_with_search.filter(layer_source__pk = x.id).count() ), LayerSource.objects.all()), + 'options': map(lambda x: (x.name + " layers", 'layer_source__pk:' + str(x.id), queryset_with_search.filter(layer_source__pk = x.id).count() ), LayerSource.objects.all()), } }, { 'name': 'Git repository URL', @@ -2188,6 +2201,15 @@ if toastermain.settings.MANAGED: { 'name': 'Add | Delete', 'dclass': 'span2', 'qhelp': "Add or delete layers to / from your project ", + 'filter': { + 'class': 'add-del-layers', + 'label': 'Show:', + 'options': [ + ('Layers added to this project', "projectlayer__project:" + str(prj.id), queryset_with_search.filter(projectlayer__project = prj.id).count()), + ('Layers not added to this project', "projectlayer__project:NOT" + str(prj.id), queryset_with_search.exclude(projectlayer__project = prj.id).count()), + ] + + } }, ] |