summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bitbake/lib/toaster/toastergui/static/css/default.css1
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/layerdetails.js404
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/libtoaster.js35
-rw-r--r--bitbake/lib/toaster/toastergui/templates/layerdetails.html644
-rw-r--r--bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html9
-rw-r--r--bitbake/lib/toaster/toastergui/urls.py1
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py94
7 files changed, 1038 insertions, 150 deletions
diff --git a/bitbake/lib/toaster/toastergui/static/css/default.css b/bitbake/lib/toaster/toastergui/static/css/default.css
index 199c7531dc..a3fa0ddf6a 100644
--- a/bitbake/lib/toaster/toastergui/static/css/default.css
+++ b/bitbake/lib/toaster/toastergui/static/css/default.css
@@ -232,3 +232,4 @@ dd > span { line-height: 20px; }
232.animate-repeat.ng-enter.ng-enter-active { 232.animate-repeat.ng-enter.ng-enter-active {
233 opacity:1; 233 opacity:1;
234} 234}
235.tab-pane table { margin-top: 10px; }
diff --git a/bitbake/lib/toaster/toastergui/static/js/layerdetails.js b/bitbake/lib/toaster/toastergui/static/js/layerdetails.js
new file mode 100644
index 0000000000..a5a6330630
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/static/js/layerdetails.js
@@ -0,0 +1,404 @@
1"use strict"
2
3function layerDetailsPageInit (ctx) {
4
5 var layerDepInput = $("#layer-dep-input");
6 var layerDepBtn = $("#add-layer-dependency-btn");
7 var layerDepsList = $("#layer-deps-list");
8 var currentLayerDepSelection;
9 var addRmLayerBtn = $("#add-remove-layer-btn");
10
11 /* setup the dependencies typeahead */
12 libtoaster.makeTypeahead(layerDepInput, ctx.xhrDataTypeaheadUrl, { type : "layers", project_id: ctx.projectId, include_added: "true" }, function(item){
13 currentLayerDepSelection = item;
14
15 layerDepBtn.removeAttr("disabled");
16 });
17
18 function addRemoveDep(depLayerId, add, doneCb) {
19 var data = { layer_version_id : ctx.layerVersion.id };
20 if (add)
21 data.add_dep = depLayerId;
22 else
23 data.rm_dep = depLayerId;
24
25 $.ajax({
26 type: "POST",
27 url: ctx.xhrUpdateLayerUrl,
28 data: data,
29 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
30 success: function (data) {
31 if (data.error != "ok") {
32 console.warn(data.error);
33 } else {
34 doneCb();
35 }
36 },
37 error: function (data) {
38 console.warn("Call failed");
39 console.warn(data);
40 }
41 });
42 }
43
44 function layerRemoveClick() {
45 var toRemove = $(this).parent().data('layer-id');
46 var layerDepItem = $(this);
47
48 addRemoveDep(toRemove, false, function(){
49 layerDepItem.parent().fadeOut(function (){
50 layerDepItem.remove();
51 });
52 });
53 }
54
55 /* Add dependency layer button click handler */
56 layerDepBtn.click(function(){
57 if (currentLayerDepSelection == undefined)
58 return;
59
60 addRemoveDep(currentLayerDepSelection.id, true, function(){
61 /* Make a list item for the new layer dependency */
62 var newLayerDep = $("<li><a></a><span class=\"icon-trash\" data-toggle=\"tooltip\" title=\"Delete\"></span></li>");
63
64 newLayerDep.data('layer-id', currentLayerDepSelection.id);
65 newLayerDep.children("span").tooltip();
66
67 var link = newLayerDep.children("a");
68 link.attr("href", ctx.layerDetailsUrl+String(currentLayerDepSelection.id));
69 link.text(currentLayerDepSelection.name);
70 link.tooltip({title: currentLayerDepSelection.tooltip, placement: "right"});
71
72 /* Connect up the tash icon */
73 var trashItem = newLayerDep.children("span");
74 trashItem.click(layerRemoveClick);
75
76 layerDepsList.append(newLayerDep);
77 /* Clear the current selection */
78 layerDepInput.val("");
79 currentLayerDepSelection = undefined;
80 layerDepBtn.attr("disabled","disabled");
81 });
82 });
83
84 $(".icon-pencil").click(function (){
85 var mParent = $(this).parent("dd");
86 mParent.prev().css("margin-top", "10px");
87 mParent.children("form").slideDown();
88 var currentVal = mParent.children(".current-value");
89 currentVal.hide();
90 /* Set the current value to the input field */
91 mParent.find("textarea,input").val(currentVal.text());
92 /* Hides the "Not set" text */
93 mParent.children(".muted").hide();
94 /* We're editing so hide the delete icon */
95 mParent.children(".delete-current-value").hide();
96 mParent.find(".cancel").show();
97 $(this).hide();
98 });
99
100 $(".delete-current-value").click(function(){
101 var mParent = $(this).parent("dd");
102 mParent.find("input").val("");
103 mParent.find("textarea").val("");
104 mParent.find(".change-btn").click();
105 });
106
107 $(".cancel").click(function(){
108 var mParent = $(this).parents("dd");
109 $(this).hide();
110 mParent.children("form").slideUp(function(){
111 mParent.children(".current-value").show();
112 /* Show the "Not set" text if we ended up with no value */
113 if (!mParent.children(".current-value").html()){
114 mParent.children(".muted").fadeIn();
115 mParent.children(".delete-current-value").hide();
116 } else {
117 mParent.children(".delete-current-value").show();
118 }
119
120 mParent.children(".icon-pencil").show();
121 mParent.prev().css("margin-top", "0px");
122 });
123 });
124
125 $(".build-target-btn").click(function(){
126 /* fire a build */
127 var target = $(this).data('target-name');
128 libtoaster.startABuild(ctx.projectBuildUrl, ctx.projectId, target, null, null);
129 window.location.replace(ctx.projectPageUrl);
130 });
131
132 $(".select-machine-btn").click(function(){
133 var data = { machineName : $(this).data('machine-name') };
134 libtoaster.editProject(ctx.xhrEditProjectUrl, ctx.projectId, data,
135 function (){
136 window.location.replace(ctx.projectPageUrl);
137 }, null);
138 });
139
140 function defaultAddBtnText(){
141 var text = " Add the "+ctx.layerVersion.name+" layer to your project";
142 addRmLayerBtn.text(text);
143 addRmLayerBtn.prepend("<span class=\"icon-plus\"></span>");
144 addRmLayerBtn.removeClass("btn-danger");
145 }
146
147 $("#details-tab").on('show', function(){
148 if (!ctx.layerVersion.inCurrentPrj)
149 defaultAddBtnText();
150
151 window.location.hash = "details";
152 });
153
154 function targetsTabShow(){
155 if (!ctx.layerVersion.inCurrentPrj){
156 if (ctx.numTargets > 0) {
157 var text = " Add the "+ctx.layerVersion.name+" layer to your project "+
158 "to enable these targets";
159 addRmLayerBtn.text(text);
160 addRmLayerBtn.prepend("<span class=\"icon-plus\"></span>");
161 } else {
162 defaultAddBtnText();
163 }
164 }
165
166 window.location.hash = "targets";
167 }
168
169 $("#targets-tab").on('show', targetsTabShow);
170
171 function machinesTabShow(){
172 if (!ctx.layerVersion.inCurrentPrj) {
173 if (ctx.numMachines > 0){
174 var text = " Add the "+ctx.layerVersion.name+" layer to your project " +
175 "to enable these machines";
176 addRmLayerBtn.text(text);
177 addRmLayerBtn.prepend("<span class=\"icon-plus\"></span>");
178 } else {
179 defaultAddBtnText();
180 }
181 }
182
183 window.location.hash = "machines";
184 }
185
186 $("#machines-tab").on('show', machinesTabShow);
187
188 $(".pagesize").change(function(){
189 var search = libtoaster.parseUrlParams();
190 search.limit = this.value;
191
192 window.location.search = libtoaster.dumpsUrlParams(search);
193 });
194
195 /* Enables the Build target and Select Machine buttons and switches the
196 * add/remove button
197 */
198 function setLayerInCurrentPrj(added, depsList) {
199 ctx.layerVersion.inCurrentPrj = added;
200 var alertMsg = $("#alert-msg");
201 /* Reset alert message */
202 alertMsg.text("");
203
204 if (added){
205 /* enable and switch all the button states */
206 $(".build-target-btn").removeAttr("disabled");
207 $(".select-machine-btn").removeAttr("disabled");
208 addRmLayerBtn.addClass("btn-danger");
209 addRmLayerBtn.data('directive', "remove");
210 addRmLayerBtn.text(" Delete the "+ctx.layerVersion.name+" layer from your project");
211 addRmLayerBtn.prepend("<span class=\"icon-trash\"></span>");
212
213 if (depsList) {
214 alertMsg.append("You have added <strong>"+(depsList.length+1)+"</strong> layers: <span id=\"layer-affected-name\"></span> and its dependencies ");
215
216 /* Build the layer deps list */
217 depsList.map(function(layer, i){
218 var link = $("<a></a>");
219
220 link.attr("href", layer.layerdetailurl);
221 link.text(layer.name);
222 link.tooltip({title: layer.tooltip});
223
224 if (i != 0)
225 alertMsg.append(", ");
226
227 alertMsg.append(link);
228 });
229 } else {
230 alertMsg.append("You have added <strong>1</strong> layer: <span id=\"layer-affected-name\"></span>");
231 }
232 } else {
233 /* disable and switch all the button states */
234 $(".build-target-btn").attr("disabled","disabled");
235 $(".select-machine-btn").attr("disabled", "disabled");
236 addRmLayerBtn.removeClass("btn-danger");
237 addRmLayerBtn.data('directive', "add");
238
239 /* "special" handler so that we get the correct button text which depends
240 * on which tab is currently visible. Unfortunately we can't just call
241 * tab('show') as if it's already visible it doesn't run the event.
242 */
243 switch ($(".nav-pills .active a").prop('id')){
244 case 'machines-tab':
245 machinesTabShow();
246 break;
247 case 'targets-tab':
248 targetsTabShow();
249 break;
250 default:
251 defaultAddBtnText();
252 break;
253 }
254
255 alertMsg.append("You have deleted <strong>1</strong> layer: <span id=\"layer-affected-name\"></span>");
256 }
257
258 alertMsg.children("#layer-affected-name").text(ctx.layerVersion.name);
259 $("#alert-area").show();
260 }
261
262 /* Add or remove this layer from the project */
263 addRmLayerBtn.click(function() {
264 var directive = $(this).data('directive');
265
266 if (directive == 'add') {
267 /* If adding get the deps for this layer */
268 libtoaster.getLayerDepsForProject(ctx.xhrDataTypeaheadUrl, ctx.projectId, ctx.layerVersion.id, function (data) {
269 /* got result for dependencies */
270 if (data.list.length == 0){
271 var editData = { layerAdd : ctx.layerVersion.id };
272 libtoaster.editProject(ctx.xhrEditProjectUrl, ctx.projectId, editData,
273 function() {
274 setLayerInCurrentPrj(true);
275 });
276 return;
277 } else {
278 /* The add deps will include this layer so no need to add it
279 * separately.
280 */
281 show_layer_deps_modal(ctx.projectId, ctx.layerVersion, data.list, null, null, true, function () {
282 /* Success add deps and layer */
283 setLayerInCurrentPrj(true, data.list);
284 });
285 }
286 }, null);
287 } else if (directive == 'remove') {
288 var editData = { layerDel : ctx.layerVersion.id };
289
290 libtoaster.editProject(ctx.xhrEditProjectUrl, ctx.projectId, editData,
291 function () {
292 /* Success removed layer */
293 //window.location.reload();
294 setLayerInCurrentPrj(false);
295 }, function () {
296 console.warn ("Removing layer from project failed");
297 });
298 }
299 });
300
301 /* Handler for all of the Change buttons */
302 $(".change-btn").click(function(){
303 var mParent = $(this).parent();
304 var prop = $(this).data('layer-prop');
305
306 /* We have inputs, select and textareas to potentially grab the value
307 * from.
308 */
309 var entryElement = mParent.find("input");
310 if (entryElement.length == 0)
311 entryElement = mParent.find("textarea");
312 if (entryElement.length == 0)
313 entryElement = mParent.find("select");
314 if (entryElement.length == 0) {
315 console.warn("Could not find element to get data from for this change");
316 return;
317 }
318
319 var data = { layer_version_id: ctx.layerVersion.id };
320 data[prop] = entryElement.val();
321
322 $.ajax({
323 type: "POST",
324 url: ctx.xhrUpdateLayerUrl,
325 data: data,
326 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
327 success: function (data) {
328 if (data.error != "ok") {
329 console.warn(data.error);
330 } else {
331 /* success layer property changed */
332 var inputArea = mParent.parents("dd");
333 var text;
334 /* We don't actually want the value from the select option we want
335 * the text that represents the value to display
336 */
337 text = entryElement.children("option:selected").text();
338 if (!text)
339 text = entryElement.val();
340
341 /* Hide the "Not set" text if it's visible */
342 inputArea.find(".muted").hide();
343 inputArea.find(".current-value").text(text);
344 /* Same behaviour as cancel in that we hide the form/show current
345 * value.
346 */
347 inputArea.find(".cancel").click();
348 }
349 },
350 error: function (data) {
351 console.warn("Call failed");
352 console.warn(data);
353 }
354 });
355 });
356
357 /* Disable the change button when we have no data in the input */
358 $("dl input, dl textarea").keyup(function() {
359 if ($(this).val().length == 0)
360 $(this).parent().children(".change-btn").attr("disabled", "disabled");
361 else
362 $(this).parent().children(".change-btn").removeAttr("disabled");
363 });
364
365 /* This checks to see if the dt's dd has data in it or if the change data
366 * form is visible, otherwise hide it
367 */
368 $("dl").children().each(function (){
369 if ($(this).is("dt")) {
370 var dd = $(this).next("dd");
371 if (!dd.children("form:visible")|| !dd.find(".current-value").html()){
372 if (ctx.layerVersion.sourceId == 3){
373 /* There's no current value and the layer is editable
374 * so show the "Not set" and hide the delete icon
375 */
376 dd.find(".muted").show();
377 dd.find(".delete-current-value").hide();
378 } else {
379 /* We're not viewing an editable layer so hide the empty dd/dl pair */
380 $(this).hide();
381 dd.hide();
382 }
383 }
384 }
385 });
386
387 /* Clear the current search selection and reload the results */
388 $("#target-search-clear").click(function(){
389 $("#target-search").val("");
390 $(this).parents("form").submit();
391 });
392
393 $("#machine-search-clear").click(function(){
394 $("#machine-search").val("");
395 $(this).parents("form").submit();
396 });
397
398
399 layerDepsList.find(".icon-trash").click(layerRemoveClick);
400 layerDepsList.find("a").tooltip();
401 $(".icon-trash").tooltip();
402 $(".commit").tooltip();
403
404}
diff --git a/bitbake/lib/toaster/toastergui/static/js/libtoaster.js b/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
index a2a0abd45b..04264cd8ba 100644
--- a/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
@@ -161,6 +161,39 @@ var libtoaster = (function (){
161 }); 161 });
162 }; 162 };
163 163
164 /* parses the query string of the current window.location to an object */
165 function _parseUrlParams() {
166 string = window.location.search
167 string = string.substr(1);
168 stringArray = string.split ("&");
169 obj = {};
170
171 for (i in stringArray) {
172 keyVal = stringArray[i].split ("=");
173 obj[keyVal[0]] = keyVal[1];
174 }
175
176 return obj;
177 };
178
179 /* takes a flat object and outputs it as a query string
180 * e.g. the output of dumpsUrlParams
181 */
182 function _dumpsUrlParams(obj) {
183 var str = "?";
184
185 for (key in obj){
186 if (!obj[key])
187 continue;
188
189 str += key+ "="+obj[key].toString();
190 str += "&";
191 }
192
193 return str;
194 };
195
196
164 return { 197 return {
165 reload_params : reload_params, 198 reload_params : reload_params,
166 startABuild : _startABuild, 199 startABuild : _startABuild,
@@ -169,6 +202,8 @@ var libtoaster = (function (){
169 getLayerDepsForProject : _getLayerDepsForProject, 202 getLayerDepsForProject : _getLayerDepsForProject,
170 editProject : _editProject, 203 editProject : _editProject,
171 debug: false, 204 debug: false,
205 parseUrlParams : _parseUrlParams,
206 dumpsUrlParams : _dumpsUrlParams,
172 } 207 }
173})(); 208})();
174 209
diff --git a/bitbake/lib/toaster/toastergui/templates/layerdetails.html b/bitbake/lib/toaster/toastergui/templates/layerdetails.html
index 78dc54bfd1..c69f9e945a 100644
--- a/bitbake/lib/toaster/toastergui/templates/layerdetails.html
+++ b/bitbake/lib/toaster/toastergui/templates/layerdetails.html
@@ -1,159 +1,505 @@
1{% extends "baseprojectpage.html" %} 1{% extends "baseprojectpage.html" %}
2{% load projecttags %} 2{% load projecttags %}
3{% load humanize %} 3{% load humanize %}
4 4{% load static %}
5{% block localbreadcrumb %} 5{% block localbreadcrumb %}
6<li>Layer Details</li> 6<li><a href="{% url 'layers' %}">All Layers</a></li>
7<li>
8 {{layerversion.layer.name}} ({{layerversion.commit|truncatechars:13}})
9</li>
7{% endblock %} 10{% endblock %}
8
9{% block projectinfomain %} 11{% block projectinfomain %}
10<div class="page-header"> 12
11 <h1>Layer Details</h1> 13
12</div> 14<script src="{% static 'js/layerdetails.js' %}"></script>
13 15<script>
14 <div class="row-fluid span7 tabbable"> 16
15 <ul class="nav nav-pills"> 17 $(document).ready(function (){
16 <li class="active"> 18 var ctx = {
17 <a data-toggle="tab" href="#information">Layer details</a> 19 projectBuildUrl : "{% url 'xhr_build' %}",
18 </li> 20 layerDetailsUrl : "{% url 'layerdetails' %}",
19 <li> 21 projectPageUrl : "{% url 'project' project.id %}",
20 <a data-toggle="tab" href="#targets">Targets (0)</a> 22 xhrEditProjectUrl : "{% url 'xhr_projectedit' project.id %}",
21 </li> 23 xhrDataTypeaheadUrl : "{% url 'xhr_datatypeahead' %}",
22 <li> 24 xhrUpdateLayerUrl : "{% url 'xhr_updatelayer' %}",
23 <a data-toggle="tab" href="#machines">Machines (0)</a> 25 projectId : {{project.id}},
24 </li> 26 numTargets : {{total_targets}},
25 <li> 27 numMachines: {{machines|length}},
26 <a data-toggle="tab" href="#classes">Classes (0)</a> 28 layerVersion : {
27 </li> 29 name : "{{layerversion.layer.name}}",
28 <li> 30 id : {{layerversion.id}},
29 <a data-toggle="tab" href="#bbappends">bbappends (0)</a> 31 commit: "{{layerversion.commit}}",
30 </li> 32 inCurrentPrj : {{layer_in_project}},
31 </ul> 33 url : "{% url 'layerdetails' layerversion.id %}",
32 <div class="tab-content"> 34 sourceId: {{layerversion.layer_source_id}},
33 <div name="information" id="information" class="tab-pane active"> 35 }
34 <dl class="dl-horizontal"> 36 };
35 <dt class=""> 37
36 <i class="icon-question-sign get-help" title="Fetch/clone URL of the repository"></i> 38 try {
37 Repository URL 39 layerDetailsPageInit(ctx);
38 </dt> 40 } catch (e) {
39 <dd> 41 document.write("Sorry, An error has occurred loading this page");
40 <form id="change-repo-form" class="control-group"> 42 console.warn(e);
41 <div class="input-append"> 43 }
42 <input type="text" class="input-xlarge" id="type-repo" value="{{layerversion.layer.vcs_url}}"> 44 });
43 <button id="apply-change-repo" class="btn" type="button">Change</button> 45</script>
44 <!--a href="#" id="cancel-change-repo" class="btn btn-link">Cancel</a--> 46
45 </div> 47{# If this is not an imported layer then hide the edit ui #}
46 <span class="help-block">Cloning this Git repository failed</span> 48{% if layerversion.layer_source_id != 3 %}
47 </form> 49<style>
48 </dd> 50 .icon-pencil {
49 <dt> 51 display:none;
50 <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> 52 }
51 Repository subdirectory 53.delete-current-value{
52 </dt> 54 display: none;
53 <dd> 55}
54 <span id="subdir">{{layerversion.dirpath}}</span> 56 li .icon-trash {
55 <i id="change-subdir" class="icon-pencil"></i> 57 display:none;
56 <i id="delete-subdir" class="icon-trash"></i> 58 }
57 <form id="change-subdir-form" style="display:none;"> 59 .add-deps {
58 <div class="input-append"> 60 display:none;
59 <input type="text" id="type-subdir" value="meta-acer"> 61 }
60 <button id="apply-change-subdir" class="btn" type="button">Change</button> 62</style>
61 <a href="#" id="cancel-change-subdir" class="btn btn-link">Cancel</a> 63{% endif %}
62 </div> 64
63 </form> 65{% include "layers_dep_modal.html" %}
64 </dd> 66 <div class="container-fluid top-padded">
65 <dt>Brach, tag or commit</dt> 67 <div class="row-fluid">
66 <dd> 68 <div class="span11">
67 {{layerversion.up_branch.name}} 69 <div class="page-header">
68 <i class="icon-pencil"></i> 70 <h1>{{layerversion.layer.name}} <small class="commit" data-toggle="tooltip" title="{{layerversion.commit}}">({{layerversion.commit|truncatechars:13}})</small></h1>
69 </dd> 71 </div>
70 <dt>
71 <i class="icon-question-sign get-help" title="The Yocto Project versions with which this layer is compatible. Currently Toaster supports Yocto Project 1.6 and 1.7"></i>
72 Yocto Project compatibility
73 </dt>
74 <dd>
75 <i class="icon-pencil"></i>
76 </dd>
77 <dt>
78 <i class="icon-question-sign get-help" title="Other layers this layer depends upon"></i>
79 Layer dependencies
80 </dt>
81 <dd>
82 <ul class="unstyled">
83 {% for ld in layer.dependencies.all %}
84 <li>
85 <a href="#">openembedded core (meta)</a>
86 <i class="icon-trash"></i>
87 </li>
88 {% endfor %}
89 </ul>
90 <div class="input-append">
91 <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off"
92 data-provide="typeahead" data-source='
93 '
94 placeholder="Type a layer name" id="layer-dependency">
95 <a class="btn" type="button" id="add-layer-dependency" disabled>
96 Add layer
97 </a>
98 </div>
99 <span class="help-block">You can only add layers Toaster knows about</span>
100 </dd>
101 </dl>
102 </div>
103 <div name="targets" id="targets" class="tab-pane">
104 <div class="alert alert-info">
105 <strong>There is no target data for {{layerversion.layer.name}} ... yet</strong> <br />
106 Toaster learns about layers as they are built. Once you have used {{layerversion.layer.name}} in a build, Toaster will show you
107 here the targets it provides.
108 </div>
109 </div>
110 <div name="machines" id="machines" class="tab-pane">
111 <div class="alert alert-info">
112 <strong>There is no machine data for {{layerversion.layer.name}} ... yet</strong> <br />
113 Toaster learns about layers as they are built. Once you have used {{layerversion.layer.name}} in a build, Toaster will show you
114 here the machines it provides.
115 </div>
116 </div>
117 </div> 72 </div>
118</div> 73 </div>
119<div class="row span4 well"> 74
120<h2>About {{layerversion.layer.name}}</h2> 75 <div class="row-fluid">
121<dl> 76 <div class="span7">
122 77 <div class="tabbable">
123 <dt> 78 <div class="alert alert-info lead" id="alert-area" style="display:none">
124 Summary 79 <button type="button" class="close" id="dismiss-alert" data-dismiss="alert">&times;</button>
125 <i class="icon-question-sign get-help" title="One-line description of the layer"></i> 80 <span id="alert-msg"></span>
126 </dt> 81 <p style="margin-top:10px;"><a href="{% url 'project' project.id %}">Go to project configuration</a></p>
127 <dd> 82 </div>
128 <span >{{layerversion.layer.summary}}</span> 83 <ul class="nav nav-pills">
129 <i class="icon-pencil"></i> 84 <li class="active">
130 </dd> 85 <a data-toggle="tab" href="#information" id="details-tab">Layer details</a>
131 <!--form> 86 </li>
132 <textarea class="span12" rows="2"></textarea> 87 <li>
133 <button class="btn" type="button">Change</button> 88 <a data-toggle="tab" href="#targets" id="targets-tab">Targets ({{total_targets}})</a>
134 <a href="#" class="btn btn-link">Cancel</a> 89 </li>
135 </form--> 90 <li>
136 <dt> 91 <a data-toggle="tab" href="#machines" id="machines-tab">Machines ({{total_machines}})</a>
137 Description 92 </li>
138 </dt> 93 </ul>
139 <dd> 94 </div>
140 <span >{{layerversion.layer.description}}</span> 95 <div class="tab-content">
141 <i class="icon-pencil"></i> 96 <span class="button-place">
142 </dd> 97 {% if layer_in_project == 0 %}
143 <!--form> 98 <button id="add-remove-layer-btn" data-directive="add" class="btn btn-large btn-block">
144 <textarea class="span12" rows="6"></textarea> 99 <span class="icon-plus"></span>
145 <button class="btn" type="button">Change</button> 100 Add the {{layerversion.layer.name}} layer to your project
146 <a href="#" class="btn btn-link">Cancel</a> 101 </button>
147 </form--> 102 {% else %}
148 <dt> 103 <button id="add-remove-layer-btn" data-directive="remove" class="btn btn-block btn-large btn-danger">
149 Maintainer(s) 104 <span class="icon-trash"></span>
150 </dt> 105 Delete the {{layerversion.layer.name}} layer from your project
151 <dd> 106 </button>
152 <span class="muted">Not set</span> 107 {% endif %}
153 <i class="icon-pencil"></i> 108 </span>
154 </dd> 109
155</dl> 110 <!-- layer details pane -->
156</div> 111 <div name="information" id="information" class="tab-pane active">
112 <dl class="dl-horizontal">
113 <dt class="">
114 <i class="icon-question-sign get-help" title="Fetch/clone URL of the repository"></i>
115 Repository URL
116 </dt>
117 <dd>
118 <span class="current-value">{{layerversion.layer.vcs_url}}</span>
119 {% if layerversion.get_vcs_link_url %}
120 <a href="{{layerversion.get_vcs_link_url}}/" class="icon-share get-info"></a>
121 {% endif %}
122 <form id="change-repo-form" class="control-group" style="display:none">
123 <div class="input-append">
124 <input type="text" class="input-xlarge" value="{{layerversion.layer.vcs_url}}">
125 <button data-layer-prop="vcs_url" class="btn change-btn" type="button">Save</button>
126 <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
127 </div>
128 </form>
129 <i class="icon-pencil" ></i>
130 </dd>
131 <dt>
132 <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>
133 Repository subdirectory
134 </dt>
135 <dd>
136 <span class="muted" style="display:none">Not set</span>
137 <span class="current-value">{{layerversion.dirpath}}</span>
138 {% if layerversion.get_vcs_dirpath_link_url %}
139 <a href="{{layerversion.get_vcs_dirpath_link_url}}" class="icon-share get-info"></a>
140 {% endif %}
141 <form id="change-subdir-form" style="display:none;">
142 <div class="input-append">
143 <input type="text" value="{{layerversion.dirpath}}">
144 <button data-layer-prop="dirpath" class="btn change-btn" type="button">Save</button>
145 <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
146 </div>
147 </form>
148 <i id="change-subdir" class="icon-pencil"></i>
149 <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
150 </dd>
151 <dt>Brach, tag or commit</dt>
152 <dd>
153 <span class="current-value">{{layerversion.commit}}</span>
154 <form style="display:none;">
155 <div class="input-append">
156 <input type="text" value="{{layerversion.commit}}">
157 <button data-layer-prop="commit" class="btn change-btn" type="button">Save</button>
158 <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
159 </div>
160 </form>
161 <i class="icon-pencil"></i>
162 </dd>
163 <dt>
164 <i class="icon-question-sign get-help" title="The Yocto Project versions with which this layer is compatible. Currently Toaster supports Yocto Project 1.6 and 1.7"></i>
165 Yocto Project compatibility
166 </dt>
167 <dd>
168 <span class="current-value">{{layerversion.up_branch.name}}</span>
169 <form style="display:none">
170 <div class="input-append">
171 <select name="projectversion" id="projectversion">
172 {% for compat in yocto_compat %}
173 <option value="{{compat.id}}" {%if layerversion.up_branch.id == compat.id %} selected{%endif%}>{{compat.name}}</option>
174 {% endfor %}
175 </select>
176 <button data-layer-prop="up_branch" class="btn change-btn" type="button">Save</button>
177 <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
178 </div>
179 </form>
180 <i class="icon-pencil"></i>
181 </dd>
182 <dt>
183 <i class="icon-question-sign get-help" title="Other layers this layer depends upon"></i>
184 Layer dependencies
185 </dt>
186 <dd>
187 <ul class="unstyled" id="layer-deps-list">
188 {% for ld in layerversion.dependencies.all %}
189 <span class="current-value">
190 <li data-layer-id="{{ld.depends_on.id}}">
191 <!-- TODO use ld.depends_on.get_vcs_reference instead of commit -->
192 <a data-toggle="tooltip" title="{{ld.depends_on.layer.vcs_url}} | {{ld.depends_on.commit}}" href="{% url 'layerdetails' ld.depends_on.id %}">{{ld.depends_on.layer.name}}</a>
193 <span class="icon-trash " data-toggle="tooltip" title="Delete"></span>
194 </li>
195 </span>
196 {% endfor %}
197 </ul>
198 <div class="input-append add-deps">
199 <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" placeholder="Type a layer name" id="layer-dep-input">
200 <a class="btn" type="button" id="add-layer-dependency-btn" disabled>
201 Add layer
202 </a>
203 </div>
204 <span class="help-block add-deps">You can only add layers Toaster knows about</span>
205 </dd>
206 </dl>
207 </div>
208 <!-- targets tab -->
209 <div name="targets" id="targets" class="tab-pane">
210 {% if total_targets == 0 %}
211 <div class="alert alert-info">
212 <strong>There is no target data for {{layerversion.layer.name}} ... yet</strong> <br />
213 Toaster learns about layers as they are built. Once you have used {{layerversion.layer.name}} in a build, Toaster will show you
214 here the targets it provides.
215 </div>
216 {% else %}
217
218 <div class="row-fluid">
219
220 {% if targets.paginator.count == 0 %}
221 <div class="alert">
222 <h3>No targets found</h3>
223 {% endif %}
224
225 {# only show the search form if we have more than 10 results #}
226 {% if targets.paginator.count > 10 or request.GET.targets_search %}
227 {% if targets.paginator.count == 0 %}
228 <form class="input-append">
229 {% else %}
230 <form class="navbar-search input-append pull-left">
231 {% endif %}
232
233 <input type="text" id="target-search" name="targets_search" placeholder="Search targets" class="input-xlarge" value="{{request.GET.targets_search}}">
234 {% if request.GET.targets_search %}
235 <a class="add-on btn" id="target-search-clear">
236 <i class="icon-remove"></i>
237 </a>
238 {% endif %}
239 <button type="submit" class="btn">Search</button>
240 </form>
241 {% endif %}
242
243 {% if targets.paginator.count == 0 %}
244 <!-- end alert -->
245 </div>
246 <!-- end row-fluid -->
247 </div>
248 {% else %}
249
250 <div class="pull-right">
251 <span class="help-inline" style="padding-top:5px;">Show rows:</span>
252 <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
253 {% with "10 25 50 100 150" as list%}
254 {% for i in list.split %}
255 {% if request.session.limit == i %}
256 <option value="{{i}}" selected>{{i}}</option>
257 {% else %}
258 <option value="{{i}}">{{i}}</option>
259 {% endif %}
260 {% endfor %}
261 {% endwith %}
262 </select>
263 </div>
264 </div>
265
266 <table class="table table-bordered table-hover">
267 <thead>
268 <tr>
269 <th>
270 <i class="icon-question-sign get-help" title="Information about a single piece of software, including where to download the source, configuration options, how to compile the source files and how to package the compiled output"></i>
271 Target
272 {% if request.GET.targets_search %}
273 <span class="badge badge-info">{{targets.paginator.count}}</span>
274 {% endif %}
275 </th>
276 <th>
277 <i class="icon-question-sign get-help" title="The recipe version and revision"></i>
278 Target version
279 </th>
280 <th class="span4">Description</th>
281 <th>Build target</th>
282 </tr>
283 </thead>
284 <tbody>
285 {% for target in targets %}
286 <tr>
287 <td>
288 {{target.name}}
289 {% if target.up_id %}
290 <a href="http://layers.openembedded.org/layerindex/recipe/{{target.up_id}}/" class="icon-share get-info"></a>
291 {% endif %}
292 </td>
293 <td>{{target.version}}</td>
294 <td>{{target.summary}}</td>
295 <td><button class="btn btn-block build-target-btn" data-target-name="{{target.name}}" {% if layer_in_project == 0 %}disabled="disabled"{% endif %} >Build Target</button></td>
296 </tr>
297 {% endfor %}
298 </tbody>
299 </table>
300
301 <!-- Show pagination controls -->
302 <div class="pagination">
303 <ul>
304 {%if targets.has_previous %}
305 <li><a href="?tpage={{targets.previous_page_number}}{{request.GET.limit}}#targets">&laquo;</a></li>
306 {%else%}
307 <li class="disabled"><a href="#">&laquo;</a></li>
308 {%endif%}
309 {% for i in targets.paginator.page_range %}
310 <li {%if i == targets.number %} class="active" {%endif%}><a href="?tpage={{i}}#targets">{{i}}</a></li>
311 {% endfor %}
312 {%if targets.has_next%}
313 <li><a href="?tpage={{targets.next_page_number}}#targets">&raquo;</a></li>
314 {%else%}
315 <li class="disabled"><a href="#">&raquo;</a></li>
316 {%endif%}
317 </ul>
318 <div class="pull-right">
319 <span class="help-inline" style="padding-top:5px;">Show rows:</span>
320 <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
321 {% with "10 25 50 100 150" as list%}
322 {% for i in list.split %}
323 {% if request.session.limit == i %}
324 <option value="{{i}}" selected>{{i}}</option>
325 {% else %}
326 <option value="{{i}}">{{i}}</option>
327 {% endif %}
328 {% endfor %}
329 {% endwith %}
330 </select>
331 </div>
332 </div>
333 {% endif %}
334 {% endif %}
335 </div>
336
337
338 <div name="machines" id="machines" class="tab-pane">
339 {% if total_machines == 0 %}
340 <div class="alert alert-info">
341 <strong>There is no machine data for {{layerversion.layer.name}} ... yet</strong> <br />
342 Toaster learns about layers as they are built. Once you have used {{layerversion.layer.name}} in a build, Toaster will show you
343 here the machines it provides.
344 </div>
345 {% else %}
346
347 <div class="row-fluid">
348
349 {% if machines.paginator.count == 0 %}
350 <div class="alert">
351 <h3>No machines found</h3>
352 {% endif %}
353
354 {# only show the search form if we have more than 10 results #}
355 {% if machines.paginator.count > 10 or request.GET.machines_search %}
356 {% if machines.paginator.count == 0 %}
357 <form class="input-append">
358 {% else %}
359 <form class="navbar-search input-append pull-left">
360 {% endif %}
361
362 <input type="text" id="machine-search" name="machines_search" placeholder="Search machines" class="input-xlarge" value="{{request.GET.machines_search}}">
363 {% if request.GET.machines_search %}
364 <a class="add-on btn" id="machine-search-clear">
365 <i class="icon-remove"></i>
366 </a>
367 {% endif %}
368 <button type="submit" class="btn">Search</button>
369 </form>
370 {% endif %}
371
372 {% if machines.paginator.count == 0 %}
373 <!-- end alert -->
374 </div>
375 <!-- end row-fluid -->
376 </div>
377 {% else %}
378
379 <div class="pull-right">
380 <span class="help-inline" style="padding-top:5px;">Show rows:</span>
381 <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
382 {% with "10 25 50 100 150" as list%}
383 {% for i in list.split %}
384 {% if request.session.limit == i %}
385 <option value="{{i}}" selected>{{i}}</option>
386 {% else %}
387 <option value="{{i}}">{{i}}</option>
388 {% endif %}
389 {% endfor %}
390 {% endwith %}
391 </select>
392 </div>
393 </div>
394
395 <table class="table table-bordered table-hover">
396 <thead>
397 <tr>
398 <th>
399 <i class="icon-question-sign get-help" title="The machine is the hardware for which you are building"></i>
400 Machine
401 {% if request.GET.machines_search %}
402 <span class="badge badge-info">{{machines.paginator.count}}</span>
403 {% endif %}
404 </th>
405 <th>Description</th>
406 <th>Select machine</th>
407 </tr>
408 </thead>
409 <tbody>
410 {% for machine in machines %}
411 <tr>
412 <td>{{machine.name}}</td>
413 <td>{{machine.description}}</td>
414 <td><button class="btn btn-block select-machine-btn" data-machine-name="{{machine.name}}" {% if layer_in_project == 0 %}disabled="disabled"{% endif %}}>Select machine</button></td>
415 </tr>
416 {% endfor %}
417 </tbody>
418 </table>
419
420 <!-- Show pagination controls -->
421 <div class="pagination">
422 <ul>
423 {%if machines.has_previous %}
424 <li><a href="?mpage={{machines.previous_page_number}}{{request.GET.limit}}#machines">&laquo;</a></li>
425 {%else%}
426 <li class="disabled"><a href="#">&laquo;</a></li>
427 {%endif%}
428 {% for i in machines.paginator.page_range %}
429 <li {%if i == machines.number %} class="active" {%endif%}><a href="?mpage={{i}}#machines">{{i}}</a></li>
430 {% endfor %}
431 {%if machines.has_next%}
432 <li><a href="?mpage={{machines.next_page_number}}#machines">&raquo;</a></li>
433 {%else%}
434 <li class="disabled"><a href="#">&raquo;</a></li>
435 {%endif%}
436 </ul>
437 <div class="pull-right">
438 <span class="help-inline" style="padding-top:5px;">Show rows:</span>
439 <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
440 {% with "10 25 50 100 150" as list%}
441 {% for i in list.split %}
442 {% if request.session.limit == i %}
443 <option value="{{i}}" selected>{{i}}</option>
444 {% else %}
445 <option value="{{i}}">{{i}}</option>
446 {% endif %}
447 {% endfor %}
448 {% endwith %}
449 </select>
450 </div>
451 </div>
452 {% endif %}
453 {% endif %}
454 </div>
455 </div>
456 </div>
457 <div class="row span4 well">
458 <h2>About {{layerversion.layer.name}}</h2>
459 <dl>
460
461 <dt>
462 Summary
463 <i class="icon-question-sign get-help" title="One-line description of the layer"></i>
464 </dt>
465 <dd>
466 <span class="muted" style="display:none">Not set</span>
467 <span class="current-value">{{layerversion.layer.summary}}</span>
468 <form style="display:none; margin-bottom:20px">
469 <textarea class="span12" rows="2">{% if layerversion.layer.summary %}{{layerversion.layer.summary}}{% endif %}</textarea>
470 <button class="btn change-btn" data-layer-prop="summary" type="button">Save</button>
471 <a href="#" class="btn btn-link cancel">Cancel</a>
472 </form>
473 <i class="icon-pencil"></i>
474 <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
475 </dd>
476 <dt>
477 Description
478 </dt>
479 <dd>
480 <span class="muted" style="display:none">Not set</span>
481 <span class="current-value">{{layerversion.layer.description}}</span>
482 <form style="display:none; margin-bottom:20px">
483 <textarea class="span12" rows="6">{% if layerversion.layer.description %}{{layerversion.layer.description}}{% endif %}</textarea>
484 <button class="btn change-btn" data-layer-prop="description" type="button" >Save</button>
485 <a href="#" class="btn btn-link cancel">Cancel</a>
486 </form>
487 <i class="icon-pencil"></i>
488 <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
489 </dd>
490 </dd>
491 {% if layerversion.layer.up_id %}
492 <dt>Layer index</dt>
493 <dd>
494 <a href="http://layers.openembedded.org/layerindex/branch/{{layerversion.up_branch.name}}/layer/{{layerversion.layer.name}}"/>layer index link</a>
495
496 </dd>
497 {% endif %}
157 498
499 </dl>
500 </div>
158 501
502 </div>
503 </div>
504 </div>
159{% endblock %} 505{% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html b/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html
index b03fd0b218..8222027d4f 100644
--- a/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html
+++ b/bitbake/lib/toaster/toastergui/templates/layers_dep_modal.html
@@ -18,7 +18,16 @@
18 </div> 18 </div>
19 19
20<script> 20<script>
21 /* projectId: current project
22 * layer: Object representing the parent layer { id: .. name: ... url }
23 * dependencies: array of dependency layer objects { id: .. name: ..}
24 * title: optional override for title
25 * body: optional override for body
26 * addToProject: Whether to add layers to project on accept
27 * successAdd: function to run on success
28 */
21function show_layer_deps_modal(projectId, layer, dependencies, title, body, addToProject, successAdd) { 29function show_layer_deps_modal(projectId, layer, dependencies, title, body, addToProject, successAdd) {
30
22 // update layer name 31 // update layer name
23 if (title) { 32 if (title) {
24 $('#dependencies_modal #title').text(title); 33 $('#dependencies_modal #title').text(title);
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py
index 6e1b0ab913..5c969f814c 100644
--- a/bitbake/lib/toaster/toastergui/urls.py
+++ b/bitbake/lib/toaster/toastergui/urls.py
@@ -94,6 +94,7 @@ urlpatterns = patterns('toastergui.views',
94 94
95 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'), 96 url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'),
97 url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'),
97 98
98 99
99 # default redirection 100 # default redirection
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 8c21ca48e0..7a9d662b31 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2336,6 +2336,50 @@ if toastermain.settings.MANAGED:
2336 2336
2337 return HttpResponse(jsonfilter({"error": "ok", "imported_layer" : { "name" : layer.name, "id": layer_version.id }, "deps_added": layers_added }), content_type = "application/json") 2337 return HttpResponse(jsonfilter({"error": "ok", "imported_layer" : { "name" : layer.name, "id": layer_version.id }, "deps_added": layers_added }), content_type = "application/json")
2338 2338
2339 def xhr_updatelayer(request):
2340
2341 def error_response(error):
2342 return HttpResponse(jsonfilter({"error": error}), content_type = "application/json")
2343
2344 if not request.POST.has_key("layer_version_id"):
2345 return error_response("Please specify a layer version id")
2346 try:
2347 layer_version_id = request.POST["layer_version_id"]
2348 layer_version = Layer_Version.objects.get(id=layer_version_id)
2349 except:
2350 return error_response("Cannot find layer to update")
2351
2352
2353 if request.POST.has_key("vcs_url"):
2354 layer_version.layer.vcs_url = request.POST["vcs_url"]
2355 if request.POST.has_key("dirpath"):
2356 layer_version.dirpath = request.POST["dirpath"]
2357 if request.POST.has_key("commit"):
2358 layer_version.commit = request.POST["commit"]
2359 if request.POST.has_key("up_branch"):
2360 layer_version.up_branch_id = int(request.POST["up_branch"])
2361
2362 if request.POST.has_key("add_dep"):
2363 lvd = LayerVersionDependency(layer_version=layer_version, depends_on_id=request.POST["add_dep"])
2364 lvd.save()
2365
2366 if request.POST.has_key("rm_dep"):
2367 rm_dep = LayerVersionDependency.objects.get(layer_version=layer_version, depends_on_id=request.POST["rm_dep"])
2368 rm_dep.delete()
2369
2370 if request.POST.has_key("summary"):
2371 layer_version.layer.summary = request.POST["summary"]
2372 if request.POST.has_key("description"):
2373 layer_version.layer.description = request.POST["description"]
2374
2375 try:
2376 layer_version.layer.save()
2377 layer_version.save()
2378 except:
2379 return error_response("Could not update layer version entry")
2380
2381 return HttpResponse(jsonfilter({"error": "ok",}), content_type = "application/json")
2382
2339 2383
2340 2384
2341 def importlayer(request): 2385 def importlayer(request):
@@ -2439,8 +2483,53 @@ if toastermain.settings.MANAGED:
2439 2483
2440 def layerdetails(request, layerid): 2484 def layerdetails(request, layerid):
2441 template = "layerdetails.html" 2485 template = "layerdetails.html"
2486 limit = 10
2487
2488 if request.GET.has_key("limit"):
2489 request.session['limit'] = request.GET['limit']
2490
2491 if request.session.has_key('limit'):
2492 limit = request.session['limit']
2493
2494 layer_version = Layer_Version.objects.get(pk = layerid)
2495
2496 # Targets tab query functionality
2497 if request.GET.has_key('targets_search'):
2498 targets = Paginator(Recipe.objects.filter(layer_version=layer_version,name__icontains=request.GET['targets_search']).order_by("name"), limit)
2499 else:
2500 targets = Paginator(Recipe.objects.filter(layer_version=layer_version).order_by("name"), limit)
2501
2502 if request.GET.has_key("tpage"):
2503 try:
2504 targets = targets.page(request.GET['tpage'])
2505 except EmptyPage:
2506 targets = targets.page(targets.num_pages)
2507 else:
2508 targets = targets.page(1)
2509
2510 # Machines tab query functionality
2511 if request.GET.has_key('machines_search'):
2512 machines = Paginator(Machine.objects.filter(layer_version=layer_version,name__icontains=request.GET['machines_search']).order_by("name"), limit)
2513 else:
2514 machines = Paginator(Machine.objects.filter(layer_version=layer_version).order_by("name"), limit)
2515
2516 if request.GET.has_key("mpage"):
2517 try:
2518 machines = machines.page(request.GET['mpage'])
2519 except EmptyPage:
2520 machines = machines.page(machines.num_pages)
2521 else:
2522 machines = machines.page(1)
2523
2442 context = { 2524 context = {
2443 'layerversion': Layer_Version.objects.get(pk = layerid), 2525 'layerversion': layer_version,
2526 'layer_in_project' : ProjectLayer.objects.filter(project_id=request.session['project_id'],layercommit=layerid).count(),
2527 'yocto_compat': Branch.objects.filter(layer_source=layer_version.layer_source),
2528 'machines': machines,
2529 'targets': targets,
2530 'total_targets': Recipe.objects.filter(layer_version=layer_version).count(),
2531
2532 'total_machines': Machine.objects.filter(layer_version=layer_version).count(),
2444 } 2533 }
2445 return render(request, template, context) 2534 return render(request, template, context)
2446 2535
@@ -2972,3 +3061,6 @@ else:
2972 3061
2973 def xhr_importlayer(request): 3062 def xhr_importlayer(request):
2974 raise Exception("page not available in interactive mode") 3063 raise Exception("page not available in interactive mode")
3064
3065 def xhr_updatelayer(request):
3066 raise Exception("page not available in interactive mode")