summaryrefslogtreecommitdiffstats
path: root/bitbake/lib
diff options
context:
space:
mode:
authorMichael Wood <michael.g.wood@intel.com>2015-07-31 15:09:12 +0300
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-08-01 11:26:12 +0100
commit80c1586bbb7f661f37754de5ba637c8d6e2185de (patch)
tree56011e83e1f192a68f4e9014ca266dd6cd76c719 /bitbake/lib
parent258c929973e233f212938ea69eec8f83f3285854 (diff)
downloadpoky-80c1586bbb7f661f37754de5ba637c8d6e2185de.tar.gz
bitbake: toastergui: Add new project page and navigation
This brings in the new project page design and improved navigation. As this also removes the dependency on Angular it also required that the entry points to the project page such as machine-change notifications are also updated. [YOCTO #7329] (Bitbake rev: 6489e6eb5c3b0d59063b6d60521fc33fe563e707) Signed-off-by: Michael Wood <michael.g.wood@intel.com> Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib')
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/base.js81
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/importlayer.js4
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/layerdetails.js5
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/libtoaster.js15
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/projectpage.js429
-rw-r--r--bitbake/lib/toaster/toastergui/templates/base.html7
-rw-r--r--bitbake/lib/toaster/toastergui/templates/baseprojectpage.html2
-rw-r--r--bitbake/lib/toaster/toastergui/templates/importlayer.html12
-rw-r--r--bitbake/lib/toaster/toastergui/templates/machine_btn.html2
-rw-r--r--bitbake/lib/toaster/toastergui/templates/project.html498
-rw-r--r--bitbake/lib/toaster/toastergui/templates/projecttopbar.html26
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py5
12 files changed, 610 insertions, 476 deletions
diff --git a/bitbake/lib/toaster/toastergui/static/js/base.js b/bitbake/lib/toaster/toastergui/static/js/base.js
index 6fd77ea457..0fb1f8b6bf 100644
--- a/bitbake/lib/toaster/toastergui/static/js/base.js
+++ b/bitbake/lib/toaster/toastergui/static/js/base.js
@@ -6,13 +6,20 @@ function basePageInit(ctx) {
6 var newBuildTargetInput; 6 var newBuildTargetInput;
7 var newBuildTargetBuildBtn; 7 var newBuildTargetBuildBtn;
8 8
9 /* initially the current project is used unless overridden by the new build
10 * button in top right nav
11 */
9 var selectedProject = libtoaster.ctx; 12 var selectedProject = libtoaster.ctx;
13
10 var selectedTarget; 14 var selectedTarget;
11 15
12 var newBuildProjectInput = $("#new-build-button #project-name-input"); 16 var newBuildProjectInput = $("#new-build-button #project-name-input");
13 var newBuildProjectSaveBtn = $("#new-build-button #save-project-button"); 17 var newBuildProjectSaveBtn = $("#new-build-button #save-project-button");
14 18
15 $("#config-nav .nav li a").each(function(){ 19
20 _checkProjectBuildable();
21
22 $("#project-topbar .nav li a").each(function(){
16 if (window.location.pathname === $(this).attr('href')) 23 if (window.location.pathname === $(this).attr('href'))
17 $(this).parent().addClass('active'); 24 $(this).parent().addClass('active');
18 else 25 else
@@ -26,7 +33,7 @@ function basePageInit(ctx) {
26 }); 33 });
27 } 34 }
28 35
29 /* Hide the button if we're on the project,newproject or importlyaer page 36 /* Hide the button if we're on the project,newproject or importlyaer page
30 * or if there are no projects yet defined 37 * or if there are no projects yet defined
31 * only show if there isn't already a build-target-input already 38 * only show if there isn't already a build-target-input already
32 */ 39 */
@@ -37,8 +44,8 @@ function basePageInit(ctx) {
37 newBuildTargetInput = $("#new-build-button .build-target-input"); 44 newBuildTargetInput = $("#new-build-button .build-target-input");
38 newBuildTargetBuildBtn = $("#new-build-button .build-button"); 45 newBuildTargetBuildBtn = $("#new-build-button .build-button");
39 46
40 newBuildButton.show();
41 _setupNewBuildButton(); 47 _setupNewBuildButton();
48 newBuildButton.show();
42 } else if ($(".build-target-input").length > 0) { 49 } else if ($(".build-target-input").length > 0) {
43 newBuildTargetInput = $("#project-topbar .build-target-input"); 50 newBuildTargetInput = $("#project-topbar .build-target-input");
44 newBuildTargetBuildBtn = $("#project-topbar .build-button"); 51 newBuildTargetBuildBtn = $("#project-topbar .build-button");
@@ -52,9 +59,35 @@ function basePageInit(ctx) {
52 $('#project .icon-pencil').hide(); 59 $('#project .icon-pencil').hide();
53 } 60 }
54 61
62 libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.projectTargetsUrl, { format: "json" }, function (item) {
63 /* successfully selected a target */
64 selectedTarget = item;
65 });
55 66
56 _checkProjectBuildable(); 67 newBuildTargetInput.on('input', function () {
68 if ($(this).val().length === 0) {
69 newBuildTargetBuildBtn.attr("disabled", "disabled");
70 } else {
71 newBuildTargetBuildBtn.removeAttr("disabled");
72 }
73 });
74
75 newBuildTargetBuildBtn.click(function (e) {
76 e.preventDefault();
77
78 if (!newBuildTargetInput.val()) {
79 return;
80 }
57 81
82 if (!selectedTarget) {
83 selectedTarget = { name: newBuildTargetInput.val() };
84 }
85 /* Fire off the build */
86 libtoaster.startABuild(selectedProject.projectBuildsUrl,
87 selectedProject.projectId, selectedTarget.name, function(){
88 window.location.replace(selectedProject.projectBuildsUrl);
89 }, null);
90 });
58 91
59 function _checkProjectBuildable() { 92 function _checkProjectBuildable() {
60 if (selectedProject.projectId === undefined) { 93 if (selectedProject.projectId === undefined) {
@@ -74,21 +107,12 @@ function basePageInit(ctx) {
74 /* we can build this project; enable input fields */ 107 /* we can build this project; enable input fields */
75 newBuildTargetInput.prop("disabled", false); 108 newBuildTargetInput.prop("disabled", false);
76 newBuildTargetBuildBtn.prop("disabled", false); 109 newBuildTargetBuildBtn.prop("disabled", false);
77 110 }
78 libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.projectTargetsUrl, { format: "json" }, function (item) {
79 /* successfully selected a target */
80 selectedProject.projectPageUrl = item.projectPageUrl;
81 selectedProject.projectName = item.name;
82 selectedProject.projectId = item.id;
83 selectedProject.projectBuildsUrl = item.projectBuildsUrl;
84 });
85
86 }
87 }, null); 111 }, null);
88 } 112 }
89 113
114 /* Setup New build button in the top nav bar */
90 function _setupNewBuildButton() { 115 function _setupNewBuildButton() {
91 /* Setup New build button */
92 116
93 /* If we don't have a current project then present the set project 117 /* If we don't have a current project then present the set project
94 * form. 118 * form.
@@ -98,7 +122,6 @@ function basePageInit(ctx) {
98 $('#project .icon-pencil').hide(); 122 $('#project .icon-pencil').hide();
99 } 123 }
100 124
101
102 libtoaster.makeTypeahead(newBuildProjectInput, selectedProject.projectsUrl, { format : "json" }, function (item) { 125 libtoaster.makeTypeahead(newBuildProjectInput, selectedProject.projectsUrl, { format : "json" }, function (item) {
103 /* successfully selected a project */ 126 /* successfully selected a project */
104 newBuildProjectSaveBtn.removeAttr("disabled"); 127 newBuildProjectSaveBtn.removeAttr("disabled");
@@ -115,40 +138,16 @@ function basePageInit(ctx) {
115 newBuildProjectSaveBtn.attr("disabled", "disabled"); 138 newBuildProjectSaveBtn.attr("disabled", "disabled");
116 }); 139 });
117 140
118 newBuildTargetInput.on('input', function () {
119 if ($(this).val().length === 0) {
120 newBuildTargetBuildBtn.attr("disabled", "disabled");
121 } else {
122 newBuildTargetBuildBtn.removeAttr("disabled");
123 }
124 });
125
126 newBuildTargetBuildBtn.click(function () {
127 if (!newBuildTargetInput.val()) {
128 return;
129 }
130
131 if (!selectedTarget) {
132 selectedTarget = { name: newBuildTargetInput.val() };
133 }
134 /* fire and forget */
135 libtoaster.startABuild(selectedProject.projectBuildsUrl, selectedProject.projectId, selectedTarget.name, null, null);
136 window.location.replace(selectedProject.projectPageUrl);
137 });
138 141
139 newBuildProjectSaveBtn.click(function () { 142 newBuildProjectSaveBtn.click(function () {
140 selectedProject.projectId = selectedProject.pk; 143 selectedProject.projectId = selectedProject.pk;
141 /* Update the typeahead project_id paramater */ 144 /* Update the typeahead project_id paramater */
142 _checkProjectBuildable(); 145 _checkProjectBuildable();
143 146
144 /* we set the effective context of the page to the currently selected project */
145 /* TBD: do we override even if we already have a context project ?? */
146 /* TODO: replace global library context with references to the "selected" project */
147
148 /* we can create a target typeahead only after we have a project selected */
149 newBuildTargetInput.prop("disabled", false); 147 newBuildTargetInput.prop("disabled", false);
150 newBuildTargetBuildBtn.prop("disabled", false); 148 newBuildTargetBuildBtn.prop("disabled", false);
151 149
150 /* Update the typeahead to use the new selectedProject */
152 libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.projectTargetsUrl, { format: "json" }, function (item) { 151 libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.projectTargetsUrl, { format: "json" }, function (item) {
153 /* successfully selected a target */ 152 /* successfully selected a target */
154 selectedTarget = item; 153 selectedTarget = item;
diff --git a/bitbake/lib/toaster/toastergui/static/js/importlayer.js b/bitbake/lib/toaster/toastergui/static/js/importlayer.js
index 560e25a01c..03274c00f3 100644
--- a/bitbake/lib/toaster/toastergui/static/js/importlayer.js
+++ b/bitbake/lib/toaster/toastergui/static/js/importlayer.js
@@ -16,8 +16,6 @@ function importLayerPageInit (ctx) {
16 var currentLayerDepSelection; 16 var currentLayerDepSelection;
17 var validLayerName = /^(\w|-)+$/; 17 var validLayerName = /^(\w|-)+$/;
18 18
19 $("#new-project-button").hide();
20
21 libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.projectLayersUrl, { include_added: "true" }, function(item){ 19 libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.projectLayersUrl, { include_added: "true" }, function(item){
22 currentLayerDepSelection = item; 20 currentLayerDepSelection = item;
23 21
@@ -154,7 +152,7 @@ function importLayerPageInit (ctx) {
154 } else { 152 } else {
155 /* Success layer import now go to the project page */ 153 /* Success layer import now go to the project page */
156 $.cookie('layer-imported-alert', JSON.stringify(data), { path: '/'}); 154 $.cookie('layer-imported-alert', JSON.stringify(data), { path: '/'});
157 window.location.replace(libtoaster.ctx.projectPageUrl+'#/layerimported'); 155 window.location.replace(libtoaster.ctx.projectPageUrl+'?notify=layer-imported');
158 } 156 }
159 }, 157 },
160 error: function (data) { 158 error: function (data) {
diff --git a/bitbake/lib/toaster/toastergui/static/js/layerdetails.js b/bitbake/lib/toaster/toastergui/static/js/layerdetails.js
index 291ed98c34..be6bbcd20f 100644
--- a/bitbake/lib/toaster/toastergui/static/js/layerdetails.js
+++ b/bitbake/lib/toaster/toastergui/static/js/layerdetails.js
@@ -16,14 +16,15 @@ function layerDetailsPageInit (ctx) {
16 }); 16 });
17 17
18 $(".breadcrumb li:first a").click(function(e){ 18 $(".breadcrumb li:first a").click(function(e){
19 e.preventDefault();
19 /* By default this link goes to the project configuration page. However 20 /* By default this link goes to the project configuration page. However
20 * if we have some builds we go there instead of the default href 21 * if we have some builds we go there instead of the default href
21 */ 22 */
22 libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){ 23 libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){
23 if (prjInfo.builds && prjInfo.builds.length > 0) { 24 if (prjInfo.builds && prjInfo.builds.length > 0) {
24 window.location.replace(libtoaster.ctx.projectBuildsUrl); 25 window.location.replace(libtoaster.ctx.projectBuildsUrl);
25 e.preventDefault(); 26 } else {
26 return; 27 window.location.replace(libtoaster.ctx.projectPageUrl);
27 } 28 }
28 }); 29 });
29 }); 30 });
diff --git a/bitbake/lib/toaster/toastergui/static/js/libtoaster.js b/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
index afd665ca99..079bbcb0b8 100644
--- a/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
@@ -79,11 +79,12 @@ var libtoaster = (function (){
79 data: data, 79 data: data,
80 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 80 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
81 success: function (_data) { 81 success: function (_data) {
82 /* No proper reponse YOCTO #7995
82 if (_data.error !== "ok") { 83 if (_data.error !== "ok") {
83 console.warn(_data.error); 84 console.warn(_data.error);
84 } else { 85 } else { */
85 if (onsuccess !== undefined) onsuccess(_data); 86 if (onsuccess !== undefined) onsuccess(_data);
86 } 87 // }
87 }, 88 },
88 error: function (_data) { 89 error: function (_data) {
89 console.warn("Call failed"); 90 console.warn("Call failed");
@@ -180,7 +181,15 @@ var libtoaster = (function (){
180 if (onFail !== undefined) 181 if (onFail !== undefined)
181 onFail(data); 182 onFail(data);
182 } else { 183 } else {
183 onSuccess(data.layerdeps); 184 var deps = {};
185 /* Filter out layer dep ids which are in the
186 * project already.
187 */
188 deps.list = data.layerdeps.list.filter(function(layerObj){
189 return (data.projectlayers.lastIndexOf(layerObj.id) < 0);
190 });
191
192 onSuccess(deps);
184 } 193 }
185 }, function() { 194 }, function() {
186 console.log("E: Failed to make request"); 195 console.log("E: Failed to make request");
diff --git a/bitbake/lib/toaster/toastergui/static/js/projectpage.js b/bitbake/lib/toaster/toastergui/static/js/projectpage.js
new file mode 100644
index 0000000000..b7cb074f11
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/static/js/projectpage.js
@@ -0,0 +1,429 @@
1"use strict";
2
3function projectPageInit(ctx) {
4
5 var layerAddInput = $("#layer-add-input");
6 var layersInPrjList = $("#layers-in-project-list");
7 var layerAddBtn = $("#add-layer-btn");
8
9 var machineChangeInput = $("#machine-change-input");
10 var machineChangeBtn = $("#machine-change-btn");
11 var machineForm = $("#select-machine-form");
12 var machineChangeFormToggle = $("#change-machine-toggle");
13 var machineNameTitle = $("#project-machine-name");
14 var machineChangeCancel = $("#cancel-machine-change");
15
16 var freqBuildBtn = $("#freq-build-btn");
17 var freqBuildList = $("#freq-build-list");
18
19 var releaseChangeFormToggle = $("#release-change-toggle");
20 var releaseTitle = $("#project-release-title");
21 var releaseForm = $("#change-release-form");
22 var releaseModal = $("#change-release-modal");
23 var cancelReleaseChange = $("#cancel-release-change");
24
25 var currentLayerAddSelection;
26 var currentMachineAddSelection = {};
27
28 var urlParams = libtoaster.parseUrlParams();
29
30 libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){
31 updateProjectLayers(prjInfo.layers);
32 updateFreqBuildRecipes(prjInfo.freqtargets);
33 updateProjectRelease(prjInfo.release);
34 updateProjectReleases(prjInfo.releases, prjInfo.release);
35
36 /* If we're receiving a machine set from the url and it's different from
37 * our current machine then activate set machine sequence.
38 */
39 if (urlParams.hasOwnProperty('setMachine') &&
40 urlParams.setMachine !== prjInfo.machine.name){
41 currentMachineAddSelection.name = urlParams.setMachine;
42 machineChangeBtn.click();
43 } else {
44 updateMachineName(prjInfo.machine.name);
45 }
46
47 /* Now we're really ready show the page */
48 $("#project-page").show();
49 });
50
51 (function notificationRequest(){
52
53 if (urlParams.hasOwnProperty('notify')){
54 switch (urlParams.notify){
55 case 'new-project':
56 $("#project-created-notification").show();
57 break;
58 case 'layer-imported':
59 layerImportedNotification();
60 break;
61 default:
62 break;
63 }
64 }
65 })();
66
67 /* Layer imported notification */
68 function layerImportedNotification(){
69 var imported = $.cookie("layer-imported-alert");
70 var message = "Layer imported";
71
72 if (!imported)
73 return;
74 else
75 imported = JSON.parse(imported);
76
77 if (imported.deps_added.length === 0) {
78 message = "You have imported <strong><a href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a></strong> and added it to your project.";
79 } else {
80
81 var links = "<a href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a>, ";
82
83 imported.deps_added.map (function(item, index){
84 links +='<a href="'+item.layerdetailurl+'">'+item.name+'</a>';
85 /*If we're at the last element we don't want the trailing comma */
86 if (imported.deps_added[index+1] !== undefined)
87 links += ', ';
88 });
89
90 /* Length + 1 here to do deps + the imported layer */
91 message = 'You have imported <strong><a href="'+imported.imported_layer.layerdetailurl+'">'+imported.imported_layer.name+'</a></strong> and added <strong>'+(imported.deps_added.length+1)+'</strong> layers to your project: <strong>'+links+'</strong>';
92 }
93
94 libtoaster.showChangeNotification(message);
95
96 $.removeCookie("layer-imported-alert", { path: "/"});
97 }
98
99 /* Add/Rm layer functionality */
100
101 libtoaster.makeTypeahead(layerAddInput, libtoaster.ctx.projectLayersUrl, { include_added: "false" }, function(item){
102 currentLayerAddSelection = item;
103 layerAddBtn.removeAttr("disabled");
104 });
105
106 layerAddBtn.click(function(e){
107 e.preventDefault();
108 var layerObj = currentLayerAddSelection;
109
110 addRmLayer(layerObj, true);
111 /* Reset the text input */
112 layerAddInput.val("");
113 });
114
115 function addRmLayer(layerObj, add){
116
117 libtoaster.addRmLayer(layerObj, add, function(layerDepsList){
118 if (add){
119 updateProjectLayers([layerObj]);
120 updateProjectLayers(layerDepsList);
121 }
122
123 /* Show the alert message */
124 var message = libtoaster.makeLayerAddRmAlertMsg(layerObj, layerDepsList, add);
125 libtoaster.showChangeNotification(message);
126 });
127 }
128
129 function updateProjectLayers(layers){
130
131 /* No layers to add */
132 if (layers.length === 0){
133 updateLayersCount();
134 return;
135 }
136
137 for (var i in layers){
138 var layerObj = layers[i];
139
140 var projectLayer = $("<li><a></a><span class=\"icon-trash\" data-toggle=\"tooltip\" title=\"Delete\"></span></li>");
141
142 projectLayer.data('layer', layerObj);
143 projectLayer.children("span").tooltip();
144
145 var link = projectLayer.children("a");
146
147 link.attr("href", layerObj.layerdetailurl);
148 link.text(layerObj.name);
149 /* YOCTO #8024
150 link.tooltip({title: layerObj.giturl + " | "+ layerObj.branch.name, placement: "right"});
151 branch name not accessible sometimes it is revision instead
152 */
153
154 var trashItem = projectLayer.children("span");
155 trashItem.click(function (e) {
156 e.preventDefault();
157 var layerObjToRm = $(this).parent().data('layer');
158
159 addRmLayer(layerObjToRm, false);
160
161 $(this).parent().fadeOut(function (){
162 $(this).remove();
163 updateLayersCount();
164 });
165 });
166
167 layersInPrjList.append(projectLayer);
168
169 updateLayersCount();
170 }
171 }
172
173 function updateLayersCount(){
174 var count = $("#layers-in-project-list").children().length;
175
176 if (count === 0)
177 $("#no-layers-in-project").fadeIn();
178 else
179 $("#no-layers-in-project").hide();
180
181 $("#project-layers-count").text(count);
182
183 return count;
184 }
185
186 /* Frequent builds functionality */
187 function updateFreqBuildRecipes(recipes) {
188 var noMostBuilt = $("#no-most-built");
189
190 if (recipes.length === 0){
191 noMostBuilt.show();
192 freqBuildBtn.hide();
193 } else {
194 noMostBuilt.hide();
195 freqBuildBtn.show();
196 }
197
198 for (var i in recipes){
199 var freqTargetCheck = $('<li><label class="checkbox"><input type="checkbox" /><span class="freq-target-name"></span></label></li>');
200 freqTargetCheck.find(".freq-target-name").text(recipes[i]);
201 freqTargetCheck.find("input").val(recipes[i]);
202 freqTargetCheck.click(function(){
203 if (freqBuildList.find(":checked").length > 0)
204 freqBuildBtn.removeAttr("disabled");
205 else
206 freqBuildBtn.attr("disabled", "disabled");
207 });
208
209 freqBuildList.append(freqTargetCheck);
210 }
211 }
212
213 freqBuildBtn.click(function(e){
214 e.preventDefault();
215
216 var toBuild = "";
217 freqBuildList.find(":checked").each(function(){
218 toBuild += $(this).val();
219 toBuild += " ";
220 });
221
222 libtoaster.startABuild(libtoaster.ctx.projectBuildsUrl, libtoaster.ctx.projectId, toBuild, function(){
223 /* Build started */
224 window.location.replace(libtoaster.ctx.projectBuildsUrl);
225 },
226 function(){
227 /* Build start failed */
228 /* [YOCTO #7995] */
229 window.location.replace(libtoaster.ctx.projectBuildsUrl);
230 });
231 });
232
233
234 /* Change machine functionality */
235
236 machineChangeFormToggle.click(function(){
237 machineForm.slideDown();
238 machineNameTitle.hide();
239 $(this).hide();
240 });
241
242 machineChangeCancel.click(function(){
243 machineForm.slideUp(function(){
244 machineNameTitle.show();
245 machineChangeFormToggle.show();
246 });
247 });
248
249 function updateMachineName(machineName){
250 machineChangeInput.val(machineName);
251 machineNameTitle.text(machineName);
252 }
253
254 libtoaster.makeTypeahead(machineChangeInput, libtoaster.ctx.projectMachinesUrl, { }, function(item){
255 currentMachineAddSelection = item;
256 machineChangeBtn.removeAttr("disabled");
257 });
258
259 machineChangeBtn.click(function(e){
260 e.preventDefault();
261 if (currentMachineAddSelection.name === undefined)
262 return;
263
264 libtoaster.editCurrentProject({ machineName : currentMachineAddSelection.name },
265 function(){
266 /* Success machine changed */
267 updateMachineName(currentMachineAddSelection.name);
268 machineChangeCancel.click();
269
270 /* Show the alert message */
271 var message = $('<span class="lead">You have changed the machine to: <strong><span id="notify-machine-name"></span></strong></span>');
272 message.find("#notify-machine-name").text(currentMachineAddSelection.name);
273 libtoaster.showChangeNotification(message);
274 },
275 function(){
276 /* Failed machine changed */
277 console.log("failed to change machine");
278 });
279 });
280
281
282 /* Change release functionality */
283 function updateProjectRelease(release){
284 releaseTitle.text(release.description);
285 }
286
287 function updateProjectReleases(releases, current){
288 for (var i in releases){
289 var releaseOption = $("<option></option>");
290
291 releaseOption.val(releases[i].id);
292 releaseOption.text(releases[i].description);
293 releaseOption.data('release', releases[i]);
294
295 if (releases[i].id == current.id)
296 releaseOption.attr("selected", "selected");
297
298 releaseForm.children("select").append(releaseOption);
299 }
300 }
301
302 releaseChangeFormToggle.click(function(){
303 releaseForm.slideDown();
304 releaseTitle.hide();
305 $(this).hide();
306 });
307
308 cancelReleaseChange.click(function(e){
309 e.preventDefault();
310 releaseForm.slideUp(function(){
311 releaseTitle.show();
312 releaseChangeFormToggle.show();
313 });
314 });
315
316 function changeProjectRelease(release, layersToRm){
317 libtoaster.editCurrentProject({ projectVersion : release.id },
318 function(){
319 /* Success */
320 /* Update layers list with new layers */
321 layersInPrjList.addClass('muted');
322 libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl,
323 function(prjInfo){
324 layersInPrjList.children().remove();
325 updateProjectLayers(prjInfo.layers);
326 layersInPrjList.removeClass('muted');
327 releaseChangedNotification(release, prjInfo.layers, layersToRm);
328 });
329 updateProjectRelease(release);
330 cancelReleaseChange.click();
331 });
332 }
333
334 /* Create a notification to show the changes to the layer configuration
335 * caused by changing a release.
336 */
337
338 function releaseChangedNotification(release, layers, layersToRm){
339
340 var message;
341
342 if (layers.length === 0 && layersToRm.length === 0){
343 message = $('<span><span class="lead">You have changed the project release to: <strong><span id="notify-release-name"></span></strong>.');
344 message.find("#notify-release-name").text(release.description);
345 libtoaster.showChangeNotification(message);
346 return;
347 }
348
349 /* Create the whitespace separated list of layers removed */
350 var layersDelList = "";
351
352 layersToRm.map(function(layer, i){
353 layersDelList += layer.name;
354 if (layersToRm[i+1] !== undefined)
355 layersDelList += ', ';
356 });
357
358 message = $('<span><span class="lead">You have changed the project release to: <strong><span id="notify-release-name"></span></strong>. This has caused the following changes in your project layers:</span><ul id="notify-layers-changed-list"></ul></span>');
359
360 var changedList = message.find("#notify-layers-changed-list");
361
362 message.find("#notify-release-name").text(release.description);
363
364 /* Manually construct the list item for changed layers */
365 var li = '<li><strong>'+layers.length+'</strong> layers changed to the <strong>'+release.name+'</strong> release: ';
366 for (var i in layers){
367 li += '<a href='+layers[i].layerdetailurl+'>'+layers[i].name+'</a>';
368 if (i !== 0)
369 li += ', ';
370 }
371
372 changedList.append($(li));
373
374 /* Layers removed */
375 if (layersToRm && layersToRm.length > 0){
376 if (layersToRm.length == 1)
377 li = '<li><strong>1</strong> layer deleted: '+layersToRm[0].name+'</li>';
378 else
379 li = '<li><strong>'+layersToRm.length+'</strong> layers deleted: '+layersDelList+'</li>';
380
381 changedList.append($(li));
382 }
383
384 libtoaster.showChangeNotification(message);
385 }
386
387 /* Show the modal dialog which gives the option to remove layers which
388 * aren't compatible with the proposed release
389 */
390 function showReleaseLayerChangeModal(release, layers){
391 var layersToRmList = releaseModal.find("#layers-to-remove-list");
392 layersToRmList.text("");
393
394 releaseModal.find(".proposed-release-change-name").text(release.description);
395 releaseModal.data("layers", layers);
396 releaseModal.data("release", release);
397
398 for (var i in layers){
399 layersToRmList.append($("<li></li>").text(layers[i].name));
400 }
401 releaseModal.modal('show');
402 }
403
404 $("#change-release-btn").click(function(e){
405 e.preventDefault();
406
407 var newRelease = releaseForm.find("option:selected").data('release');
408
409 $.getJSON(ctx.typeaheadUrl,
410 { search: newRelease.id, type: "versionlayers" },
411 function(layers) {
412 if (layers.rows.length === 0){
413 /* No layers to change for this release */
414 changeProjectRelease(newRelease, []);
415 } else {
416 showReleaseLayerChangeModal(newRelease, layers.rows);
417 }
418 });
419 });
420
421 /* Release change modal accept */
422 $("#change-release-and-rm-layers").click(function(){
423 var layers = releaseModal.data("layers");
424 var release = releaseModal.data("release");
425
426 changeProjectRelease(release, layers);
427 });
428
429}
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html
index 8def2daf8e..4c6676c6e7 100644
--- a/bitbake/lib/toaster/toastergui/templates/base.html
+++ b/bitbake/lib/toaster/toastergui/templates/base.html
@@ -38,6 +38,8 @@
38 projectName : {{project.name|json}}, 38 projectName : {{project.name|json}},
39 projectTargetsUrl: {% url 'projectavailabletargets' project.id as paturl%}{{paturl|json}}, 39 projectTargetsUrl: {% url 'projectavailabletargets' project.id as paturl%}{{paturl|json}},
40 projectBuildsUrl: {% url 'projectbuilds' project.id as pburl %}{{pburl|json}}, 40 projectBuildsUrl: {% url 'projectbuilds' project.id as pburl %}{{pburl|json}},
41 projectLayersUrl: {% url 'projectlayers' project.id as plurl %}{{plurl|json}},
42 projectMachinesUrl: {% url 'projectmachines' project.id as pmurl %}{{pmurl|json}},
41 projectId : {{project.id}}, 43 projectId : {{project.id}},
42 {% else %} 44 {% else %}
43 projectId : undefined, 45 projectId : undefined,
@@ -69,6 +71,11 @@
69 Loading <i class="fa-pulse icon-spinner"></i> 71 Loading <i class="fa-pulse icon-spinner"></i>
70 </div> 72 </div>
71 73
74 <div id="change-notification" class="alert lead alert-info" style="display:none">
75 <button type="button" class="close" id="hide-alert">&times;</button>
76 <span id="change-notification-msg"></span>
77 </div>
78
72 <div class="navbar navbar-fixed-top"> 79 <div class="navbar navbar-fixed-top">
73 <div class="navbar-inner"> 80 <div class="navbar-inner">
74 <div class="container-fluid"> 81 <div class="container-fluid">
diff --git a/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html b/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html
index ac32deac2f..0db06a86da 100644
--- a/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html
+++ b/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html
@@ -29,8 +29,6 @@
29 <li><a href="{% url 'projecttargets' project.id %}">Recipes</a></li> 29 <li><a href="{% url 'projecttargets' project.id %}">Recipes</a></li>
30 <li><a href="{% url 'projectmachines' project.id %}">Machines</a></li> 30 <li><a href="{% url 'projectmachines' project.id %}">Machines</a></li>
31 <li><a href="{% url 'projectlayers' project.id %}">Layers</a></li> 31 <li><a href="{% url 'projectlayers' project.id %}">Layers</a></li>
32 <li class="nav-header">Actions</li>
33 <li><a href="{% url 'importlayer' project.id %}">Import layer</a></li>
34 </ul> 32 </ul>
35 </div> 33 </div>
36 <div class="span10"> 34 <div class="span10">
diff --git a/bitbake/lib/toaster/toastergui/templates/importlayer.html b/bitbake/lib/toaster/toastergui/templates/importlayer.html
index d6984bcc9f..bec5f1aa74 100644
--- a/bitbake/lib/toaster/toastergui/templates/importlayer.html
+++ b/bitbake/lib/toaster/toastergui/templates/importlayer.html
@@ -1,10 +1,11 @@
1{% extends "baseprojectpage.html" %} 1{% extends "base.html" %}
2{% load projecttags %} 2{% load projecttags %}
3{% load humanize %} 3{% load humanize %}
4{% load static %} 4{% load static %}
5{% block pagecontent %}
5 6
7{% include "projecttopbar.html" %}
6 8
7{% block projectinfomain %}
8 9
9 {% if project and project.release %} 10 {% if project and project.release %}
10 <script src="{% static 'js/layerDepsModal.js' %}"></script> 11 <script src="{% static 'js/layerDepsModal.js' %}"></script>
@@ -24,12 +25,10 @@
24 }); 25 });
25 </script> 26 </script>
26 27
27 <h2>Import layer</h2> 28 <form class="span11">
28
29 <form>
30 <span class="help-block">The layer you are importing must be compatible with <strong>{{project.release.description}}</strong>, which is the release you are using in this project.</span>
31 <fieldset class="air"> 29 <fieldset class="air">
32 <legend>Layer repository information</legend> 30 <legend>Layer repository information</legend>
31 <span class="help-block">The layer you are importing must be compatible with <strong>{{project.release.description}}</strong>, which is the release you are using in this project.</span>
33 <div class="alert alert-error" id="import-error" style="display:none"> 32 <div class="alert alert-error" id="import-error" style="display:none">
34 <button type="button" class="close" data-dismiss="alert">&times;</button> 33 <button type="button" class="close" data-dismiss="alert">&times;</button>
35 <h3>&nbsp;</h3> 34 <h3>&nbsp;</h3>
@@ -136,5 +135,4 @@
136 </div> 135 </div>
137 136
138 {% endif %} 137 {% endif %}
139
140{% endblock %} 138{% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/templates/machine_btn.html b/bitbake/lib/toaster/toastergui/templates/machine_btn.html
index 54ff5def71..d2cb55bab4 100644
--- a/bitbake/lib/toaster/toastergui/templates/machine_btn.html
+++ b/bitbake/lib/toaster/toastergui/templates/machine_btn.html
@@ -1,4 +1,4 @@
1<a href="{% url 'project' extra.pid %}#/machineselect={{data.name}}" class="btn btn-block layer-exists-{{data.layer_version.id}}" style="margin-top: 5px; display:none"> 1<a href="{% url 'project' extra.pid %}?setMachine={{data.name}}" class="btn btn-block layer-exists-{{data.layer_version.id}}" style="margin-top: 5px; display:none">
2 Select machine</a> 2 Select machine</a>
3<button class="btn btn-block layerbtn layer-add-{{data.layer_version.id}}" data-layer='{ "id": {{data.layer_version.id}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.id %}"}' data-directive="add"> 3<button class="btn btn-block layerbtn layer-add-{{data.layer_version.id}}" data-layer='{ "id": {{data.layer_version.id}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.id %}"}' data-directive="add">
4 <i class="icon-plus"></i> 4 <i class="icon-plus"></i>
diff --git a/bitbake/lib/toaster/toastergui/templates/project.html b/bitbake/lib/toaster/toastergui/templates/project.html
index 0fbfb599b7..aad79b4860 100644
--- a/bitbake/lib/toaster/toastergui/templates/project.html
+++ b/bitbake/lib/toaster/toastergui/templates/project.html
@@ -1,446 +1,130 @@
1 {% extends "baseprojectpage.html" %} 1{% extends "baseprojectpage.html" %}
2<!-- 2
3vim: expandtab tabstop=2
4-->
5{% load projecttags %} 3{% load projecttags %}
6{% load humanize %} 4{% load humanize %}
7{% load static %} 5{% load static %}
8 6
9
10{% block projectinfomain %} 7{% block projectinfomain %}
11<script src="{% static "js/angular.min.js" %}"></script>
12<script src="{% static "js/angular-animate.min.js" %}"></script>
13<script src="{% static "js/angular-cookies.min.js" %}"></script>
14<script src="{% static "js/angular-route.min.js" %}"></script>
15<script src="{% static "js/angular-sanitize.min.js" %}"></script>
16<script src="{% static "js/ui-bootstrap-tpls-0.11.0.js" %}"></script>
17 8
18{% if lvs_nos == 0 %} 9<script src="{% static 'js/layerDepsModal.js' %}"></script>
10<script src="{% static 'js/projectpage.js' %}"></script>
11<script>
12 $(document).ready(function (){
13 var ctx = {
14 typeaheadUrl : "{% url 'xhr_datatypeahead' project.id %}",
15
16 };
17
18 try {
19 projectPageInit(ctx);
20 } catch (e) {
21 document.write("Sorry, An error has occurred loading this page");
22 console.warn(e);
23 }
24 });
25</script>
19 26
20 <div class="page-header"> 27<div id="change-release-modal" class="modal hide fade in" tabindex="-1" role="dialog" aria-labelledby="change-release-modal" aria-hidden="false">
21 <h1> {{ project.name }} </h1> 28 <div class="modal-header">
29 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
30 <h3>Changing Yocto Project release to <span class="proposed-release-change-name"></span></h3>
22 </div> 31 </div>
23 <div class="alert alert-info lead"> 32 <div class="modal-body">
24 <p>Toaster has no layer information. Without layer information, you cannot run builds. To generate layer information you can:</p> 33 <p>The following added layers do not exist for <span class="proposed-release-change-name"></span>: </p>
25 <ul> 34 <ul id="layers-to-remove-list">
26 <li> <a href="http://www.yoctoproject.org/docs/latest/toaster-manual/toaster-manual.html#layer-source">Configure a layer source</a></li> 35 </ul>
27 <li> <a href="{% url 'importlayer' project.id %}">Import a layer</a></li> 36 <p>If you change the Yocto Project release to <span class="proposed-release-change-name"></span>, the above layers will be deleted from your added layers.</p>
28 </ul>
29</div>
30
31{%else%}
32
33<div id="main" role="main" data-ng-app="project" data-ng-controller="prjCtrl" data-ng-cloak>
34
35 <!-- alerts section 1-->
36 <div data-ng-repeat="a in zone1alerts">
37 <div class="alert alert-dismissible lead" role="alert" data-ng-class="a.type"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span></button>
38 <span data-ng-bind-html="a.text"></span>
39 </div>
40 </div> 37 </div>
38 <div class="modal-footer">
39 <button id="change-release-and-rm-layers" data-dismiss="modal" type="submit" class="btn btn-primary">Change release and delete layers</button>
40 <button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
41 </div>
42</div>
41 43
42 <!-- custom templates for ng -->
43
44 <style>
45 .missing-layer {
46 color: lightgrey;
47 }
48 </style>
49 <script type="text/ng-template" id="recipes_suggestion_details">
50 <a> {[match.model.name]}
51 <span data-ng-class="{'missing-layer':($parent.$parent.$parent.$parent.filterProjectLayerIds().indexOf(match.model.projectcompatible_layer.id) == -1)}">
52 [{[match.model.layer_version__layer__name]}]
53 </span>
54 </a>
55 </script>
56
57 <script type="text/ng-template" id="machines_suggestion_details">
58 <a> {[match.model.name]} <span class="{'missing-layer':(filterProjectLayerIds().indexOf(match.model.layer_version_compatible_id) == -1)}">[{[match.model.layer_version__layer__name]}]</span> </a>
59 </script>
60 44
61 <script type="text/ng-template" id="layers_suggestion_details"> 45<div class="row-fluid" id="project-page" style="display:none">
62 <a> {[match.model['layer__name']]} ( {[match.model.layer__vcs_url]} ) </a> 46 <div class="span6">
63 </script> 47 <div class="well well-transparent" id="machine-section">
48 <h3>Machine</h3>
64 49
50 <p class="lead"><span id="project-machine-name"></span> <i title="" data-original-title="" id="change-machine-toggle" class="icon-pencil"></i></p>
65 51
52 <form id="select-machine-form" style="display:none;">
53 <div class="alert alert-info">
54 <strong>Machine changes have a big impact on build outcome.</strong> You cannot really compare the builds for the new machine with the previous ones.
55 </div>
66 56
57 <div class="input-append">
58 <input id="machine-change-input" autocomplete="off" value="" data-provide="typeahead" data-minlength="1" data-autocomplete="off" type="text">
59 <button id="machine-change-btn" class="btn" type="button">Save</button> <a href="#" id="cancel-machine-change" class="btn btn-link">Cancel</a>
60 </div>
67 61
68 <!-- modal dialogs --> 62 <p><a href="{% url 'projectmachines' project.id %}" class="link">View compatible machines</a></p>
69 <script type="text/ng-template" id="dependencies_modal"> 63 </form>
70 <div class="modal-header">
71 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
72 <h3><span data-ng-bind="layerAddName"></span> dependencies</h3>
73 </div>
74 <div class="modal-body">
75 <p><strong>{[layerAddName]}</strong> depends on some layers that are not added to your project. Select the ones you want to add:</p>
76 <ul class="unstyled">
77 <li data-ng-repeat="ld in items">
78 <label class="checkbox">
79 <input type="checkbox" data-ng-model="selectedItems[ld.id]"> {[ld.name]}
80 </label>
81 </li>
82 </ul>
83 </div>
84 <div class="modal-footer">
85 <button class="btn btn-primary" data-ng-click="ok()">Add layers</button>
86 <button class="btn" data-ng-click="cancel()">Cancel</button>
87 </div> 64 </div>
88 </form>
89 </script>
90 65
66 <div class="well well-transparent">
67 <h3>Most built recipes</h3>
91 68
92 <script type="text/ng-template" id="change_version_modal"> 69 <div class="alert alert-info" style="display:none" id="no-most-built">
93 <div class="modal-header"> 70 <span class="lead">You haven't built any recipes yet</span>
94 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
95 <h3>Changing release to {[releaseDescription]}</h3>
96 </div> 71 </div>
97 <div class="modal-body">
98 <p>The following project layers do not exist for the {[releaseDescription]} release:</p>
99 <ul>
100 <li data-ng-repeat="i in items"><span class="layer-info" data-toggle="tooltip" tooltip="{[i.detail]}">{[i.name]}</span></li>
101 </ul>
102 <p>If you change the release to {[releaseDescription]}, the above layers will be deleted from your project.</p>
103 </div>
104 <div class="modal-footer">
105 <button class="btn btn-primary" data-ng-click="ok()">Change release and delete layers</button>
106 <button class="btn" data-ng-click="cancel()">Cancel</button>
107 </div>
108 </script>
109
110 <script type="text/ng-template" id="target_display">
111 <div data-ng-switch on="t.task.length">
112 <div data-ng-switch-when="undefined">{[t.target]}</div>
113 <div data-ng-switch-default>{[t.target]}:{[t.task]}</div>
114 </div>
115 </script>
116
117
118 <!-- latest builds list -->
119
120 <a id="buildslist"></a>
121 <h2 class="air" data-ng-if="builds.length">Latest builds</h2>
122 <div class="animate-repeat alert" data-ng-repeat="b in builds track by b.id" data-ng-class="{'queued':'alert-info', 'In Progress':'alert-info', 'Succeeded':'alert-success', 'Failed':'alert-error'}[b.status]">
123 <div class="row-fluid">
124 <switch data-ng-switch="b.status">
125
126 <case data-ng-switch-when="Failed">
127 <div class="lead span3">
128 <a data-ng-class="{'Succeeded': 'success', 'Failed': 'error'}[b.status]" href="{[b.br_page_url]}">
129 <span data-ng-repeat="t in b.targets" data-ng-include src="'target_display'"></span>
130 </a>
131 </div>
132 <div class="span2 lead">
133 <ngif data-ng-if="b.updated - todaydate > 0">
134 {[b.updated|date:'HH:mm']}
135 </ngif>
136 <ngif data-ng-if="b.updated - todaydate < 0">
137 {[b.updated|date:'dd/MM/yy HH:mm']}
138 </ngif>
139 </div>
140 <div class="span2">
141 <ngif data-ng-if="b.errors.length">
142 <span>
143 <i class="icon-minus-sign red lead"></i>
144 <a href="{[b.br_page_url]}#errors" class="lead error">{[b.errors.length]}
145 <ng-pluralize count="b.errors.length" when="{'1':'error','other':'errors'}"></ng-pluralize></a>
146 </span>
147 </ngif>
148 </div>
149 <div class="span2">
150 <!-- we don't have warnings in this case -->
151 </div>
152 <div> <span class="lead">Build time: {[b.command_time|timediff]}</span>
153 <button class="btn pull-right" data-ng-class="{'Succeeded': 'btn-success', 'Failed': 'btn-danger'}[b.status]"
154 data-ng-click="buildExistingTarget(b.targets)">Run again</button>
155
156 </div>
157 </case>
158
159 <case data-ng-switch-when="queued">
160 <div class="lead span5"> <span data-ng-repeat="t in b.targets" data-ng-include src="'target_display'"></span> </div>
161 <div class="span4 lead" >Build queued
162 <i title="This build will start as soon as a build server is available" class="icon-question-sign get-help get-help-blue heading-help" data-toggle="tooltip"></i>
163 </div>
164 <button class="btn pull-right btn-info" data-ng-click="buildCancel(b)">Cancel</button>
165 </case>
166
167 <case data-ng-switch-when="In Progress">
168 <switch data-ng-switch="b.build.length">
169 <case data-ng-switch-when="0">
170 <div class="lead span5"> <span data-ng-repeat="t in b.targets" data-ng-include src="'target_display'"></span> </div>
171 <div class="span4 lead">
172 Checking out layers
173 </div>
174 </case>
175 <case data-ng-switch-default="">
176 <div class="lead span3"> <span data-ng-repeat="t in b.targets" data-ng-include src="'target_display'"></span> </div>
177 <div class="span4 offset1" >
178 <div class="progress" style="margin-top:5px;" data-toggle="tooltip" tooltip="{[b.build[0].completeper]}% of tasks complete">
179 <div style="width: {[b.build[0].completeper]}%;" class="bar"></div>
180 </div>
181 </div>
182 <div class="text-right lead">{[b.build[0].completeper]}% tasks completed</div>
183 </case>
184 </case>
185
186 72
187 <case data-ng-switch-when="Succeeded"> 73 <ul class="unstyled configuration-list" id="freq-build-list">
188 <div class="lead span3"> 74 </ul>
189 <a data-ng-class="{'Succeeded': 'success', 'Failed': 'error'}[b.build[0].status]" href="{[b.build[0].build_page_url]}"> 75 <button class="btn btn-primary" id="freq-build-btn" disabled="disabled">Build selected recipes</button>
190 <span data-ng-repeat="t in b.targets" data-ng-include src="'target_display'"></span> 76 </div>
191 </a>
192 </div>
193 <div class="span2 lead">
194 <ngif data-ng-if="b.build[0].completed_on - todaydate > 0">
195 {[b.build[0].completed_on|date:'HH:mm']}
196 </ngif>
197 <ngif data-ng-if="b.build[0].completed_on - todaydate < 0">
198 {[b.build[0].completed_on|date:'dd/MM/yy HH:mm']}
199 </ngif>
200 </div>
201 <div class="span2">
202 <ngif data-ng-if="b.build[0].errors">
203 <span>
204 <i class="icon-minus-sign red lead"></i>
205 <a href="{[b.build[0].build_page_url]}#errors" class="lead error">{[b.build[0].errors]}
206 <ng-pluralize count="b.build[0].errors" when="{'1':'error','other':'errors'}"></ng-pluralize></a>
207 </span>
208 </ngif>
209 </div>
210 <div class="span2">
211 <ngif data-ng-if="b.build[0].warnings">
212 <span>
213 <i class="icon-warning-sign yellow lead"></i>
214 <a href="{[b.build[0].build_page_url]}#warnings" class="lead warning">{[b.build[0].warnings]}
215 <ng-pluralize count="b.build[0].warnings" when="{'1':'warning','other':'warnings'}"></ng-pluralize></a>
216 </span>
217 </ngif>
218 </div>
219 <div> <span class="lead">Build time: <a href="{[b.build[0].build_time_page_url]}">{[b.build[0].build_time|timediff]}</a></span>
220 <button class="btn pull-right" data-ng-class="{'Succeeded': 'btn-success', 'Failed': 'btn-danger'}[b.build[0].status]"
221 data-ng-click="buildExistingTarget(b.targets)">Run again</button>
222 77
223 </div> 78 <div class="well well-transparent">
224 </case> 79 <h3>Project release</h3>
225 80
81 <p class="lead"><span id="project-release-title"></span> <i title="" data-original-title="" id="release-change-toggle" class="icon-pencil"></i></p>
226 82
227 <case data-ng-switch-default=""> 83 <form class="form-inline" id="change-release-form" style="display:none;">
228 <div>FIXME!</div> 84 <select></select>
229 </case> 85 <button class="btn" style="margin-left:5px;" id="change-release-btn">Change</button> <a href="#" id="cancel-release-change" class="btn btn-link">Cancel</a>
230 </switch> 86 </form>
231 <div class="lead pull-right">
232 </div>
233 </div> 87 </div>
234 </div> 88 </div>
235 89
236 <h2 class="air">Project configuration</h2> 90 <div class="span6">
91 <div class="well well-transparent" id="layer-container">
92 <h3>Layers <span class="muted counter">(<span id="project-layers-count"></span>)</span>
93 <i data-original-title="OpenEmbedded organises metadata into modules called 'layers'. Layers allow you to isolate different types of customizations from each other. <a href='http://www.yoctoproject.org/docs/current/dev-manual/dev-manual.html#understanding-and-creating-layers' target='_blank'>More on layers</a>" class="icon-question-sign get-help heading-help" title=""></i>
94 </h3>
237 95
238 <!-- alerts section 2 --> 96 <div class="alert lead" id="no-layers-in-project" style="display:none">
239 <div data-ng-repeat="a in zone2alerts"> 97 You need to add some layers. For that you can:
240 <div class="alert alert-dismissible lead" role="alert" data-ng-class="a.type"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span></button> 98 <ul>
241 <span data-ng-bind-html="a.text"></span> 99 <li><a href="{% url 'projectlayers' project.id %}">View all layers compatible with this project</a></li>
242 </div> 100 <li><a href="{% url 'importlayer' project.id %}">Import a layer</a></li>
243 </div> 101 <li><a href="http://www.yoctoproject.org/docs/current/dev-manual/dev-manual.html#understanding-and-creating-layers" target="_blank">Read about layers in the documentation</a></li>
102 </ul>
103 <p>Or type a layer name below.</p>
104 </div>
244 105
245 <div class="row-fluid"> 106 <form style="margin-top:20px">
107 <!--div class="control-group error"-->
246 108
247 <!-- project layers -->
248 <div id="layer-container" class="well well-transparent span4">
249 <h3>
250 Layers <span class="muted counter">({[layers.length]})</span>
251 <i class="icon-question-sign get-help heading-help" title="Bitbake reads metadata files from modules called 'layers'. Layers allow you to isolate different types of customizations from each other. <a href='http://www.yoctoproject.org/docs/current/dev-manual/dev-manual.html#understanding-and-creating-layers' target='_blank'>More on layers</a>"></i>
252 </h3>
253 <div class="alert" data-ng-if="project.release && !layers.length">
254 <b>You need to add some layers </b>
255 <p>
256 You can:
257 <ul>
258 <li> <a href="{% url 'projectlayers' project.id %}">View all compatible layers available in Toaster</a>
259 <li> <a href="{% url 'importlayer' project.id %}">Import a layer</a>
260 <li> <a href="https://www.yoctoproject.org/docs/1.6.1/dev-manual/dev-manual.html#understanding-and-creating-layers" target="_blank">Read about layers in the manual</a>
261 </ul>
262 Or type a layer name below.
263 </p>
264 </div>
265 <form data-ng-submit="layerAdd()">
266 <div class="input-append"> 109 <div class="input-append">
267 <input type="text" style="width: 100%" id="layer" autocomplete="off" placeholder="Type a layer name" data-minLength="1" data-ng-model="layerAddName" data-typeahead="e for e in getLayersAutocompleteSuggestions($viewValue)" data-typeahead-template-url="layers_suggestion_details" data-typeahead-on-select="onLayerSelect($item, $model, $label)" data-typeahead-editable="false" data-ng-class="{ 'has-error': layerAddName.$invalid }" data-ng-disabled="!project.release" /> 110 <input id="layer-add-input" autocomplete="off" placeholder="Type a layer name" data-minlength="1" data-autocomplete="off" data-provide="typeahead" data-source="" type="text">
268 <input type="submit" id="add-layer" class="btn" value="Add" data-ng-disabled="!layerAddName.length"/> 111 <button id="add-layer-btn" class="btn" disabled>Add</button>
269 </div> 112 </div>
270 {% csrf_token %}
271 </form>
272 <p>
273 <a href="{% url 'projectlayers' project.id %}">View all compatible layers</a>
274 <i class="icon-question-sign get-help" title="View all the layers you can build with the release selected for this project, which is {[project.release.desc]}"></i>
275 |
276 <a href="{% url 'importlayer' project.id %}">Import layer</a></p>
277 <ul class="unstyled configuration-list">
278 <li data-ng-repeat="l in layers track by l.id" class="animate-repeat">
279 <a href="{[l.layerdetailurl]}" class="layer-info" data-toggle="tooltip" tooltip-placement="right" tooltip="{[l.giturl]} | {[l.branch.name]}">{[l.name]}</a>
280 <i class="icon-trash" data-ng-click="layerDel(l.id)" tooltip="Delete"></i>
281 </li>
282 </ul>
283 </div>
284 113
285 114 <div id="import-alert" class="alert alert-info" style="display:none;">
286 <!-- project targets --> 115 Toaster does not know about this layer. Please <a href="#">import it</a>
287 <div id="target-container" class="well well-transparent span4">
288 <h3>
289 Recipes
290 <i class="icon-question-sign get-help heading-help" title="What you build, often an image recipe that produces a root file system file. Something like <code>core-image-minimal</code> or <code>core-image-sato</code>"></i>
291 </h3>
292 <form data-ng-submit="buildNamedTarget()">
293 <div class="input-append">
294 <input type="text" style="width: 100%" placeholder="Type the recipe(s) you want to build" autocomplete="off" data-minLength="1" data-ng-model="targetName1" data-typeahead="a.name for a in getRecipesAutocompleteSuggestions($viewValue)" data-typeahead-template-url="recipes_suggestion_details" data-ng-disabled="!project.release || !layers.length">
295 <button type="submit" class="btn btn-primary" data-ng-disabled="!targetName1.length">
296 Build </button>
297 </div>
298 {% csrf_token %}
299 </form>
300 <p>
301 <a href="{% url 'projecttargets' project.id %}">View all compatible recipes</a>
302 <i class="icon-question-sign get-help" title="View all the recipes you can build with the release selected for this project, which is {[project.release.desc]}"></i>
303 </p>
304 <div data-ng-if="frequenttargets.length">
305 <h4 class="air">
306 Most built recipes
307 </h4>
308 <ul class="unstyled configuration-list {[mutedtargets]}">
309 <li data-ng-repeat="t in frequenttargets">
310 <label class="checkbox">
311 <input type="checkbox" data-ng-model="mostBuiltTargets[t]" data-ng-disabled="disableBuildCheckbox(t)" data-ng-checked="mostBuiltTargets[t] && !disableBuildCheckbox(t)">{[t]}
312 </label>
313 </li>
314 </ul>
315 <button class="btn btn-large btn-primary" data-ng-disabled="enableBuildSelectedTargets()" data-ng-click="buildSelectedTargets()">Build selected recipes</button>
316 </div> 116 </div>
317 </div>
318 117
319 <!-- project configuration -->
320 <div id="machine-distro" class="well well-transparent span4">
321 <h3>
322 Machine
323 <i class="icon-question-sign get-help heading-help" title="The machine is the hardware for which you want to build. You can only set one machine per project"></i>
324 </h3>
325 <p class="lead" id="select-machine-opposite">
326 <span>{[machine.name]}</span>
327 <i id="change-machine" class="icon-pencil" data-ng-click="toggle('#select-machine')"></i>
328 </p>
329 <div id="select-machine" style="display: none">
330 <div class="alert alert-info">
331 <strong>Machine changes have a big impact on build outcome.</strong>
332 You cannot really compare the builds for the new machine with the previous ones.
333 </div>
334 <form data-ng-submit="editProjectSettings('#select-machine')" class="input-append">
335 <input type="text" id="machine" autocomplete="off" data-ng-model="machineName" value="{[machine.name]}" data-typeahead="m.name for m in getMachinesAutocompleteSuggestions($viewValue)" data-typeahead-template-url="machines_suggestion_details" />
336 <input type="submit" id="apply-change-machine" class="btn" data-ng-disabled="machineName == machine.name || machineName.length == 0" value="Save"/>
337 <input type="reset" id="cancel-machine" class="btn btn-link" data-ng-click="toggle('#select-machine')" value="Cancel"/>
338 {% csrf_token %}
339 </form>
340 <p> 118 <p>
341 <a href="{% url 'projectmachines' project.id %}" class="link">View all compatible machines</a> 119 <a href="{% url 'projectlayers' project.id %}" id="view-compatible-layers">View compatible layers</a>
342 <i class="icon-question-sign get-help" title="View all the machines you can set with the release selected for this project, which is {[project.release.desc]}"></i> 120 <i data-original-title="View all the layers you can build with the release selected for this project, which is Yocto Project master" class="icon-question-sign get-help" title=""></i>
343 </p> 121 | <a href="{% url 'importlayer' project.id %}">Import layer</a>
344 </div> 122 </p>
345 <p class="link-action">
346 <a href="{% url 'projectconf' project.id %}" class="link">Edit configuration variables</a>
347 <i data-original-title="You can set other project configuration options here. Each option, like everything else in the build system, is a variable - value pair" class="icon-question-sign get-help heading-help" title=""></i>
348 </p>
349 </div>
350 </div>
351
352
353 <h2>Project details</h2>
354
355 <!-- alerts section 3 -->
356 <div data-ng-repeat="a in zone3alerts">
357 <div class="alert alert-dismissible lead" role="alert" data-ng-class="a.type"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span></button>
358 <span data-ng-bind-html="a.text"></span>
359 </div>
360 </div>
361
362
363 <div id="project-details" class="well well-transparent">
364 <h3>Project name</h3>
365 <p class="lead" id="change-project-name-opposite">
366 <span >{[project.name]}</span>
367 <i class="icon-pencil" data-ng-click="toggle('#change-project-name')" ></i>
368 </p>
369 <div id="change-project-name" style="display:none;">
370 <form data-ng-submit="editProjectSettings('#change-project-name')" class="input-append">
371 <input type="text" class="input-xlarge" id="type-project-name" data-ng-model="projectName" value="{[project.name]}">
372 <input type="submit" class="btn" value="Save" data-ng-disabled="project.name == projectName"/>
373 <input type="reset" class="btn btn-link" value="Cancel" data-ng-click="toggle('#change-project-name')">
374 </form>
375 </div>
376
377 <h3 data-ng-if="releases.length > 1">
378 Release
379 <i class="icon-question-sign get-help heading-help" title="The version of the build system you want to use"></i>
380 </h3>
381 <p data-ng-if="releases.length > 1" class="lead" id="change-project-version-opposite">
382 <span id="project-version">{[project.release.desc]}</span>
383 <i id="change-version" class="icon-pencil" data-ng-click="toggle('#change-project-version')" ></i>
384 </p>
385 <div class="div-inline" id="change-project-version" style="display:none;">
386 <form data-ng-submit="testProjectSettingsChange('#change-project-version')" class="input-append">
387 <select id="select-version" data-ng-model="projectVersion">
388 <option data-ng-repeat="r in releases" value="{[r.id]}" data-ng-selected="r.id == project.release.id">{[r.description]}</option>
389 </select>
390 <input type="submit" class="btn" style="margin-left:5px;" value="Save" data-ng-disabled="project.release.id == projectVersion"/>
391 <input type="reset" class="btn btn-link" value="Cancel" data-ng-click="toggle('#change-project-version')"/>
392
393 </form> 123 </form>
124
125 <ul class="unstyled configuration-list" id="layers-in-project-list">
126 </ul>
394 </div> 127 </div>
395 </div> 128 </div>
396 129</div>
397</div> <!-- end main -->
398
399</div> <!-- end row -->
400
401
402
403<!-- load application logic !-->
404<script src="{% static "js/projectapp.js" %}"></script>
405
406<!-- dump initial data for use in the angular app -->
407<script>
408angular.element(document).ready(function() {
409 scope = angular.element("#main").scope();
410 scope.urls = {};
411 scope.urls.xhr_build = "{% url 'projectbuilds' project.id %}";
412 scope.urls.xhr_edit = "{% url 'project' project.id %}?format=json";
413 scope.urls.layers = "{% url 'projectlayers' project.id %}";
414 scope.urls.targets = "{% url 'projectavailabletargets' project.id %}";
415 scope.urls.machines = "{% url 'projectmachines' project.id %}";
416 scope.urls.importlayer = "{% url 'importlayer' project.id %}";
417 scope.urls.xhr_datatypeahead = {% url 'xhr_datatypeahead' project.id as xhrdta %}{{xhrdta|json}};
418 scope.project = {{prj|json}};
419 scope.builds = {{builds|json}};
420 scope.layers = {{layers|json}};
421 scope.targets = {{targets|json}};
422 scope.frequenttargets = {{freqtargets|json}};
423 scope.machine = {{machine|json}};
424 scope.releases = {{releases|json}};
425 scope.layerCount = scope.layers.length;
426 scope.mutedtargets = (scope.layerCount == 0 ? "muted" : "")
427 var now = (new Date()).getTime();
428 scope.todaydate = now - (now % 86400000);
429
430 scope.zone1alerts = [];
431 scope.zone2alerts = [];
432 scope.zone3alerts = [];
433
434 scope.mostBuiltTargets = {};
435
436 scope.updateDisplayWithCommands();
437 scope.validateData();
438
439 scope.init();
440 scope.$digest();
441
442 });
443</script>
444
445{% endif %} {# from lvs_nos check #}
446{% endblock %} 130{% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/templates/projecttopbar.html b/bitbake/lib/toaster/toastergui/templates/projecttopbar.html
index 46473cb76b..d4d1951ae4 100644
--- a/bitbake/lib/toaster/toastergui/templates/projecttopbar.html
+++ b/bitbake/lib/toaster/toastergui/templates/projecttopbar.html
@@ -1,3 +1,8 @@
1<div class="alert alert-success lead" id="project-created-notification" style="margin-top:15px; display:none">
2 <button type="button" class="close" data-dismiss="alert">×</button>
3 Your project <strong>{{project.name}}</strong> has been created. You can now <a href="{% url 'projectmachines' project.id %}">select your target machine</a> and <a href="{% url 'projecttargets' project.id %}">choose image recipes</a> to build.
4</div>
5
1<!-- project name --> 6<!-- project name -->
2<div class="row-fluid page-header"> 7<div class="row-fluid page-header">
3 <h1>{{project.name}}</h1> 8 <h1>{{project.name}}</h1>
@@ -7,7 +12,7 @@
7 <ul class="nav nav-pills"> 12 <ul class="nav nav-pills">
8 <li> 13 <li>
9 <a href="{% url 'projectbuilds' project.id %}"> 14 <a href="{% url 'projectbuilds' project.id %}">
10 Builds (<span class="total-builds"></span>) 15 Builds (<span class="total-builds">0</span>)
11 </a> 16 </a>
12 </li> 17 </li>
13 <li id="topbar-configuration-tab"> 18 <li id="topbar-configuration-tab">
@@ -15,6 +20,11 @@
15 Configuration 20 Configuration
16 </a> 21 </a>
17 </li> 22 </li>
23 <li>
24 <a href="{% url 'importlayer' project.id %}">
25 Import layer
26 </a>
27 </li>
18 <!-- Coming soon 28 <!-- Coming soon
19 <li> 29 <li>
20 <a href="my-image-recipes.html"> 30 <a href="my-image-recipes.html">
@@ -23,16 +33,16 @@
23 </li> 33 </li>
24 --> 34 -->
25 <li class="pull-right"> 35 <li class="pull-right">
26 <form class="form-inline" style="margin-bottom: 0">
27 36
28 <i class="icon-question-sign get-help heading-help" data-placement="left" title="" data-original-title="Type the name of one or more recipes you want to build, separated by a space. You can also specify a task by appending a semicolon and a task name to the recipe name, like so: <code>busybox:clean</code>"> 37 <i class="icon-question-sign get-help heading-help" data-placement="left" title="" data-original-title="Type the name of one or more recipes you want to build, separated by a space. You can also specify a task by appending a semicolon and a task name to the recipe name, like so: <code>busybox:clean</code>">
29 </i> 38 </i>
30 <div class="input-append"> 39 <div class="input-append">
31 <input type="text" class="input-xlarge build-target-input" placeholder="Type the recipe you want to build" autocomplete="off"> 40 <form class="form-inline" style="margin-bottom: 0">
32 <button class="btn btn-primary build-button" data-project-id="{{project.id}}" disabled>Build 41 <input type="text" class="input-xlarge input-lg build-target-input" placeholder="Type the recipe you want to build" autocomplete="off" disabled>
33 </button> 42 <button class="btn btn-primary btn-large build-button" data-project-id="{{project.id}}" disabled>Build
43 </button>
44 </form>
34 </div> 45 </div>
35 </form> 46 </li>
36 </li> 47 </ul>
37 </ul>
38</div> 48</div>
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 688a4c276f..b43a01e951 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2132,7 +2132,7 @@ if True:
2132 prj = Project.objects.create_project(name = request.POST['projectname'], release = release) 2132 prj = Project.objects.create_project(name = request.POST['projectname'], release = release)
2133 prj.user_id = request.user.pk 2133 prj.user_id = request.user.pk
2134 prj.save() 2134 prj.save()
2135 return redirect(reverse(project, args=(prj.pk,)) + "#/newproject") 2135 return redirect(reverse(project, args=(prj.pk,)) + "?notify=new-project")
2136 2136
2137 except (IntegrityError, BadParameterException) as e: 2137 except (IntegrityError, BadParameterException) as e:
2138 # fill in page with previously submitted values 2138 # fill in page with previously submitted values
@@ -2238,7 +2238,8 @@ if True:
2238 } 2238 }
2239 2239
2240 if prj.release is not None: 2240 if prj.release is not None:
2241 context["prj"]["release"] = { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description} 2241 context['release'] = { "id": prj.release.pk, "name": prj.release.name, "description": prj.release.description}
2242
2242 2243
2243 try: 2244 try:
2244 context["machine"] = {"name": prj.projectvariable_set.get(name="MACHINE").value} 2245 context["machine"] = {"name": prj.projectvariable_set.get(name="MACHINE").value}