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" |