diff options
author | Michael Wood <michael.g.wood@intel.com> | 2014-11-28 20:12:18 +0000 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2014-12-18 10:24:07 +0000 |
commit | 1605cd37dbfcd75043f57dd527e0bfc0a79e1e78 (patch) | |
tree | 505bf86cdde6993010c5a153d153b3206ab98cf2 /bitbake | |
parent | 13141af70813d84b27e41d7a6e5792c748b3ae90 (diff) | |
download | poky-1605cd37dbfcd75043f57dd527e0bfc0a79e1e78.tar.gz |
bitbake: toaster: Add import layer feature.
This feature allows users to import layers from git into their current
project and associate it with the release of the current project and the
dependencies for the newly imported layer with existing layers.
It will also resolve the child dependencies of the dependencies added.
[YOCTO #6595]
(Bitbake rev: 017f5c746e894f9d87d927c848386459ea332378)
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake')
-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" |