summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/toastergui/static/js/projectapp.js
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster/toastergui/static/js/projectapp.js')
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/projectapp.js985
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
21var 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 */
79function _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
107if (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
116var projectApp = angular.module('project', ['ngCookies', 'ngAnimate', 'ui.bootstrap', 'ngRoute', 'ngSanitize'], angular_formpost);
117
118// modify the template tag markers to prevent conflicts with Django
119projectApp.config(function($interpolateProvider) {
120 $interpolateProvider.startSymbol("{[");
121 $interpolateProvider.endSymbol("]}");
122});
123
124
125// add time interval to HH:mm filter
126projectApp.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
142projectApp.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
155projectApp.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
978var _testing_scope;
979
980function 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}