diff options
Diffstat (limited to 'bitbake/lib/toaster/toastergui/static/js/projectapp.js')
-rw-r--r-- | bitbake/lib/toaster/toastergui/static/js/projectapp.js | 985 |
1 files changed, 0 insertions, 985 deletions
diff --git a/bitbake/lib/toaster/toastergui/static/js/projectapp.js b/bitbake/lib/toaster/toastergui/static/js/projectapp.js deleted file mode 100644 index ea44bf34f1..0000000000 --- a/bitbake/lib/toaster/toastergui/static/js/projectapp.js +++ /dev/null | |||
@@ -1,985 +0,0 @@ | |||
1 | // vim: set tabstop=4 expandtab ai: | ||
2 | // BitBake Toaster Implementation | ||
3 | // | ||
4 | // Copyright (C) 2013 Intel Corporation | ||
5 | // | ||
6 | // This program is free software; you can redistribute it and/or modify | ||
7 | // it under the terms of the GNU General Public License version 2 as | ||
8 | // published by the Free Software Foundation. | ||
9 | // | ||
10 | // This program is distributed in the hope that it will be useful, | ||
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | // GNU General Public License for more details. | ||
14 | // | ||
15 | // You should have received a copy of the GNU General Public License along | ||
16 | // with this program; if not, write to the Free Software Foundation, Inc., | ||
17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | |||
19 | 'use strict'; | ||
20 | |||
21 | var angular_formpost = function($httpProvider) { | ||
22 | // Use x-www-form-urlencoded Content-Type | ||
23 | // By Ezekiel Victor, http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/, no license, with attribution | ||
24 | $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'; | ||
25 | |||
26 | /** | ||
27 | * The workhorse; converts an object to x-www-form-urlencoded serialization. | ||
28 | * @param {Object} obj | ||
29 | * @return {String} | ||
30 | */ | ||
31 | var param = function(obj) { | ||
32 | var query = '', name, value, fullSubName, subName, subValue, innerObj, i; | ||
33 | |||
34 | for(name in obj) { | ||
35 | value = obj[name]; | ||
36 | |||
37 | if(value instanceof Array) { | ||
38 | for(i=0; i<value.length; ++i) { | ||
39 | subValue = value[i]; | ||
40 | fullSubName = name + '[' + i + ']'; | ||
41 | innerObj = {}; | ||
42 | innerObj[fullSubName] = subValue; | ||
43 | query += param(innerObj) + '&'; | ||
44 | } | ||
45 | } | ||
46 | else if(value instanceof Object) { | ||
47 | for(subName in value) { | ||
48 | subValue = value[subName]; | ||
49 | fullSubName = name + '[' + subName + ']'; | ||
50 | innerObj = {}; | ||
51 | innerObj[fullSubName] = subValue; | ||
52 | query += param(innerObj) + '&'; | ||
53 | } | ||
54 | } | ||
55 | else if(value !== undefined && value !== null) | ||
56 | query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&'; | ||
57 | } | ||
58 | |||
59 | return query.length ? query.substr(0, query.length - 1) : query; | ||
60 | }; | ||
61 | |||
62 | // Override $http service's default transformRequest | ||
63 | $httpProvider.defaults.transformRequest = [function(data) { | ||
64 | return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data; | ||
65 | }]; | ||
66 | }; | ||
67 | |||
68 | |||
69 | /** | ||
70 | * Helper to execute callback on elements from array differences; useful for incremental UI updating. | ||
71 | * @param {Array} oldArray | ||
72 | * @param {Array} newArray | ||
73 | * @param {function} compareElements | ||
74 | * @param {function} onAdded | ||
75 | * @param {function} onDeleted | ||
76 | * | ||
77 | * no return | ||
78 | */ | ||
79 | function _diffArrays(existingArray, newArray, compareElements, onAdded, onDeleted ) { | ||
80 | var added = []; | ||
81 | var removed = []; | ||
82 | newArray.forEach( function( newElement ) { | ||
83 | var existingIndex = existingArray.findIndex(function ( existingElement ) { | ||
84 | return compareElements(newElement, existingElement); | ||
85 | }); | ||
86 | if (existingIndex < 0 && onAdded) { added.push(newElement); } | ||
87 | }); | ||
88 | existingArray.forEach( function( existingElement ) { | ||
89 | var newIndex = newArray.findIndex(function ( newElement ) { | ||
90 | return compareElements(newElement, existingElement); | ||
91 | }); | ||
92 | if (newIndex < 0 && onDeleted) { removed.push(existingElement); } | ||
93 | }); | ||
94 | |||
95 | if (onAdded) { | ||
96 | added.map(onAdded); | ||
97 | } | ||
98 | |||
99 | if (onDeleted) { | ||
100 | removed.map(onDeleted); | ||
101 | } | ||
102 | |||
103 | } | ||
104 | |||
105 | // add Array findIndex if not there | ||
106 | |||
107 | if (Array.prototype.findIndex === undefined) { | ||
108 | Array.prototype.findIndex = function (callback) { | ||
109 | var i = 0; | ||
110 | for ( i = 0; i < this.length; i++ ) | ||
111 | if (callback(this[i], i, this)) return i; | ||
112 | return -1; | ||
113 | }; | ||
114 | } | ||
115 | |||
116 | var projectApp = angular.module('project', ['ngCookies', 'ngAnimate', 'ui.bootstrap', 'ngRoute', 'ngSanitize'], angular_formpost); | ||
117 | |||
118 | // modify the template tag markers to prevent conflicts with Django | ||
119 | projectApp.config(function($interpolateProvider) { | ||
120 | $interpolateProvider.startSymbol("{["); | ||
121 | $interpolateProvider.endSymbol("]}"); | ||
122 | }); | ||
123 | |||
124 | |||
125 | // add time interval to HH:mm filter | ||
126 | projectApp.filter('timediff', function() { | ||
127 | return function(input) { | ||
128 | function pad(j) { | ||
129 | if (parseInt(j) < 10) {return "0" + j;} | ||
130 | return j; | ||
131 | } | ||
132 | var seconds = parseInt(input); | ||
133 | var minutes = Math.floor(seconds / 60); | ||
134 | seconds = seconds - minutes * 60; | ||
135 | var hours = Math.floor(minutes / 60); | ||
136 | minutes = minutes - hours * 60; | ||
137 | return pad(hours) + ":" + pad(minutes) + ":" + pad(seconds); | ||
138 | }; | ||
139 | }); | ||
140 | |||
141 | // add "time to future" eta that computes time from now to a point in the future | ||
142 | projectApp.filter('toeta', function() { | ||
143 | return function(input) { | ||
144 | var crtmiliseconds = new Date().getTime(); | ||
145 | diff = (parseInt(input) - crtmiliseconds ) / 1000; | ||
146 | console.log("Debug: future time ", input, "crt time", crtmiliseconds, ":", diff); | ||
147 | return diff < 0 ? 300 : diff; | ||
148 | }; | ||
149 | }); | ||
150 | |||
151 | /** | ||
152 | * main controller for the project page | ||
153 | */ | ||
154 | |||
155 | projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $cookieStore, $q, $sce, $anchorScroll, $animate, $sanitize) { | ||
156 | |||
157 | /** | ||
158 | * Retrieves text suggestions for text-edit drop down autocomplete boxes | ||
159 | */ | ||
160 | |||
161 | $scope.getLayersAutocompleteSuggestions = function(currentValue) { | ||
162 | var deffered = $q.defer(); | ||
163 | |||
164 | $http({method:"GET", url: $scope.urls.layers, params : { search: currentValue, format: "json" }}) | ||
165 | .success(function (_data) { | ||
166 | if (_data.error != "ok") { | ||
167 | console.warn("error on data", _data.error); | ||
168 | deffered.reject(_data.error); | ||
169 | } | ||
170 | deffered.resolve(_data.rows); | ||
171 | }); | ||
172 | |||
173 | return deffered.promise; | ||
174 | } | ||
175 | |||
176 | $scope.filterProjectLayerIds = function () { | ||
177 | return $scope.layers.map(function (e) { return e.id; }); | ||
178 | } | ||
179 | |||
180 | $scope.getMachinesAutocompleteSuggestions = function(currentValue) { | ||
181 | var deffered = $q.defer(); | ||
182 | |||
183 | $http({method:"GET", url: $scope.urls.machines, params : { search: currentValue, format: "json" }}) | ||
184 | .success(function (_data) { | ||
185 | if (_data.error != "ok") { | ||
186 | console.warn("error on data", _data.error); | ||
187 | deffered.reject(_data.error); | ||
188 | } | ||
189 | deffered.resolve(_data.rows); | ||
190 | }); | ||
191 | |||
192 | return deffered.promise; | ||
193 | } | ||
194 | |||
195 | $scope.getRecipesAutocompleteSuggestions = function(currentValue) { | ||
196 | var deffered = $q.defer(); | ||
197 | |||
198 | $http({method:"GET", url: $scope.urls.targets, params : { search: currentValue, format: "json" }}) | ||
199 | .success(function (_data) { | ||
200 | if (_data.error != "ok") { | ||
201 | console.warn("error on data", _data.error); | ||
202 | deffered.reject(_data.error); | ||
203 | } | ||
204 | deffered.resolve(_data.rows); | ||
205 | }); | ||
206 | return deffered.promise; | ||
207 | } | ||
208 | |||
209 | $scope.values = function() { | ||
210 | var deffered = $q.defer(); | ||
211 | |||
212 | deffered.resolve(["mama", "tata"]); | ||
213 | |||
214 | return deffered.promise; | ||
215 | }; | ||
216 | |||
217 | $scope.getAutocompleteSuggestions = function(type, currentValue) { | ||
218 | var deffered = $q.defer(); | ||
219 | |||
220 | $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: type, search: currentValue}}) | ||
221 | .success(function (_data) { | ||
222 | if (_data.error != "ok") { | ||
223 | console.warn(_data.error); | ||
224 | deffered.reject(_data.error); | ||
225 | } | ||
226 | deffered.resolve(_data.rows); | ||
227 | }); | ||
228 | |||
229 | return deffered.promise; | ||
230 | }; | ||
231 | |||
232 | var inXHRcall = false; | ||
233 | |||
234 | /** | ||
235 | * XHR call wrapper that automatically handles errors and auto-updates the page content to reflect project state on server side. | ||
236 | */ | ||
237 | $scope._makeXHRCall = function(callparams) { | ||
238 | if (inXHRcall) { | ||
239 | if (callparams.data === undefined) { | ||
240 | // we simply skip the data refresh calls | ||
241 | console.warn("TRC1: race on XHR, aborted"); | ||
242 | return; | ||
243 | } else { | ||
244 | // we return a promise that we'll solve by reissuing the command later | ||
245 | var delayed = $q.defer(); | ||
246 | console.warn("TRC2: race on XHR, delayed"); | ||
247 | $interval(function () {$scope._makeXHRCall(callparams).then(function (d) { delayed.resolve(d); });}, 100, 1); | ||
248 | |||
249 | return delayed.promise; | ||
250 | } | ||
251 | |||
252 | } | ||
253 | var deffered = $q.defer(); | ||
254 | |||
255 | /* we only talk in JSON to the server */ | ||
256 | if (callparams.method == 'GET') { | ||
257 | if (callparams.data === undefined) { | ||
258 | callparams.data = {}; | ||
259 | } | ||
260 | callparams.data.format = "json"; | ||
261 | } else { | ||
262 | if (callparams.url.indexOf("?") > -1) { | ||
263 | callparams.url = callparams.url.split("?").map(function (element, index) { | ||
264 | if (index == 1) { | ||
265 | var elements = []; | ||
266 | if (element.indexOf("&")>-1) { | ||
267 | elements = element.split("&"); | ||
268 | } | ||
269 | elements.push("format=json"); | ||
270 | element = elements.join("&"); | ||
271 | } | ||
272 | return element; | ||
273 | }).join("?"); | ||
274 | } else { | ||
275 | callparams.url += "?format=json"; | ||
276 | } | ||
277 | } | ||
278 | |||
279 | |||
280 | if (undefined === callparams.headers) { callparams.headers = {}; } | ||
281 | callparams.headers['X-CSRFToken'] = $cookies.csrftoken; | ||
282 | |||
283 | $http(callparams).success(function(_data, _status, _headers, _config) { | ||
284 | if (_data.error != "ok") { | ||
285 | console.warn("Failed XHR request (" + _status + "): " + _data.error); | ||
286 | console.error("Failed XHR request: ", _data, _status, _headers, _config); | ||
287 | // stop refreshing hte page | ||
288 | $interval.cancel($scope.pollHandle); | ||
289 | deffered.reject(_data.error); | ||
290 | } | ||
291 | else { | ||
292 | if (_data.layers !== undefined) { | ||
293 | |||
294 | var addedLayers = []; | ||
295 | var deletedLayers = []; | ||
296 | |||
297 | // step 1 - delete entries not found | ||
298 | $scope.layers.forEach(function (elem) { | ||
299 | if (-1 == _data.layers.findIndex(function (elemX) { return elemX.id == elem.id && elemX.name == elem.name; })) { | ||
300 | deletedLayers.push(elem); | ||
301 | } | ||
302 | }); | ||
303 | deletedLayers.forEach(function (elem) { | ||
304 | $scope.layers.splice($scope.layers.indexOf(elem),1); | ||
305 | }); | ||
306 | // step 2 - merge new entries | ||
307 | _data.layers.forEach(function (elem) { | ||
308 | var found = false; | ||
309 | var i; | ||
310 | for (i = 0 ; i < $scope.layers.length; i ++) { | ||
311 | if ($scope.layers[i].orderid < elem.orderid) continue; | ||
312 | if ($scope.layers[i].orderid == elem.orderid) { | ||
313 | found = true; break; | ||
314 | } | ||
315 | if ($scope.layers[i].orderid > elem.orderid) break; | ||
316 | } | ||
317 | if (!found) { | ||
318 | $scope.layers.splice(i, 0, elem); | ||
319 | addedLayers.push(elem); | ||
320 | } | ||
321 | }); | ||
322 | |||
323 | // step 3 - display alerts. | ||
324 | if (addedLayers.length > 0) { | ||
325 | $scope.displayAlert($scope.zone2alerts, | ||
326 | "You have added <b>"+addedLayers.length+"</b> layer" + ((addedLayers.length>1)?"s: ":": ") + addedLayers.map(function (e) { return "<a href=\""+e.layerdetailurl+"\">"+e.name+"</a>"; }).join(", "), | ||
327 | "alert-info"); | ||
328 | // invalidate error layer data based on current layers | ||
329 | $scope.layersForTargets = {}; | ||
330 | } | ||
331 | if (deletedLayers.length > 0) { | ||
332 | $scope.displayAlert($scope.zone2alerts, "You have deleted <b>"+deletedLayers.length+"</b> layer" + ((deletedLayers.length>1)?"s: ":": ") + deletedLayers.map(function (e) { return "<a href=\""+e.layerdetailurl+"\">"+e.name+"</a>"; }).join(", "), "alert-info"); | ||
333 | // invalidate error layer data based on current layers | ||
334 | $scope.layersForTargets = {}; | ||
335 | } | ||
336 | |||
337 | } | ||
338 | |||
339 | |||
340 | if (_data.builds !== undefined) { | ||
341 | var toDelete = []; | ||
342 | // step 1 - delete entries not found | ||
343 | $scope.builds.forEach(function (elem) { | ||
344 | if (-1 == _data.builds.findIndex(function (elemX) { return elemX.id == elem.id; })) { | ||
345 | toDelete.push(elem); | ||
346 | } | ||
347 | }); | ||
348 | toDelete.forEach(function (elem) { | ||
349 | $scope.builds.splice($scope.builds.indexOf(elem),1); | ||
350 | }); | ||
351 | // step 2 - merge new entries | ||
352 | _data.builds.forEach(function (elem) { | ||
353 | var found = false; | ||
354 | var i = 0; | ||
355 | for (i = 0 ; i < $scope.builds.length; i ++) { | ||
356 | if ($scope.builds[i].id > elem.id) continue; | ||
357 | if ($scope.builds[i].id == elem.id) { | ||
358 | found=true; | ||
359 | // do deep data copy | ||
360 | for (var attr in elem) { | ||
361 | $scope.builds[i][attr] = elem[attr]; | ||
362 | } | ||
363 | break; | ||
364 | } | ||
365 | if ($scope.builds[i].id < elem.id) break; | ||
366 | } | ||
367 | if (!found) { | ||
368 | $scope.builds.splice(i, 0, elem); | ||
369 | } | ||
370 | }); | ||
371 | // step 3 - merge "Canceled" builds | ||
372 | $scope.canceledBuilds.forEach(function (elem) { | ||
373 | // mock the build object | ||
374 | var found = false; | ||
375 | var i = 0; | ||
376 | for (i = 0; i < $scope.builds.length; i ++) { | ||
377 | if ($scope.builds[i].id > elem.id) continue; | ||
378 | if ($scope.builds[i].id == elem.id) { found=true; break; } | ||
379 | if ($scope.builds[i].id < elem.id) break; | ||
380 | } | ||
381 | if (!found) { | ||
382 | $scope.builds.splice(i, 0, elem); | ||
383 | } | ||
384 | }); | ||
385 | |||
386 | |||
387 | $scope.fetchLayersForTargets(); | ||
388 | } | ||
389 | if (_data.targets !== undefined) { | ||
390 | $scope.targets = _data.targets; | ||
391 | } | ||
392 | if (_data.machine !== undefined) { | ||
393 | $scope.machine = _data.machine; | ||
394 | } | ||
395 | if (_data.user !== undefined) { | ||
396 | $scope.user = _data.user; | ||
397 | } | ||
398 | |||
399 | if (_data.prj !== undefined) { | ||
400 | $scope.project = _data.prj; | ||
401 | |||
402 | // update breadcrumb, outside the controller | ||
403 | $('#project_name').text($scope.project.name); | ||
404 | } | ||
405 | |||
406 | $scope.validateData(); | ||
407 | inXHRcall = false; | ||
408 | deffered.resolve(_data); | ||
409 | } | ||
410 | }).error(function(_data, _status, _headers, _config) { | ||
411 | if (_status === 0) { | ||
412 | // the server has gone away | ||
413 | // alert("The server is not responding. The application will terminate now"); | ||
414 | $interval.cancel($scope.pollHandle); | ||
415 | } | ||
416 | else { | ||
417 | console.error("Failed HTTP XHR request: ", _data, _status, _headers, _config); | ||
418 | inXHRcall = false; | ||
419 | deffered.reject(_data.error); | ||
420 | } | ||
421 | }); | ||
422 | |||
423 | return deffered.promise; | ||
424 | }; | ||
425 | |||
426 | $scope.layeralert = undefined; | ||
427 | /** | ||
428 | * Verifies and shows user alerts on invalid project data | ||
429 | */ | ||
430 | |||
431 | $scope.validateData = function () { | ||
432 | if ($scope.project.release) { | ||
433 | if ($scope.layers.length === 0) { | ||
434 | $scope.layeralert = $scope.displayAlert($scope.zone1alerts, "You need to add some layers to this project. <a href=\""+$scope.urls.layers+"\">View all layers available in Toaster</a> or <a href=\""+$scope.urls.importlayer+"\">import a layer</a>"); | ||
435 | } else { | ||
436 | if ($scope.layeralert !== undefined) { | ||
437 | $scope.layeralert.close(); | ||
438 | $scope.layeralert = undefined; | ||
439 | } | ||
440 | } | ||
441 | } else { | ||
442 | $scope.layeralert = $scope.displayAlert($scope.zone1alerts, "This project is not set to run builds."); | ||
443 | } | ||
444 | }; | ||
445 | |||
446 | $scope.buildExistingTarget = function(targets) { | ||
447 | $scope.buildTargetList(targets.map(function(v){return ((v.task) ? v.target + ":" + v.task : v.target);})); | ||
448 | }; | ||
449 | |||
450 | $scope.buildTargetList = function(targetlist) { | ||
451 | var oldTargetName = $scope.targetName; | ||
452 | $scope.targetName = targetlist.join(' '); | ||
453 | $scope.buildNamedTarget(); | ||
454 | $scope.targetName = oldTargetName; | ||
455 | }; | ||
456 | |||
457 | $scope.buildNamedTarget = function() { | ||
458 | if ($scope.targetName === undefined && $scope.targetName1 === undefined){ | ||
459 | console.warn("No target defined, please type in a target name"); | ||
460 | return; | ||
461 | } | ||
462 | |||
463 | // this writes the $scope.safeTargetName variable | ||
464 | $scope.sanitizeTargetName(); | ||
465 | |||
466 | $scope._makeXHRCall({ | ||
467 | method: "POST", url: $scope.urls.xhr_build, | ||
468 | data : { | ||
469 | targets: $scope.safeTargetName, | ||
470 | } | ||
471 | }).then(function (data) { | ||
472 | // make sure nobody re-uses the current $safeTargetName | ||
473 | delete $scope.safeTargetName; | ||
474 | console.warn("TRC3: received ", data); | ||
475 | $scope.targetName = undefined; | ||
476 | $scope.targetName1 = undefined; | ||
477 | $location.hash('buildslist'); | ||
478 | // call $anchorScroll() | ||
479 | $anchorScroll(); | ||
480 | }); | ||
481 | }; | ||
482 | |||
483 | $scope.sanitizeTargetName = function() { | ||
484 | $scope.safeTargetName = undefined; | ||
485 | if (undefined === $scope.targetName) $scope.safeTargetName = $scope.targetName1; | ||
486 | if (undefined === $scope.targetName1) $scope.safeTargetName = $scope.targetName; | ||
487 | |||
488 | if (undefined === $scope.safeTargetName) return; | ||
489 | |||
490 | $scope.safeTargetName = $scope.safeTargetName.replace(/\[.*\]/, '').trim(); | ||
491 | }; | ||
492 | |||
493 | $scope.buildCancel = function(build) { | ||
494 | $scope._makeXHRCall({ | ||
495 | method: "POST", url: $scope.urls.xhr_build, | ||
496 | data: { | ||
497 | buildCancel: build.id, | ||
498 | } | ||
499 | }).then( function () { | ||
500 | build.status = "deleted"; | ||
501 | $scope.canceledBuilds.push(build); | ||
502 | }); | ||
503 | }; | ||
504 | |||
505 | $scope.buildDelete = function(build) { | ||
506 | $scope.canceledBuilds.splice($scope.canceledBuilds.indexOf(build), 1); | ||
507 | }; | ||
508 | |||
509 | |||
510 | $scope.onLayerSelect = function (item, model, label) { | ||
511 | $scope.layerToAdd = item; | ||
512 | $scope.layerAddName = item.layer__name; | ||
513 | }; | ||
514 | |||
515 | $scope.machineSelect = function (machineName) { | ||
516 | $scope._makeXHRCall({ | ||
517 | method: "POST", url: $scope.urls.xhr_edit, | ||
518 | data: { | ||
519 | machineName: machineName, | ||
520 | } | ||
521 | }).then(function () { | ||
522 | $scope.machine.name = machineName; | ||
523 | |||
524 | $scope.displayAlert($scope.zone2alerts, "You have changed the machine to: <strong>" + $scope.machine.name + "</strong>", "alert-info"); | ||
525 | var machineDistro = angular.element("#machine-distro"); | ||
526 | |||
527 | angular.element("html, body").animate({ scrollTop: machineDistro.position().top }, 700).promise().done(function() { | ||
528 | $animate.addClass(machineDistro, "machines-highlight"); | ||
529 | }); | ||
530 | }); | ||
531 | }; | ||
532 | |||
533 | |||
534 | $scope.layerAdd = function() { | ||
535 | |||
536 | $http({method:"GET", url: $scope.layerToAdd.layerDetailsUrl, params : {format: "json"}}) | ||
537 | .success(function (_data) { | ||
538 | if (_data.error != "ok") { | ||
539 | console.warn(_data.error); | ||
540 | } else { | ||
541 | /* filter out layers that are already in the project */ | ||
542 | var filtered_list = []; | ||
543 | var projectlayers_ids = $scope.layers.map(function (e) { return e.id }); | ||
544 | for (var i = 0; i < _data.layerdeps.list.length; i++) { | ||
545 | if (projectlayers_ids.indexOf(_data.layerdeps.list[i].id) == -1) { | ||
546 | filtered_list.push( _data.layerdeps.list[i]); | ||
547 | } | ||
548 | } | ||
549 | |||
550 | _data.layerdeps.list = filtered_list; | ||
551 | if (_data.layerdeps.list.length > 0) { | ||
552 | // activate modal | ||
553 | console.log("listing modals"); | ||
554 | var modalInstance = $modal.open({ | ||
555 | templateUrl: 'dependencies_modal', | ||
556 | controller: function ($scope, $modalInstance, items, layerAddName) { | ||
557 | $scope.items = items; | ||
558 | $scope.layerAddName = layerAddName; | ||
559 | $scope.selectedItems = (function () { | ||
560 | var s = {}; | ||
561 | for (var i = 0; i < items.length; i++) | ||
562 | { s[items[i].id] = true; } | ||
563 | return s; | ||
564 | })(); | ||
565 | |||
566 | $scope.ok = function() { | ||
567 | console.warn("TRC4: scope selected is ", $scope.selectedItems); | ||
568 | $modalInstance.close(Object.keys($scope.selectedItems).filter(function (e) { return $scope.selectedItems[e];})); | ||
569 | }; | ||
570 | |||
571 | $scope.cancel = function() { | ||
572 | $modalInstance.dismiss('cancel'); | ||
573 | }; | ||
574 | |||
575 | $scope.update = function() { | ||
576 | console.warn("TRC5: updated ", $scope.selectedItems); | ||
577 | }; | ||
578 | }, | ||
579 | resolve: { | ||
580 | items: function () { | ||
581 | return _data.layerdeps.list; | ||
582 | }, | ||
583 | layerAddName: function () { | ||
584 | return $scope.layerAddName; | ||
585 | }, | ||
586 | } | ||
587 | }); | ||
588 | console.log("built modal instance", modalInstance); | ||
589 | |||
590 | modalInstance.result.then(function (selectedArray) { | ||
591 | selectedArray.push($scope.layerToAdd.id); | ||
592 | console.warn("TRC6: selected", selectedArray); | ||
593 | |||
594 | $scope._makeXHRCall({ | ||
595 | method: "POST", url: $scope.urls.xhr_edit, | ||
596 | data: { | ||
597 | layerAdd: selectedArray.join(","), | ||
598 | } | ||
599 | }).then(function () { | ||
600 | $scope.adjustMostBuiltItems(selectedArray.length); | ||
601 | $scope.layerAddName = undefined; | ||
602 | }); | ||
603 | }); | ||
604 | } | ||
605 | else { | ||
606 | $scope.adjustMostBuiltItems(1); | ||
607 | $scope._makeXHRCall({ | ||
608 | method: "POST", url: $scope.urls.xhr_edit, | ||
609 | data: { | ||
610 | layerAdd: $scope.layerToAdd.id, | ||
611 | } | ||
612 | }).then(function () { | ||
613 | $scope.layerAddName = undefined; | ||
614 | }); | ||
615 | } | ||
616 | } | ||
617 | }); | ||
618 | }; | ||
619 | |||
620 | $scope.layerDel = function(id) { | ||
621 | $scope.adjustMostBuiltItems(-1); | ||
622 | $scope._makeXHRCall({ | ||
623 | method: "POST", url: $scope.urls.xhr_edit, | ||
624 | data: { | ||
625 | layerDel: id, | ||
626 | } | ||
627 | }); | ||
628 | }; | ||
629 | |||
630 | $scope.adjustMostBuiltItems = function(listDelta) { | ||
631 | $scope.layerCount += listDelta; | ||
632 | $scope.mutedtargets = ($scope.layerCount == 0 ? "muted" : ""); | ||
633 | }; | ||
634 | |||
635 | /* | ||
636 | */ | ||
637 | |||
638 | |||
639 | /** | ||
640 | * Verifies if a project settings change would trigger layer updates. If user confirmation is needed, | ||
641 | * a modal dialog will prompt the user to ack the changes. If not, the editProjectSettings() function is called directly. | ||
642 | * | ||
643 | * Only "versionlayers" change for is supported (and hardcoded) for now. | ||
644 | */ | ||
645 | |||
646 | $scope.testProjectSettingsChange = function(elementid) { | ||
647 | if (elementid != '#change-project-version') throw "Not implemented"; | ||
648 | |||
649 | $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "versionlayers", search: $scope.projectVersion }}). | ||
650 | success(function (_data) { | ||
651 | if (_data.error != "ok") { | ||
652 | alert (_data.error); | ||
653 | } | ||
654 | else { | ||
655 | if (_data.rows.length > 0) { | ||
656 | // activate modal | ||
657 | var modalInstance = $modal.open({ | ||
658 | templateUrl: 'change_version_modal', | ||
659 | controller: function ($scope, $modalInstance, items, releaseName, releaseDescription) { | ||
660 | $scope.items = items; | ||
661 | $scope.releaseName = releaseName; | ||
662 | $scope.releaseDescription = releaseDescription; | ||
663 | |||
664 | $scope.ok = function() { | ||
665 | $modalInstance.close(); | ||
666 | }; | ||
667 | |||
668 | $scope.cancel = function() { | ||
669 | $modalInstance.dismiss('cancel'); | ||
670 | }; | ||
671 | |||
672 | }, | ||
673 | resolve: { | ||
674 | items: function () { | ||
675 | return _data.rows; | ||
676 | }, | ||
677 | releaseName: function () { | ||
678 | return $scope.releases.filter(function (e) { if (e.id == $scope.projectVersion) return e;})[0].name; | ||
679 | }, | ||
680 | releaseDescription: function () { | ||
681 | return $scope.releases.filter(function (e) { if (e.id == $scope.projectVersion) return e;})[0].description; | ||
682 | }, | ||
683 | } | ||
684 | }); | ||
685 | |||
686 | modalInstance.result.then(function () { $scope.editProjectSettings(elementid); }); | ||
687 | } else { | ||
688 | $scope.editProjectSettings(elementid); | ||
689 | } | ||
690 | } | ||
691 | }); | ||
692 | }; | ||
693 | |||
694 | /** | ||
695 | * Performs changes to project settings, and updates the user interface accordingly. | ||
696 | */ | ||
697 | |||
698 | $scope.editProjectSettings = function(elementid) { | ||
699 | var data = {}; | ||
700 | console.warn("TRC7: editProjectSettings with ", elementid); | ||
701 | var alertText; | ||
702 | var alertZone; | ||
703 | var oldLayers = []; | ||
704 | |||
705 | switch(elementid) { | ||
706 | case '#select-machine': | ||
707 | alertText = "You have changed the machine to: <strong>" + $scope.machineName + "</strong>"; | ||
708 | alertZone = $scope.zone2alerts; | ||
709 | data.machineName = $scope.machineName; | ||
710 | break; | ||
711 | case '#change-project-name': | ||
712 | data.projectName = $scope.projectName; | ||
713 | alertText = "You have changed the project name to: <strong>" + $scope.projectName + "</strong>"; | ||
714 | alertZone = $scope.zone3alerts; | ||
715 | break; | ||
716 | case '#change-project-version': | ||
717 | data.projectVersion = $scope.projectVersion; | ||
718 | alertText = "You have changed the release to: "; | ||
719 | alertZone = $scope.zone3alerts; | ||
720 | // save old layers | ||
721 | oldLayers = $scope.layers.slice(0); | ||
722 | break; | ||
723 | default: | ||
724 | throw "FIXME: implement conversion for element " + elementid; | ||
725 | } | ||
726 | |||
727 | $scope._makeXHRCall({ | ||
728 | method: "POST", url: $scope.urls.xhr_edit, data: data, | ||
729 | }).then( function (_data) { | ||
730 | $scope.toggle(elementid); | ||
731 | if (data.projectVersion !== undefined) { | ||
732 | alertText += "<strong>" + $scope.project.release.desc + "</strong>. "; | ||
733 | } | ||
734 | if (elementid == '#change-project-version') { | ||
735 | $scope.layersForTargets = {}; // invalidate error layers for the targets, since layers changed | ||
736 | |||
737 | // requirement https://bugzilla.yoctoproject.org/attachment.cgi?id=2229, notification for changed version to include layers | ||
738 | $scope.zone2alerts.forEach(function (e) { e.close(); }); | ||
739 | |||
740 | |||
741 | // warnings - this is executed AFTER the generic XHRCall handling is done; at this point, | ||
742 | if (_data.layers !== undefined) { | ||
743 | // show added/deleted layer notifications; scope.layers is already updated by this point. | ||
744 | var addedLayers = []; | ||
745 | var deletedLayers = []; | ||
746 | _diffArrays( oldLayers, $scope.layers, function (e, f) { return e.id == f.id; }, | ||
747 | function (e) {addedLayers.push(e); }, | ||
748 | function (e) {deletedLayers.push(e); }); | ||
749 | |||
750 | var hasDifferentLayers = (addedLayers.length || deletedLayers.length) | ||
751 | if (hasDifferentLayers) { | ||
752 | alertText += "This has caused the following changes in your project layers:<ul>"; | ||
753 | } | ||
754 | // some of the deleted layers are actually replaced (changed) layers | ||
755 | var changedLayers = []; | ||
756 | deletedLayers.forEach(function (e) { | ||
757 | if ( -1 < addedLayers.findIndex(function (f) { return f.name == e.name; })) { | ||
758 | changedLayers.push(e); | ||
759 | } | ||
760 | }); | ||
761 | |||
762 | changedLayers.forEach(function (e) { | ||
763 | deletedLayers.splice(deletedLayers.indexOf(e), 1); | ||
764 | }); | ||
765 | |||
766 | if (addedLayers.length > 0) { | ||
767 | alertText += "<li><strong>"+addedLayers.length+"</strong> layer" + ((addedLayers.length>1)?"s":"") + " changed to the <strong> " + $scope.project.release.name + " </strong> branch: " + addedLayers.map(function (e) { return "<a href=\""+e.layerdetailurl+"\">"+e.name+"</a>"; }).join(", ") + "</li>"; | ||
768 | } | ||
769 | if (deletedLayers.length > 0) { | ||
770 | alertText += "<li><strong>"+deletedLayers.length+"</strong> layer" + ((deletedLayers.length>1)?"s":"") + " deleted: " + deletedLayers.map(function (e) { return "<a href=\""+e.layerdetailurl+"\">"+e.name+"</a>"; }).join(", ") + "</li>"; | ||
771 | } | ||
772 | |||
773 | } | ||
774 | if (hasDifferentLayers) { | ||
775 | alertText += "</ul>"; | ||
776 | } | ||
777 | } | ||
778 | $scope.displayAlert(alertZone, alertText, "alert-info"); | ||
779 | }); | ||
780 | }; | ||
781 | |||
782 | |||
783 | /** | ||
784 | * Extracts a command passed through the local path in location, and executes/updates UI based on the command | ||
785 | */ | ||
786 | |||
787 | $scope.updateDisplayWithCommands = function() { | ||
788 | |||
789 | function _cmdExecuteWithParam(param, f) { | ||
790 | var cmd = $location.path(); | ||
791 | if (cmd.indexOf(param) === 0) { | ||
792 | if (cmd.indexOf("=") > -1) { | ||
793 | var parameter = cmd.split("=", 2)[1]; | ||
794 | if (parameter !== undefined && parameter.length > 0) { | ||
795 | f(parameter); | ||
796 | } | ||
797 | } else { | ||
798 | f(); | ||
799 | } | ||
800 | } | ||
801 | } | ||
802 | |||
803 | _cmdExecuteWithParam("/newproject", function () { | ||
804 | $scope.displayAlert($scope.zone1alerts, | ||
805 | "Your project <strong>" + $scope.project.name + | ||
806 | "</strong> has been created. You can now <a href=\""+ $scope.urls.layers + | ||
807 | "\">add layers</a> and <a href=\""+ $scope.urls.targets + | ||
808 | "\">select recipes</a> you want to build.", "alert-success"); | ||
809 | }); | ||
810 | |||
811 | _cmdExecuteWithParam("/layerimported", function () { | ||
812 | var imported = $cookieStore.get("layer-imported-alert"); | ||
813 | var text; | ||
814 | |||
815 | if (!imported) | ||
816 | return; | ||
817 | |||
818 | if (imported.deps_added.length === 0) { | ||
819 | text = "You have imported <strong><a href=\""+imported.imported_layer.layerDetailsUrl+"\">"+imported.imported_layer.name+ | ||
820 | "</a></strong> and added it to your project."; | ||
821 | } else { | ||
822 | var links = "<a href=\""+$scope.urls.layer+ | ||
823 | imported.imported_layer.id+"\">"+imported.imported_layer.name+ | ||
824 | "</a>, "; | ||
825 | |||
826 | imported.deps_added.map (function(item, index){ | ||
827 | links +="<a href=\""+item.layerDetailsUrl+"\" >"+item.name+ | ||
828 | "</a>"; | ||
829 | /*If we're at the last element we don't want the trailing comma */ | ||
830 | if (imported.deps_added[index+1] !== undefined) | ||
831 | links += ", "; | ||
832 | }); | ||
833 | |||
834 | /* Length + 1 here to do deps + the imported layer */ | ||
835 | text = "You have imported <strong><a href=\""+$scope.urls.layer+ | ||
836 | imported.imported_layer.id+"\">"+imported.imported_layer.name+ | ||
837 | "</a></strong> and added <strong>"+(imported.deps_added.length+1)+ | ||
838 | "</strong> layers to your project: <strong>"+links+"</strong>"; | ||
839 | } | ||
840 | |||
841 | $scope.displayAlert($scope.zone2alerts, text, "alert-info"); | ||
842 | // This doesn't work | ||
843 | $cookieStore.remove("layer-imported-alert"); | ||
844 | //use jquery plugin instead | ||
845 | $.removeCookie("layer-imported-alert", { path: "/"}); | ||
846 | }); | ||
847 | |||
848 | _cmdExecuteWithParam("/targetbuild=", function (targets) { | ||
849 | var oldTargetName = $scope.targetName; | ||
850 | $scope.targetName = targets.split(",").join(" "); | ||
851 | $scope.buildNamedTarget(); | ||
852 | $scope.targetName = oldTargetName; | ||
853 | $location.path(''); | ||
854 | }); | ||
855 | |||
856 | _cmdExecuteWithParam("/machineselect=", function (machine) { | ||
857 | $scope.machineName = machine; | ||
858 | $scope.machine.name = machine; | ||
859 | $scope.machineSelect(machine); | ||
860 | |||
861 | }); | ||
862 | |||
863 | |||
864 | _cmdExecuteWithParam("/layeradd=", function (layer) { | ||
865 | $scope.layerToAdd = layer; | ||
866 | $scope.layerAdd(); | ||
867 | }); | ||
868 | }; | ||
869 | |||
870 | /** | ||
871 | * Utility function to display an alert to the user | ||
872 | */ | ||
873 | |||
874 | $scope.displayAlert = function(zone, text, type) { | ||
875 | if (zone.maxid === undefined) { zone.maxid = 0; } | ||
876 | var crtid = zone.maxid ++; | ||
877 | angular.forEach(zone, function (o) { o.close(); }); | ||
878 | var o = { | ||
879 | id: crtid, text: text, type: type, | ||
880 | close: function() { | ||
881 | zone.splice((function(id) { | ||
882 | for (var i = 0; i < zone.length; i++) | ||
883 | if (id == zone[i].id) | ||
884 | { return i; } | ||
885 | return undefined; | ||
886 | }) (crtid), 1); | ||
887 | }, | ||
888 | }; | ||
889 | zone.push(o); | ||
890 | return o; | ||
891 | }; | ||
892 | |||
893 | /** | ||
894 | * Toggles display items between label and input box (the edit pencil icon) on selected settings in project page | ||
895 | */ | ||
896 | |||
897 | $scope.toggle = function(id) { | ||
898 | $scope.projectName = $scope.project.name; | ||
899 | $scope.projectVersion = $scope.project.release.id; | ||
900 | $scope.machineName = $scope.machine.name; | ||
901 | |||
902 | angular.element(id).toggle(); | ||
903 | angular.element(id+"-opposite").toggle(); | ||
904 | }; | ||
905 | |||
906 | /** | ||
907 | * Functionality related to "Most build targets" | ||
908 | */ | ||
909 | |||
910 | $scope.enableBuildSelectedTargets = function () { | ||
911 | var keys = Object.keys($scope.mostBuiltTargets); | ||
912 | keys = keys.filter(function (e) { if ($scope.mostBuiltTargets[e]) return e; }); | ||
913 | return keys.length === 0; | ||
914 | }; | ||
915 | |||
916 | $scope.disableBuildCheckbox = function(t) { | ||
917 | if ( $scope.layerCount == 0 ) { | ||
918 | $scope.mostBuiltTargets[t] = 0; | ||
919 | return true; | ||
920 | }; | ||
921 | return false; | ||
922 | } | ||
923 | |||
924 | $scope.buildSelectedTargets = function () { | ||
925 | var keys = Object.keys($scope.mostBuiltTargets); | ||
926 | keys = keys.filter(function (e) { if ($scope.mostBuiltTargets[e]) return e; }); | ||
927 | |||
928 | $scope.buildTargetList(keys); | ||
929 | for (var i = 0; i < keys.length; i++) | ||
930 | { | ||
931 | $scope.mostBuiltTargets[keys[i]] = 0; | ||
932 | } | ||
933 | }; | ||
934 | |||
935 | /** | ||
936 | * Helper function to deal with error string recognition and manipulation | ||
937 | */ | ||
938 | |||
939 | $scope.getTargetNameFromErrorMsg = function (msg) { | ||
940 | return msg.split(" ").splice(2).map(function (v) { return v.replace(/'/g, ''); }); | ||
941 | }; | ||
942 | |||
943 | /** | ||
944 | * Utility function to retrieve which layers can be added to the project if the target was not | ||
945 | * provided by any of the existing project layers | ||
946 | */ | ||
947 | |||
948 | $scope.fetchLayersForTargets = function () { | ||
949 | $scope.builds.forEach(function (buildrequest) { | ||
950 | buildrequest.errors.forEach(function (error) { | ||
951 | if (error.msg.indexOf("Nothin") === 0) { | ||
952 | $scope.getTargetNameFromErrorMsg(error.msg).forEach(function (target) { | ||
953 | if ($scope.layersForTargets[target] === undefined) | ||
954 | $scope.getAutocompleteSuggestions("layers4target", target).then( function (list) { | ||
955 | $scope.layersForTargets[target] = list; | ||
956 | }); | ||
957 | }); | ||
958 | } | ||
959 | }); | ||
960 | }); | ||
961 | }; | ||
962 | |||
963 | |||
964 | /** | ||
965 | * Page init code - just init variables and set the automated refresh | ||
966 | */ | ||
967 | |||
968 | $scope.init = function() { | ||
969 | $scope.canceledBuilds = []; | ||
970 | $scope.layersForTargets = {}; | ||
971 | $scope.fetchLayersForTargets(); | ||
972 | $scope.pollHandle = $interval(function () { $scope._makeXHRCall({method: "GET", url: $scope.urls.xhr_edit, data: undefined});}, 2000, 0); | ||
973 | }; | ||
974 | |||
975 | }); | ||
976 | |||
977 | |||
978 | var _testing_scope; | ||
979 | |||
980 | function test_set_alert(text) { | ||
981 | _testing_scope = angular.element("div#main").scope(); | ||
982 | _testing_scope.displayAlert(_testing_scope.zone3alerts, text); | ||
983 | console.warn("TRC8: zone3alerts", _testing_scope.zone3alerts); | ||
984 | _testing_scope.$digest(); | ||
985 | } | ||