diff options
| -rw-r--r-- | bitbake/lib/toaster/toastergui/static/js/importlayer.js | 277 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/static/js/projectapp.js | 6 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/templates/importlayer.html | 109 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html | 68 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/urls.py | 2 | ||||
| -rwxr-xr-x | bitbake/lib/toaster/toastergui/views.py | 107 |
6 files changed, 533 insertions, 36 deletions
diff --git a/bitbake/lib/toaster/toastergui/static/js/importlayer.js b/bitbake/lib/toaster/toastergui/static/js/importlayer.js new file mode 100644 index 0000000000..e2bc1ab607 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/static/js/importlayer.js | |||
| @@ -0,0 +1,277 @@ | |||
| 1 | "use strict" | ||
| 2 | |||
| 3 | function importLayerPageInit (ctx) { | ||
| 4 | |||
| 5 | var layerDepBtn = $("#add-layer-dependency-btn"); | ||
| 6 | var importAndAddBtn = $("#import-and-add-btn"); | ||
| 7 | var layerNameInput = $("#layer-name"); | ||
| 8 | var vcsURLInput = $("#layer-git-repo-url"); | ||
| 9 | var gitRefInput = $("#layer-git-ref"); | ||
| 10 | var layerDepInput = $("#layer-dependency"); | ||
| 11 | var layerNameCtrl = $("#layer-name-ctrl"); | ||
| 12 | var duplicatedLayerName = $("#duplicated-layer-name-hint"); | ||
| 13 | |||
| 14 | var layerDeps = {}; | ||
| 15 | var layerDepsDeps = {}; | ||
| 16 | var currentLayerDepSelection; | ||
| 17 | var validLayerName = /^(\w|-)+$/; | ||
| 18 | |||
| 19 | $("#new-project-button").hide(); | ||
| 20 | |||
| 21 | libtoaster.makeTypeahead(layerDepInput, ctx.xhrDataTypeaheadUrl, { type : "layers", project_id: ctx.projectId, include_added: "true" }, function(item){ | ||
| 22 | currentLayerDepSelection = item; | ||
| 23 | |||
| 24 | layerDepBtn.removeAttr("disabled"); | ||
| 25 | }); | ||
| 26 | |||
| 27 | |||
| 28 | /* We automatically add "openembedded-core" layer for convenience as a | ||
| 29 | * dependency as pretty much all layers depend on this one | ||
| 30 | */ | ||
| 31 | $.getJSON(ctx.xhrDataTypeaheadUrl, { type : "layers", project_id: ctx.projectId, include_added: "true" , value: "openembedded-core" }, function(layer) { | ||
| 32 | if (layer.list.length == 1) { | ||
| 33 | currentLayerDepSelection = layer.list[0]; | ||
| 34 | layerDepBtn.click(); | ||
| 35 | } | ||
| 36 | }); | ||
| 37 | |||
| 38 | layerDepBtn.click(function(){ | ||
| 39 | if (currentLayerDepSelection == undefined) | ||
| 40 | return; | ||
| 41 | |||
| 42 | layerDeps[currentLayerDepSelection.id] = currentLayerDepSelection; | ||
| 43 | |||
| 44 | /* Make a list item for the new layer dependency */ | ||
| 45 | var newLayerDep = $("<li><a></a><span class=\"icon-trash\" data-toggle=\"tooltip\" title=\"Delete\"></span></li>"); | ||
| 46 | |||
| 47 | newLayerDep.data('layer-id', currentLayerDepSelection.id); | ||
| 48 | newLayerDep.children("span").tooltip(); | ||
| 49 | |||
| 50 | var link = newLayerDep.children("a"); | ||
| 51 | link.attr("href", ctx.layerDetailsUrl+String(currentLayerDepSelection.id)); | ||
| 52 | link.text(currentLayerDepSelection.name); | ||
| 53 | link.tooltip({title: currentLayerDepSelection.tooltip, placement: "right"}); | ||
| 54 | |||
| 55 | var trashItem = newLayerDep.children("span"); | ||
| 56 | trashItem.click(function () { | ||
| 57 | var toRemove = $(this).parent().data('layer-id'); | ||
| 58 | delete layerDeps[toRemove]; | ||
| 59 | $(this).parent().fadeOut(function (){ | ||
| 60 | $(this).remove(); | ||
| 61 | }); | ||
| 62 | }); | ||
| 63 | |||
| 64 | $("#layer-deps-list").append(newLayerDep); | ||
| 65 | |||
| 66 | libtoaster.getLayerDepsForProject(ctx.xhrDataTypeaheadUrl, ctx.projectId, currentLayerDepSelection.id, function (data){ | ||
| 67 | /* These are the dependencies of the layer added as a dependency */ | ||
| 68 | if (data.list.length > 0) { | ||
| 69 | currentLayerDepSelection.url = ctx.layerDetailsUrl+currentLayerDepSelection.id; | ||
| 70 | layerDeps[currentLayerDepSelection.id].deps = data.list | ||
| 71 | } | ||
| 72 | |||
| 73 | /* Clear the current selection */ | ||
| 74 | layerDepInput.val(""); | ||
| 75 | currentLayerDepSelection = undefined; | ||
| 76 | layerDepBtn.attr("disabled","disabled"); | ||
| 77 | }, null); | ||
| 78 | }); | ||
| 79 | |||
| 80 | importAndAddBtn.click(function(){ | ||
| 81 | /* arrray of all layer dep ids includes parent and child deps */ | ||
| 82 | var allDeps = []; | ||
| 83 | /* temporary object to use to do a reduce on the dependencies for each | ||
| 84 | * layer dependency added | ||
| 85 | */ | ||
| 86 | var depDeps = {}; | ||
| 87 | |||
| 88 | /* the layers that have dependencies have an extra property "deps" | ||
| 89 | * look in this for each layer and reduce this to a unquie object | ||
| 90 | * of deps. | ||
| 91 | */ | ||
| 92 | for (var key in layerDeps){ | ||
| 93 | if (layerDeps[key].hasOwnProperty('deps')){ | ||
| 94 | for (var dep in layerDeps[key].deps){ | ||
| 95 | var layer = layerDeps[key].deps[dep]; | ||
| 96 | depDeps[layer.id] = layer; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | allDeps.push(layerDeps[key].id); | ||
| 100 | } | ||
| 101 | |||
| 102 | /* we actually want it as an array so convert it now */ | ||
| 103 | var depDepsArray = []; | ||
| 104 | for (var key in depDeps) | ||
| 105 | depDepsArray.push (depDeps[key]); | ||
| 106 | |||
| 107 | if (depDepsArray.length > 0) { | ||
| 108 | var layer = { name: layerNameInput.val(), url: "#", id: -1 }; | ||
| 109 | show_layer_deps_modal(ctx.projectId, layer, depDepsArray, function(selected){ | ||
| 110 | /* Add the accepted dependencies to the allDeps array */ | ||
| 111 | if (selected.length > 0){ | ||
| 112 | allDeps.concat (selected); | ||
| 113 | } | ||
| 114 | import_and_add (); | ||
| 115 | }); | ||
| 116 | } else { | ||
| 117 | import_and_add (); | ||
| 118 | } | ||
| 119 | |||
| 120 | function import_and_add () { | ||
| 121 | /* convert to a csv of all the deps to be added */ | ||
| 122 | var layerDepsCsv = allDeps.join(","); | ||
| 123 | |||
| 124 | var layerData = { | ||
| 125 | name: layerNameInput.val(), | ||
| 126 | vcs_url: vcsURLInput.val(), | ||
| 127 | git_ref: gitRefInput.val(), | ||
| 128 | summary: $("#layer-summary").val(), | ||
| 129 | dir_path: $("#layer-subdir").val(), | ||
| 130 | project_id: ctx.projectId, | ||
| 131 | layer_deps: layerDepsCsv, | ||
| 132 | }; | ||
| 133 | |||
| 134 | $.ajax({ | ||
| 135 | type: "POST", | ||
| 136 | url: ctx.xhrImportLayerUrl, | ||
| 137 | data: layerData, | ||
| 138 | headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, | ||
| 139 | success: function (data) { | ||
| 140 | if (data.error != "ok") { | ||
| 141 | show_error_message(data, layerData); | ||
| 142 | console.log(data.error); | ||
| 143 | } else { | ||
| 144 | /* Success layer import now go to the project page */ | ||
| 145 | window.location.replace(ctx.projectPageUrl+'#/layerimported='+layerData.name); | ||
| 146 | } | ||
| 147 | }, | ||
| 148 | error: function (data) { | ||
| 149 | console.log("Call failed"); | ||
| 150 | console.log(data); | ||
| 151 | } | ||
| 152 | }); | ||
| 153 | } | ||
| 154 | }); | ||
| 155 | |||
| 156 | function show_error_message(error, layerData) { | ||
| 157 | |||
| 158 | var errorMsg = $("#import-error").fadeIn(); | ||
| 159 | var errorType = error.error; | ||
| 160 | var body = errorMsg.children("span"); | ||
| 161 | var title = errorMsg.children("h3"); | ||
| 162 | var optionsList = errorMsg.children("ul"); | ||
| 163 | var invalidLayerRevision = $("#invalid-layer-revision-hint"); | ||
| 164 | var layerRevisionCtrl = $("#layer-revision-ctrl"); | ||
| 165 | |||
| 166 | /* remove any existing items */ | ||
| 167 | optionsList.children().each(function(){ $(this).remove(); }); | ||
| 168 | body.text(""); | ||
| 169 | title.text(""); | ||
| 170 | invalidLayerRevision.hide(); | ||
| 171 | layerNameCtrl.removeClass("error"); | ||
| 172 | layerRevisionCtrl.removeClass("error"); | ||
| 173 | |||
| 174 | switch (errorType){ | ||
| 175 | case 'hint-layer-version-exists': | ||
| 176 | title.text("This layer already exists"); | ||
| 177 | body.html("A layer <strong>"+layerData.name+"</strong> already exists with this Git repository URL and this revision. You can:"); | ||
| 178 | optionsList.append("<li>Import <strong>"+layerData.name+"</strong> with a different revision </li>"); | ||
| 179 | optionsList.append("<li>or <a href=\""+ctx.layerDetailsUrl+error.existing_layer_version+"/\" >change the revision of the existing layer</a></li>"); | ||
| 180 | |||
| 181 | layerRevisionCtrl.addClass("error"); | ||
| 182 | |||
| 183 | invalidLayerRevision.html("A layer <strong>"+layerData.name+"</strong> already exists with this revision.<br />You can import <strong>"+layerData.name+"</strong> with a different revision"); | ||
| 184 | invalidLayerRevision.show(); | ||
| 185 | break; | ||
| 186 | |||
| 187 | case 'hint-layer-exists-with-different-url': | ||
| 188 | title.text("This layer already exists"); | ||
| 189 | body.html("A layer <strong>"+layerData.name+"</strong> already exists with a different Git repository URL:<br /><br />"+error.current_url+"<br /><br />You Can:"); | ||
| 190 | optionsList.append("<li>Import the layer under a different name</li>"); | ||
| 191 | optionsList.append("<li>or <a href=\""+ctx.layerDetailsUrl+error.current_id+"/\" >change the Git repository URL of the existing layer</a></li>"); | ||
| 192 | duplicatedLayerName.html("A layer <strong>"+layerData.name+"</strong> already exists with a different Git repository URL.<br />To import this layer give it a different name."); | ||
| 193 | duplicatedLayerName.show(); | ||
| 194 | layerNameCtrl.addClass("error"); | ||
| 195 | break; | ||
| 196 | |||
| 197 | case 'hint-layer-exists': | ||
| 198 | title.text("This layer already exists"); | ||
| 199 | body.html("A layer <strong>"+layerData.name+"</strong> already exists: You Can:"); | ||
| 200 | optionsList.append("<li>Import the layer under a different name</li>"); | ||
| 201 | break; | ||
| 202 | default: | ||
| 203 | title.text("Error") | ||
| 204 | body.text(data.error); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | function enable_import_btn (enabled) { | ||
| 209 | var importAndAddHint = $("#import-and-add-hint"); | ||
| 210 | |||
| 211 | if (enabled) { | ||
| 212 | importAndAddBtn.removeAttr("disabled"); | ||
| 213 | importAndAddHint.hide(); | ||
| 214 | return; | ||
| 215 | } | ||
| 216 | |||
| 217 | importAndAddBtn.attr("disabled", "disabled"); | ||
| 218 | importAndAddHint.show(); | ||
| 219 | } | ||
| 220 | |||
| 221 | function check_form() { | ||
| 222 | var valid = false; | ||
| 223 | var inputs = $("input:required"); | ||
| 224 | |||
| 225 | for (var i=0; i<inputs.length; i++){ | ||
| 226 | if (!(valid = inputs[i].value)){ | ||
| 227 | enable_import_btn(false); | ||
| 228 | break; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | if (valid) | ||
| 233 | enable_import_btn(true); | ||
| 234 | } | ||
| 235 | |||
| 236 | vcsURLInput.keyup(function() { | ||
| 237 | check_form(); | ||
| 238 | }); | ||
| 239 | |||
| 240 | gitRefInput.keyup(function() { | ||
| 241 | check_form(); | ||
| 242 | }); | ||
| 243 | |||
| 244 | layerNameInput.keyup(function() { | ||
| 245 | if ($(this).val() && !validLayerName.test($(this).val())){ | ||
| 246 | layerNameCtrl.addClass("error") | ||
| 247 | $("#invalid-layer-name-hint").show(); | ||
| 248 | enable_import_btn(false); | ||
| 249 | return; | ||
| 250 | } | ||
| 251 | |||
| 252 | /* Don't remove the error class if we're displaying the error for another | ||
| 253 | * reason. | ||
| 254 | */ | ||
| 255 | if (!duplicatedLayerName.is(":visible")) | ||
| 256 | layerNameCtrl.removeClass("error") | ||
| 257 | |||
| 258 | $("#invalid-layer-name-hint").hide(); | ||
| 259 | check_form(); | ||
| 260 | }); | ||
| 261 | |||
| 262 | /* Have a guess at the layer name */ | ||
| 263 | vcsURLInput.focusout(function (){ | ||
| 264 | /* If we a layer name specified don't overwrite it or if there isn't a | ||
| 265 | * url typed in yet return | ||
| 266 | */ | ||
| 267 | if (layerNameInput.val() || !$(this).val()) | ||
| 268 | return; | ||
| 269 | |||
| 270 | if ($(this).val().search("/")){ | ||
| 271 | var urlPts = $(this).val().split("/"); | ||
| 272 | var suggestion = urlPts[urlPts.length-1].replace(".git",""); | ||
| 273 | layerNameInput.val(suggestion); | ||
| 274 | } | ||
| 275 | }); | ||
| 276 | |||
| 277 | } | ||
diff --git a/bitbake/lib/toaster/toastergui/static/js/projectapp.js b/bitbake/lib/toaster/toastergui/static/js/projectapp.js index e9b07c7848..8e3499a94c 100644 --- a/bitbake/lib/toaster/toastergui/static/js/projectapp.js +++ b/bitbake/lib/toaster/toastergui/static/js/projectapp.js | |||
| @@ -571,6 +571,12 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc | |||
| 571 | "\">select targets</a> you want to build.", "alert-success"); | 571 | "\">select targets</a> you want to build.", "alert-success"); |
| 572 | }); | 572 | }); |
| 573 | 573 | ||
| 574 | _cmdExecuteWithParam("/layerimported", function (layer) { | ||
| 575 | $scope.displayAlert($scope.zone2alerts, | ||
| 576 | "You have imported <strong>" + layer + | ||
| 577 | "</strong> and added it to your project.", "alert-success"); | ||
| 578 | }); | ||
| 579 | |||
| 574 | _cmdExecuteWithParam("/targetbuild=", function (targets) { | 580 | _cmdExecuteWithParam("/targetbuild=", function (targets) { |
| 575 | var oldTargetName = $scope.targetName; | 581 | var oldTargetName = $scope.targetName; |
| 576 | $scope.targetName = targets.split(",").join(" "); | 582 | $scope.targetName = targets.split(",").join(" "); |
diff --git a/bitbake/lib/toaster/toastergui/templates/importlayer.html b/bitbake/lib/toaster/toastergui/templates/importlayer.html index 7e48eac66e..913f951c28 100644 --- a/bitbake/lib/toaster/toastergui/templates/importlayer.html +++ b/bitbake/lib/toaster/toastergui/templates/importlayer.html | |||
| @@ -1,68 +1,115 @@ | |||
| 1 | {% extends "baseprojectpage.html" %} | 1 | {% extends "baseprojectpage.html" %} |
| 2 | {% load projecttags %} | 2 | {% load projecttags %} |
| 3 | {% load humanize %} | 3 | {% load humanize %} |
| 4 | {% load static %} | ||
| 4 | 5 | ||
| 5 | {% block localbreadcrumb %} | 6 | {% block localbreadcrumb %} |
| 6 | <li>Layers</li> | 7 | <li>Import layer</li> |
| 7 | {% endblock %} | 8 | {% endblock %} |
| 8 | 9 | ||
| 9 | {% block projectinfomain %} | 10 | {% block projectinfomain %} |
| 11 | |||
| 12 | <script src="{% static 'js/importlayer.js' %}"></script> | ||
| 13 | <script> | ||
| 14 | $(document).ready(function (){ | ||
| 15 | var ctx = {}; | ||
| 16 | ctx.xhrDataTypeaheadUrl = "{% url 'xhr_datatypeahead' %}"; | ||
| 17 | ctx.layerDetailsUrl = "{% url 'layerdetails' %}"; | ||
| 18 | ctx.xhrImportLayerUrl = "{% url 'xhr_importlayer' %}"; | ||
| 19 | ctx.xhrEditProjectUrl = "{% url 'xhr_projectedit' project.id %}"; | ||
| 20 | ctx.projectPageUrl = "{% url 'project' project.id %}"; | ||
| 21 | ctx.projectId = {{project.id}}; | ||
| 22 | |||
| 23 | try { | ||
| 24 | importLayerPageInit(ctx); | ||
| 25 | } catch(e) { | ||
| 26 | document.write(e.stack); | ||
| 27 | console.log(e); | ||
| 28 | } | ||
| 29 | }); | ||
| 30 | </script> | ||
| 31 | |||
| 10 | <div class="page-header"> | 32 | <div class="page-header"> |
| 11 | <h1>Import layer</h1> | 33 | <h1>Import layer</h1> |
| 12 | </div> | 34 | </div> |
| 35 | |||
| 36 | {% include "layers_dep_modal.html" %} | ||
| 13 | <form> | 37 | <form> |
| 14 | {% if project %} | 38 | {% if project %} |
| 15 | <span class="help-block" style="padding-left:19px;">The layer you are importing must be compatible with {{project.release.name}} ({{project.release.description}}), which is the release you are using in this project.</span> | 39 | <span class="help-block" style="padding-left:19px;">The layer you are importing must be compatible with {{project.release.name}} ({{project.release.description}}), which is the release you are using in this project.</span> |
| 16 | {% endif %} | 40 | {% endif %} |
| 17 | <fieldset class="air"> | 41 | <fieldset class="air"> |
| 18 | <legend>Layer repository information</legend> | 42 | <legend>Layer repository information</legend> |
| 43 | <div class="alert alert-error" id="import-error" style="display:none"> | ||
| 44 | <button type="button" class="close" data-dismiss="alert">×</button> | ||
| 45 | <h3></h3> | ||
| 46 | <span></span> | ||
| 47 | <ul></ul> | ||
| 48 | </div> | ||
| 49 | |||
| 50 | <div class="control-group" id="layer-name-ctrl"> | ||
| 51 | <label class="control-label" for="layer-name"> | ||
| 52 | Layer name | ||
| 53 | <span class="icon-question-sign get-help" title="Something like 'meta-mylayer'. Your layer name must be unique and can only include letters, numbers and dashes" /> | ||
| 54 | </label> | ||
| 55 | <div class="controls"> | ||
| 56 | <input id="layer-name" type="text" required autofocus> | ||
| 57 | <span class="help-inline" style="display: none;" id="invalid-layer-name-hint">A valid layer name can only include letters, numbers and dashes</span> | ||
| 58 | <span class="help-inline" style="display: none;" id="duplicated-layer-name-hint"></span> | ||
| 59 | </div> | ||
| 60 | |||
| 61 | </div> | ||
| 62 | |||
| 19 | <label> | 63 | <label> |
| 20 | Git repository URL | 64 | Git repository URL |
| 21 | <i class="icon-question-sign get-help" title="Fetch/clone URL of the repository. Currently, Toaster only supports Git repositories."></i> | 65 | <span class="icon-question-sign get-help" title="Fetch/clone URL of the repository. Currently, Toaster only supports Git repositories." /> |
| 22 | </label> | 66 | </label> |
| 23 | <input id="repo" type="text" class="input-xxlarge" required> | 67 | |
| 24 | <label class="project-form"> | 68 | <input type="text" id="layer-git-repo-url" class="input-xxlarge" required> |
| 69 | <label class="project-form" for="layer-subdir"> | ||
| 25 | Repository subdirectory | 70 | Repository subdirectory |
| 26 | <span class="muted">(optional)</span> | 71 | <span class="muted">(optional)</span> |
| 27 | <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> | 72 | <span 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)" /> |
| 28 | </label> | ||
| 29 | <input type="text" id="subdir"> | ||
| 30 | <label class="project-form">Branch, tag or commit</label> | ||
| 31 | <input type="text" class="span4" id="layer-version" required> | ||
| 32 | <label class="project-form"> | ||
| 33 | Layer name | ||
| 34 | <i class="icon-question-sign get-help" title="Something like 'meta-mylayer'. Your layer name must be unique and can only include letters, numbers and dashes"></i> | ||
| 35 | </label> | 73 | </label> |
| 36 | <input id="layer-name" type="text" required> | 74 | <input type="text" id="layer-subdir"> |
| 75 | |||
| 76 | <div class="control-group" id="layer-revision-ctrl"> | ||
| 77 | <label class="control-label" for="layer-git-ref">Revision | ||
| 78 | <span class="icon-question-sign get-help" title="You can provide a Git branch, a tag or a commit SHA as the revision"></span> | ||
| 79 | </label> | ||
| 80 | <div class="controls"> | ||
| 81 | <input type="text" class="span4" id="layer-git-ref" required> | ||
| 82 | <span class="help-inline" style="diaply:none;" id="invalid-layer-revision-hint"></span> | ||
| 83 | </div> | ||
| 84 | </div> | ||
| 85 | |||
| 86 | <label class="project-form" for="layer-description">Layer description | ||
| 87 | <span class="muted">(optional)</span> | ||
| 88 | <span class="icon-question-sign get-help" title="Short description for for the layer" /> | ||
| 89 | </label> | ||
| 90 | <input id="layer-description" type="text" class="input-xxlarge" /> | ||
| 91 | |||
| 37 | </fieldset> | 92 | </fieldset> |
| 38 | <fieldset class="air"> | 93 | <fieldset class="air"> |
| 39 | <legend> | 94 | <legend> |
| 40 | Layer dependencies | 95 | Layer dependencies |
| 41 | <span class="muted">(optional)</span> | 96 | <span class="muted">(optional)</span> |
| 42 | <i class="icon-question-sign get-help heading-help" title="Other layers this layer depends upon"></i> | 97 | <span class="icon-question-sign get-help heading-help" title="Other layers this layer depends upon" /> |
| 43 | </legend> | 98 | </legend> |
| 44 | <ul class="unstyled configuration-list"> | 99 | <ul class="unstyled configuration-list" id="layer-deps-list"> |
| 45 | <li> | ||
| 46 | <a href="" class="layer-info" title="OpenEmbedded | daisy">openembedded-core (meta)</a> | ||
| 47 | <i class="icon-trash"></i> | ||
| 48 | </li> | ||
| 49 | </ul> | 100 | </ul> |
| 50 | <div class="input-append"> | 101 | <div class="input-append"> |
| 51 | <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" | 102 | <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" placeholder="Type a layer name" id="layer-dependency" class="input-xlarge"> |
| 52 | data-provide="typeahead" data-source=' | 103 | <a class="btn" type="button" id="add-layer-dependency-btn" disabled> |
| 53 | [] | ||
| 54 | ' placeholder="Type a layer name" id="layer-dependency" class="input-xlarge"> | ||
| 55 | <a class="btn" type="button" id="add-layer-dependency" disabled> | ||
| 56 | Add layer | 104 | Add layer |
| 57 | </a> | 105 | </a> |
| 58 | </div> | 106 | </div> |
| 59 | <span class="help-inline">You can only add layers Toaster knows about</span> | 107 | <span class="help-inline">You can only add layers Toaster knows about</span> |
| 60 | </fieldset> | 108 | </fieldset> |
| 61 | <div class="form-actions"> | 109 | <div class="form-actions" id="form-actions"> |
| 62 | <a href="#dependencies-message" class="btn btn-primary btn-large" data-toggle="modal" data-target="#dependencies-message" disabled>Import and add to project</a> | 110 | <button class="btn btn-primary btn-large" data-toggle="modal" id="import-and-add-btn" data-target="#dependencies-message" disabled>Import and add to project</button> |
| 63 | <a href="layer-details-just-imported.html" class="btn btn-large" disabled>Just import for the moment</a> | 111 | <span class="help-inline" id="import-and-add-hint" style="vertical-align: middle;">To import a layer, you need to enter a repository URL, a branch, tag or commit and a layer name</span> |
| 64 | <span class="help-inline" style="vertical-align: middle;">To import a layer, you need to enter a repository URL, a branch, tag or commit and a layer name</span> | ||
| 65 | </div> | 112 | </div> |
| 66 | </form> | 113 | </form> |
| 67 | 114 | ||
| 68 | {% endblock %} | 115 | {% endblock %} |
diff --git a/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html b/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html new file mode 100644 index 0000000000..821bbda296 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | <!-- 'Layer dependencies modal' --> | ||
| 2 | <div id="dependencies_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true"> | ||
| 3 | <form id="dependencies_modal_form"> | ||
| 4 | <div class="modal-header"> | ||
| 5 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> | ||
| 6 | <h3><span class="layer-name"></span> dependencies</h3> | ||
| 7 | </div> | ||
| 8 | <div class="modal-body"> | ||
| 9 | <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> | ||
| 10 | <ul class="unstyled" id="dependencies_list"> | ||
| 11 | </ul> | ||
| 12 | </div> | ||
| 13 | <div class="modal-footer"> | ||
| 14 | <button class="btn btn-primary" type="submit">Add layers</button> | ||
| 15 | <button class="btn" type="reset" data-dismiss="modal">Cancel</button> | ||
| 16 | </div> | ||
| 17 | </form> | ||
| 18 | </div> | ||
| 19 | |||
| 20 | <script> | ||
| 21 | function show_layer_deps_modal(projectId, layer, dependencies, successAdd) { | ||
| 22 | // update layer name | ||
| 23 | $('.layer-name').text(layer.name); | ||
| 24 | var deplistHtml = ""; | ||
| 25 | for (var i = 0; i < dependencies.length; i++) { | ||
| 26 | deplistHtml += "<li><label class=\"checkbox\"><input name=\"dependencies\" value=\""; | ||
| 27 | deplistHtml += dependencies[i].id; | ||
| 28 | deplistHtml +="\" type=\"checkbox\" checked=\"checked\"/>"; | ||
| 29 | deplistHtml += dependencies[i].name; | ||
| 30 | deplistHtml += "</label></li>"; | ||
| 31 | } | ||
| 32 | $('#dependencies_list').html(deplistHtml); | ||
| 33 | |||
| 34 | var selected = [layer.id]; | ||
| 35 | var layer_link_list = "<a href='"+layer.url+"'>"+layer.name+"</a>"; | ||
| 36 | |||
| 37 | $("#dependencies_modal_form").submit(function (e) { | ||
| 38 | e.preventDefault(); | ||
| 39 | $("input[name='dependencies']:checked").map(function () { selected.push(parseInt($(this).val()))}); | ||
| 40 | if (selected.length > 1) { | ||
| 41 | tooltipUpdateText = "" + selected.length + " layers added"; | ||
| 42 | } else { | ||
| 43 | tooltipUpdateText = "1 layer added"; | ||
| 44 | } | ||
| 45 | |||
| 46 | for (var i = 0; i < selected.length; i++) { | ||
| 47 | for (var j = 0; j < dependencies.length; j++) { | ||
| 48 | if (dependencies[j].id == selected[i]) { | ||
| 49 | layer_link_list+= ", <a href='"+dependencies[j].layerdetailurl+"'>"+dependencies[j].name+"</a>" | ||
| 50 | break; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | $('#dependencies_modal').modal('hide'); | ||
| 56 | |||
| 57 | var editProjectUrl = "{% url 'xhr_projectedit' project.id %}"; | ||
| 58 | libtoaster.editProject(editProjectUrl, projectId, { 'layerAdd': selected.join(",") }, function () { | ||
| 59 | if (successAdd) { | ||
| 60 | successAdd(selected); | ||
| 61 | } | ||
| 62 | }, function () { | ||
| 63 | console.log ("Adding layers to project failed"); | ||
| 64 | }); | ||
| 65 | }); | ||
| 66 | $('#dependencies_modal').modal('show'); | ||
| 67 | } | ||
| 68 | </script> | ||
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index b60f7614af..6e1b0ab913 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py | |||
| @@ -76,6 +76,7 @@ urlpatterns = patterns('toastergui.views', | |||
| 76 | 76 | ||
| 77 | url(r'^layers/$', 'layers', name='layers'), | 77 | url(r'^layers/$', 'layers', name='layers'), |
| 78 | url(r'^layer/(?P<layerid>\d+)/$', 'layerdetails', name='layerdetails'), | 78 | url(r'^layer/(?P<layerid>\d+)/$', 'layerdetails', name='layerdetails'), |
| 79 | url(r'^layer/$', 'layerdetails', name='layerdetails'), | ||
| 79 | url(r'^targets/$', 'targets', name='targets'), | 80 | url(r'^targets/$', 'targets', name='targets'), |
| 80 | url(r'^machines/$', 'machines', name='machines'), | 81 | url(r'^machines/$', 'machines', name='machines'), |
| 81 | 82 | ||
| @@ -92,6 +93,7 @@ urlpatterns = patterns('toastergui.views', | |||
| 92 | url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'), | 93 | url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'), |
| 93 | 94 | ||
| 94 | url(r'^xhr_datatypeahead/$', 'xhr_datatypeahead', name='xhr_datatypeahead'), | 95 | url(r'^xhr_datatypeahead/$', 'xhr_datatypeahead', name='xhr_datatypeahead'), |
| 96 | url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'), | ||
| 95 | 97 | ||
| 96 | 98 | ||
| 97 | # default redirection | 99 | # default redirection |
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index 434e1180b0..ec055d392a 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py | |||
| @@ -30,6 +30,7 @@ from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File | |||
| 30 | from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact | 30 | from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact |
| 31 | from django.views.decorators.cache import cache_control | 31 | from django.views.decorators.cache import cache_control |
| 32 | from django.core.urlresolvers import reverse | 32 | from django.core.urlresolvers import reverse |
| 33 | from django.core.exceptions import MultipleObjectsReturned | ||
| 33 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | 34 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger |
| 34 | from django.http import HttpResponseBadRequest, HttpResponseNotFound | 35 | from django.http import HttpResponseBadRequest, HttpResponseNotFound |
| 35 | from django.utils import timezone | 36 | from django.utils import timezone |
| @@ -2016,8 +2017,9 @@ if toastermain.settings.MANAGED: | |||
| 2016 | "name" : x.layercommit.layer.name, | 2017 | "name" : x.layercommit.layer.name, |
| 2017 | "giturl": x.layercommit.layer.vcs_url, | 2018 | "giturl": x.layercommit.layer.vcs_url, |
| 2018 | "url": x.layercommit.layer.layer_index_url, | 2019 | "url": x.layercommit.layer.layer_index_url, |
| 2019 | "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), | 2020 | "layerdetailurl": reverse("layerdetails", args=(x.layercommit.pk,)), |
| 2020 | "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, | 2021 | # This branch name is actually the release |
| 2022 | "branch" : { "name" : x.layercommit.commit, "layersource" : x.layercommit.up_branch.layer_source.name}}, | ||
| 2021 | prj.projectlayer_set.all().order_by("id")), | 2023 | prj.projectlayer_set.all().order_by("id")), |
| 2022 | "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()), | 2024 | "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()), |
| 2023 | "freqtargets": freqtargets, | 2025 | "freqtargets": freqtargets, |
| @@ -2164,7 +2166,7 @@ if toastermain.settings.MANAGED: | |||
| 2164 | 2166 | ||
| 2165 | 2167 | ||
| 2166 | def _lv_to_dict(x): | 2168 | def _lv_to_dict(x): |
| 2167 | return {"id": x.pk, "name": x.layer.name, | 2169 | return {"id": x.pk, "name": x.layer.name, "tooltip": x.layer.vcs_url+" | "+x.commit, |
| 2168 | "detail": "(" + x.layer.vcs_url + (")" if x.up_branch == None else " | "+x.up_branch.name+")"), | 2170 | "detail": "(" + x.layer.vcs_url + (")" if x.up_branch == None else " | "+x.up_branch.name+")"), |
| 2169 | "giturl": x.layer.vcs_url, "layerdetailurl" : reverse('layerdetails', args=(x.pk,))} | 2171 | "giturl": x.layer.vcs_url, "layerdetailurl" : reverse('layerdetails', args=(x.pk,))} |
| 2170 | 2172 | ||
| @@ -2174,8 +2176,9 @@ if toastermain.settings.MANAGED: | |||
| 2174 | # all layers for the current project | 2176 | # all layers for the current project |
| 2175 | queryset_all = prj.compatible_layerversions().filter(layer__name__icontains=request.GET.get('value','')) | 2177 | queryset_all = prj.compatible_layerversions().filter(layer__name__icontains=request.GET.get('value','')) |
| 2176 | 2178 | ||
| 2177 | # but not layers with equivalent layers already in project | 2179 | # but not layers with equivalent layers already in project |
| 2178 | queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()])[:8] | 2180 | if not request.GET.has_key('include_added'): |
| 2181 | queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()])[:8] | ||
| 2179 | 2182 | ||
| 2180 | # and show only the selected layers for this project | 2183 | # and show only the selected layers for this project |
| 2181 | final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset_all]) | 2184 | final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset_all]) |
| @@ -2243,6 +2246,100 @@ if toastermain.settings.MANAGED: | |||
| 2243 | return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") | 2246 | return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") |
| 2244 | 2247 | ||
| 2245 | 2248 | ||
| 2249 | def xhr_importlayer(request): | ||
| 2250 | if (not request.POST.has_key('vcs_url') or | ||
| 2251 | not request.POST.has_key('name') or | ||
| 2252 | not request.POST.has_key('git_ref') or | ||
| 2253 | not request.POST.has_key('project_id')): | ||
| 2254 | return HttpResponse(jsonfilter({"error": "Missing parameters; requires vcs_url, name, git_ref and project_id"}), content_type = "application/json") | ||
| 2255 | |||
| 2256 | # Rudimentary check for any possible html tags | ||
| 2257 | if "<" in request.POST: | ||
| 2258 | return HttpResponse(jsonfilter({"error": "Invalid character <"}), content_type = "application/json") | ||
| 2259 | |||
| 2260 | prj = Project.objects.get(pk=request.POST['project_id']) | ||
| 2261 | |||
| 2262 | # Strip trailing/leading whitespace from all values | ||
| 2263 | # put into a new dict because POST one is immutable | ||
| 2264 | post_data = dict() | ||
| 2265 | for key,val in request.POST.iteritems(): | ||
| 2266 | post_data[key] = val.strip() | ||
| 2267 | |||
| 2268 | |||
| 2269 | # We need to know what release the current project is so that we | ||
| 2270 | # can set the imported layer's up_branch_id | ||
| 2271 | prj_branch_name = Release.objects.get(pk=prj.release_id).branch_name | ||
| 2272 | up_branch, branch_created = Branch.objects.get_or_create(name=prj_branch_name, layer_source_id=LayerSource.TYPE_IMPORTED) | ||
| 2273 | |||
| 2274 | layer_source = LayerSource.objects.get(sourcetype=LayerSource.TYPE_IMPORTED) | ||
| 2275 | try: | ||
| 2276 | layer, layer_created = Layer.objects.get_or_create(name=post_data['name']) | ||
| 2277 | except MultipleObjectsReturned: | ||
| 2278 | return HttpResponse(jsonfilter({"error": "hint-layer-exists"}), content_type = "application/json") | ||
| 2279 | |||
| 2280 | if layer: | ||
| 2281 | if layer_created: | ||
| 2282 | layer.layer_source = layer_source | ||
| 2283 | layer.vcs_url = post_data['vcs_url'] | ||
| 2284 | if post_data.has_key('summary'): | ||
| 2285 | layer.summary = layer.description = post_data['summary'] | ||
| 2286 | |||
| 2287 | layer.up_date = timezone.now() | ||
| 2288 | layer.save() | ||
| 2289 | else: | ||
| 2290 | # We have an existing layer by this name, let's see if the git | ||
| 2291 | # url is the same, if it is then we can just create a new layer | ||
| 2292 | # version for this layer. Otherwise we need to bail out. | ||
| 2293 | if layer.vcs_url != post_data['vcs_url']: | ||
| 2294 | return HttpResponse(jsonfilter({"error": "hint-layer-exists-with-different-url" , "current_url" : layer.vcs_url, "current_id": layer.id }), content_type = "application/json") | ||
| 2295 | |||
| 2296 | |||
| 2297 | layer_version, version_created = Layer_Version.objects.get_or_create(layer_source=layer_source, layer=layer, project=prj, up_branch_id=up_branch.id,branch=post_data['git_ref'], commit=post_data['git_ref'], dirpath=post_data['dir_path']) | ||
| 2298 | |||
| 2299 | if layer_version: | ||
| 2300 | if not version_created: | ||
| 2301 | return HttpResponse(jsonfilter({"error": "hint-layer-version-exists", "existing_layer_version": layer_version.id }), content_type = "application/json") | ||
| 2302 | |||
| 2303 | layer_version.up_date = timezone.now() | ||
| 2304 | layer_version.save() | ||
| 2305 | |||
| 2306 | # Add the dependencies specified for this new layer | ||
| 2307 | if (post_data.has_key("layer_deps") and | ||
| 2308 | version_created and | ||
| 2309 | len(post_data["layer_deps"]) > 0): | ||
| 2310 | for layer_dep_id in post_data["layer_deps"].split(","): | ||
| 2311 | |||
| 2312 | layer_dep_obj = Layer_Version.objects.get(pk=layer_dep_id) | ||
| 2313 | LayerVersionDependency.objects.get_or_create(layer_version=layer_version, depends_on=layer_dep_obj) | ||
| 2314 | # Now add them to the project, we could get an execption | ||
| 2315 | # if the project now contains the exact | ||
| 2316 | # dependency already (like modified on another page) | ||
| 2317 | try: | ||
| 2318 | ProjectLayer.objects.get_or_create(layercommit=layer_dep_obj, project=prj) | ||
| 2319 | except: | ||
| 2320 | pass | ||
| 2321 | |||
| 2322 | |||
| 2323 | # If an old layer version exists in our project then remove it | ||
| 2324 | for prj_layers in ProjectLayer.objects.filter(project=prj): | ||
| 2325 | dup_layer_v = Layer_Version.objects.filter(id=prj_layers.layercommit_id, layer_id=layer.id) | ||
| 2326 | if len(dup_layer_v) >0 : | ||
| 2327 | prj_layers.delete() | ||
| 2328 | |||
| 2329 | # finally add the imported layer (version id) to the project | ||
| 2330 | ProjectLayer.objects.create(layercommit=layer_version, project=prj,optional=1) | ||
| 2331 | |||
| 2332 | else: | ||
| 2333 | # We didn't create a layer version so back out now and clean up. | ||
| 2334 | if layer_created: | ||
| 2335 | layer.delete() | ||
| 2336 | |||
| 2337 | return HttpResponse(jsonfilter({"error": "Uncaught error: Could not create layer version"}), content_type = "application/json") | ||
| 2338 | |||
| 2339 | |||
| 2340 | return HttpResponse(jsonfilter({"error": "ok"}), content_type = "application/json") | ||
| 2341 | |||
| 2342 | |||
| 2246 | 2343 | ||
| 2247 | def importlayer(request): | 2344 | def importlayer(request): |
| 2248 | template = "importlayer.html" | 2345 | template = "importlayer.html" |
