summaryrefslogtreecommitdiffstats
path: root/bitbake
diff options
context:
space:
mode:
authorAlexandru DAMIAN <alexandru.damian@intel.com>2014-09-09 11:47:13 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2014-10-30 13:39:49 +0000
commit960580cb70ab1c775b49f8a40d41632c080b3cbb (patch)
tree173570e0a9a80b14926c9dbbac995a39c49fbdcb /bitbake
parent1a463ab98ec1220b22a721a1f88ff5af61c33dc3 (diff)
downloadpoky-960580cb70ab1c775b49f8a40d41632c080b3cbb.tar.gz
bitbake: toaster: fix Project page in order to trigger builds
This patch rewrites the Project page and the additional infrastructure in order to fix a bug that makes triggering builds through UI impossible, and to introduce data feeds for suggestions for the user. Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake')
-rw-r--r--bitbake/lib/toaster/orm/models.py49
-rw-r--r--bitbake/lib/toaster/toastergui/static/css/default.css60
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/projectapp.js531
-rw-r--r--bitbake/lib/toaster/toastergui/templates/base.html12
-rw-r--r--bitbake/lib/toaster/toastergui/templates/baseprojectpage.html8
-rw-r--r--bitbake/lib/toaster/toastergui/templates/build.html7
-rw-r--r--bitbake/lib/toaster/toastergui/templates/layers.html8
-rw-r--r--bitbake/lib/toaster/toastergui/templates/newproject.html7
-rw-r--r--bitbake/lib/toaster/toastergui/templates/project.html652
-rw-r--r--bitbake/lib/toaster/toastergui/urls.py2
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py237
11 files changed, 1144 insertions, 429 deletions
diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index 1b3bb22e79..1521717482 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -76,21 +76,25 @@ class Project(models.Model):
76 def schedule_build(self): 76 def schedule_build(self):
77 from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake 77 from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake
78 br = BuildRequest.objects.create(project = self) 78 br = BuildRequest.objects.create(project = self)
79 try:
79 80
80 BRBitbake.objects.create(req = br, 81 BRBitbake.objects.create(req = br,
81 giturl = self.bitbake_version.giturl, 82 giturl = self.bitbake_version.giturl,
82 commit = self.bitbake_version.branch, 83 commit = self.bitbake_version.branch,
83 dirpath = self.bitbake_version.dirpath) 84 dirpath = self.bitbake_version.dirpath)
84 85
85 for l in self.projectlayer_set.all(): 86 for l in self.projectlayer_set.all():
86 BRLayer.objects.create(req = br, name = l.layercommit.layer.name, giturl = l.layercommit.layer.vcs_url, commit = l.layercommit.commit, dirpath = l.layercommit.dirpath) 87 BRLayer.objects.create(req = br, name = l.layercommit.layer.name, giturl = l.layercommit.layer.vcs_url, commit = l.layercommit.commit, dirpath = l.layercommit.dirpath)
87 for t in self.projecttarget_set.all(): 88 for t in self.projecttarget_set.all():
88 BRTarget.objects.create(req = br, target = t.target, task = t.task) 89 BRTarget.objects.create(req = br, target = t.target, task = t.task)
89 for v in self.projectvariable_set.all(): 90 for v in self.projectvariable_set.all():
90 BRVariable.objects.create(req = br, name = v.name, value = v.value) 91 BRVariable.objects.create(req = br, name = v.name, value = v.value)
91 92
92 br.state = BuildRequest.REQ_QUEUED 93 br.state = BuildRequest.REQ_QUEUED
93 br.save() 94 br.save()
95 except Exception as e:
96 br.delete()
97 raise e
94 return br 98 return br
95 99
96class Build(models.Model): 100class Build(models.Model):
@@ -131,7 +135,7 @@ class Build(models.Model):
131 135
132 def eta(self): 136 def eta(self):
133 from django.utils import timezone 137 from django.utils import timezone
134 eta = 0 138 eta = timezone.now()
135 completeper = self.completeper() 139 completeper = self.completeper()
136 if self.completeper() > 0: 140 if self.completeper() > 0:
137 eta = timezone.now() + ((timezone.now() - self.started_on)*(100-completeper)/completeper) 141 eta = timezone.now() + ((timezone.now() - self.started_on)*(100-completeper)/completeper)
@@ -534,12 +538,16 @@ class LayerIndexLayerSource(LayerSource):
534 def _get_json_response(apiurl = self.apiurl): 538 def _get_json_response(apiurl = self.apiurl):
535 import httplib, urlparse, json 539 import httplib, urlparse, json
536 parsedurl = urlparse.urlparse(apiurl) 540 parsedurl = urlparse.urlparse(apiurl)
537 (host, port) = parsedurl.netloc.split(":") 541 try:
542 (host, port) = parsedurl.netloc.split(":")
543 except ValueError:
544 host = parsedurl.netloc
545 port = None
546
538 if port is None: 547 if port is None:
539 port = 80 548 port = 80
540 else: 549 else:
541 port = int(port) 550 port = int(port)
542 #print "-- connect to: http://%s:%s%s?%s" % (host, port, parsedurl.path, parsedurl.query)
543 conn = httplib.HTTPConnection(host, port) 551 conn = httplib.HTTPConnection(host, port)
544 conn.request("GET", parsedurl.path + "?" + parsedurl.query) 552 conn.request("GET", parsedurl.path + "?" + parsedurl.query)
545 r = conn.getresponse() 553 r = conn.getresponse()
@@ -550,8 +558,9 @@ class LayerIndexLayerSource(LayerSource):
550 # verify we can get the basic api 558 # verify we can get the basic api
551 try: 559 try:
552 apilinks = _get_json_response() 560 apilinks = _get_json_response()
553 except: 561 except Exception as e:
554 print "EE: could not connect to %s, skipping update" % self.apiurl 562 import traceback
563 print "EE: could not connect to %s, skipping update: %s\n%s" % (self.apiurl, e, traceback.format_exc(e))
555 return 564 return
556 565
557 # update branches; only those that we already have names listed in the database 566 # update branches; only those that we already have names listed in the database
@@ -582,7 +591,7 @@ class LayerIndexLayerSource(LayerSource):
582 591
583 # update layerbranches/layer_versions 592 # update layerbranches/layer_versions
584 layerbranches_info = _get_json_response(apilinks['layerBranches'] 593 layerbranches_info = _get_json_response(apilinks['layerBranches']
585 + "?filter=branch:%s" % "OR".join(map(lambda x: str(x.up_id), Branch.objects.filter(layer_source = self))) 594 + "?filter=branch:%s" % "OR".join(map(lambda x: str(x.up_id), [i for i in Branch.objects.filter(layer_source = self) if i.up_id is not None] ))
586 ) 595 )
587 for lbi in layerbranches_info: 596 for lbi in layerbranches_info:
588 lv, created = Layer_Version.objects.get_or_create(layer_source = self, 597 lv, created = Layer_Version.objects.get_or_create(layer_source = self,
diff --git a/bitbake/lib/toaster/toastergui/static/css/default.css b/bitbake/lib/toaster/toastergui/static/css/default.css
index 8e0df591df..8780c4f23d 100644
--- a/bitbake/lib/toaster/toastergui/static/css/default.css
+++ b/bitbake/lib/toaster/toastergui/static/css/default.css
@@ -8,10 +8,14 @@
8 8
9/* Styles for the help information */ 9/* Styles for the help information */
10.get-help { color: #CCCCCC; } 10.get-help { color: #CCCCCC; }
11.get-help:hover { color: #999999; cursor: pointer; } 11.get-help:hover, .icon-plus-sign:hover { color: #999999; cursor: pointer; }
12.get-help-blue { color: #3A87AD; } 12.get-help-blue { color: #3A87AD; }
13.get-help-blue:hover { color: #005580; cursor: pointer; } 13.get-help-blue:hover { color: #005580; cursor: pointer; }
14.manual { margin-top: 11px; } 14.get-help-yellow { color: #C09853; }
15.get-help-yellow:hover { color: #B38942; cursor: pointer; }
16.get-help-red { color: #B94A48; font-size: 16px; padding-left: 2px; }
17.get-help-red:hover { color: #943A38; cursor: pointer; }
18.manual { margin: 11px 15px;}
15.heading-help { font-size: 14px; } 19.heading-help { font-size: 14px; }
16 20
17/* Styles for the external link */ 21/* Styles for the external link */
@@ -44,6 +48,7 @@ dd p { line-height: 20px; }
44 48
45/* Some extra space before headings when needed */ 49/* Some extra space before headings when needed */
46.details { margin-top: 30px; } 50.details { margin-top: 30px; }
51.air { margin-top: 30px; }
47 52
48/* Required classes for the highlight behaviour in tables */ 53/* Required classes for the highlight behaviour in tables */
49.highlight { -webkit-animation: target-fade 10s 1; -moz-animation: target-fade 10s 1; animation: target-fade 10s 1; } 54.highlight { -webkit-animation: target-fade 10s 1; -moz-animation: target-fade 10s 1; animation: target-fade 10s 1; }
@@ -96,6 +101,10 @@ th > a, th > span { font-weight: normal; }
96.content-directory a:hover { color: #005580; text-decoration: underline; } 101.content-directory a:hover { color: #005580; text-decoration: underline; }
97.symlink { color: #CCCCCC; } 102.symlink { color: #CCCCCC; }
98 103
104/* Styles for the navbar actions */
105.btn-group + .btn-group { margin-right: 10px; }
106.navbar-inner > .btn-group { margin-top: 6px; }
107
99/* Other styles */ 108/* Other styles */
100.dropdown-menu { padding: 10px; } 109.dropdown-menu { padding: 10px; }
101select { width: auto; } 110select { width: auto; }
@@ -104,6 +113,7 @@ select { width: auto; }
104.progress { margin-bottom: 0px; } 113.progress { margin-bottom: 0px; }
105.lead .badge { font-size: 18px; font-weight: normal; border-radius: 15px; padding: 9px; } 114.lead .badge { font-size: 18px; font-weight: normal; border-radius: 15px; padding: 9px; }
106.well > .lead, .alert .lead { margin-bottom: 0px; } 115.well > .lead, .alert .lead { margin-bottom: 0px; }
116.well-transparent { background-color: transparent; }
107.no-results { margin: 10px 0; } 117.no-results { margin: 10px 0; }
108.task-name { margin-left: 7px; } 118.task-name { margin-left: 7px; }
109.icon-hand-right {color: #CCCCCC; } 119.icon-hand-right {color: #CCCCCC; }
@@ -119,9 +129,14 @@ select { width: auto; }
119/* Configuration styles */ 129/* Configuration styles */
120.icon-trash { color: #B94A48; font-size: 16px; padding-left: 2px; } 130.icon-trash { color: #B94A48; font-size: 16px; padding-left: 2px; }
121.icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; } 131.icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; }
122.icon-pencil, .icon-download-alt { font-size: 16px; color: #0088CC; padding-left: 2px; } 132.icon-pencil, .icon-download-alt, .icon-refresh, .icon-star-empty, .icon-star, .icon-tasks { font-size: 16px; color: #0088CC; padding-left: 2px; }
123.icon-pencil:hover, .icon-download-alt:hover { color: #005580; text-decoration: none; cursor: pointer; } 133.icon-pencil:hover, .icon-download-alt:hover, .icon-refresh:hover, .icon-star-empty:hover, .icon-star:hover, .icon-tasks:hover { color: #005580; text-decoration: none; cursor: pointer; }
124.configuration-list li { line-height: 35px; font-size: 21px; font-weight: 200; } 134.icon-share { padding-left: 2px; }
135.alert-success .icon-refresh, .alert-success .icon-tasks { color: #468847; }
136.alert-success .icon-refresh:hover, .alert-success .icon-tasks:hover { color: #347132; }
137.alert-error .icon-refresh, .alert-error .icon-tasks { color: #b94a48; }
138.alert-error .icon-refresh:hover, .alert-error .icon-tasks:hover { color: #943A38; }
139.configuration-list li, .configuration-list label { line-height: 35px; font-size: 21px; font-weight: 200; margin-bottom: 0px;}
125.configuration-list { font-size: 16px; margin-bottom: 1.5em; } 140.configuration-list { font-size: 16px; margin-bottom: 1.5em; }
126.configuration-list i { font-size: 16px; } 141.configuration-list i { font-size: 16px; }
127/*.configuration-layers { height: 135px; overflow: scroll; }*/ 142/*.configuration-layers { height: 135px; overflow: scroll; }*/
@@ -132,15 +147,46 @@ select { width: auto; }
132.configuration-alert p { margin-bottom: 0px; } 147.configuration-alert p { margin-bottom: 0px; }
133fieldset { padding-left: 19px; } 148fieldset { padding-left: 19px; }
134.project-form { margin-top: 10px; } 149.project-form { margin-top: 10px; }
135.add-layers .btn-block + .btn-block { margin-top: 0px; } 150.add-layers .btn-block + .btn-block, .build .btn-block + .btn-block { margin-top: 0px; }
136input.huge { font-size: 17.5px; padding: 11px 19px; } 151input.huge { font-size: 17.5px; padding: 11px 19px; }
137.build-form { margin-bottom: 0px; padding-left: 20px; } 152.build-form { margin-bottom: 0px; }
153.build-form .input-append { margin-bottom: 0px; }
154.build-form .btn-large { padding: 11px 35px; }
155.build-form p { font-size:17.5px ;margin:12px 0 0 10px;}
156.btn-primary .icon-question-sign, .btn-danger .icon-question-sign { color: #fff; }
157.btn-primary .icon-question-sign:hover, .btn-danger .icon-question-sign:hover { color: #999; }
138a code { color: #0088CC; } 158a code { color: #0088CC; }
139a code:hover { color: #005580; } 159a code:hover { color: #005580; }
140.localconf { font-size: 17.5px; margin-top: 40px; } 160.localconf { font-size: 17.5px; margin-top: 40px; }
141.localconf code { font-size: 17.5px; } 161.localconf code { font-size: 17.5px; }
142#add-layer-dependencies { margin-top: 5px; } 162#add-layer-dependencies { margin-top: 5px; }
163.link-action { font-size: 17.5px; margin-top: 40px; }
164.link-action code { font-size: 17.5px; }
143.artifact { width: 9em; } 165.artifact { width: 9em; }
144.control-group { margin-bottom: 0px; } 166.control-group { margin-bottom: 0px; }
145#project-details form { margin: 0px; } 167#project-details form { margin: 0px; }
146dd form { margin: 10px 0 0 0; } 168dd form { margin: 10px 0 0 0; }
169dd form { margin-bottom: 0px; }
170dl textarea { resize: vertical; }
171.navbar-fixed-top { z-index: 1; }
172.popover { z-index: 2; }
173.btn-danger .icon-trash { color: #fff; }
174.bbappends { list-style-type: none; margin-left: 0; }
175.bbappends li { line-height: 25px; }
176.configuration-list input[type="checkbox"] { margin-top:13px;margin-right:10px; }
177.alert input[type="checkbox"] { margin-top: 0px; margin-right: 3px; }
178.alert ol { padding: 10px 0px 0px 20px; }
179.alert ol > li { line-height: 35px; }
180.dl-vertical form { margin-top: 10px; }
181.scrolling { border: 1px solid #dddddd; height: 154px; overflow: auto; padding: 8px; width: 27.5%; margin-bottom: 10px; }
182.lead .help-block { font-size: 14px; line-height: 20px; font-weight: normal; }
183.button-place .btn { margin: 0 0 20px 0; }
184.tooltip-inner { max-width: 250px; }
185dd > span { line-height: 20px; }
186.new-build { padding: 20px; }
187.new-build li { line-height: 30px; }
188.new-build h6 { margin: 10px 0 0 0; color: #5a5a5a; }
189.new-build h3 { margin: 0; color: #5a5a5a; }
190.new-build form { margin: 5px 0 0; }
191.new-build .input-append { margin-bottom: 0; }
192#build-selected { margin-top: 15px; }
diff --git a/bitbake/lib/toaster/toastergui/static/js/projectapp.js b/bitbake/lib/toaster/toastergui/static/js/projectapp.js
new file mode 100644
index 0000000000..e674d8ffd1
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/static/js/projectapp.js
@@ -0,0 +1,531 @@
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
19angular_formpost = function($httpProvider) {
20 // Use x-www-form-urlencoded Content-Type
21 // By Ezekiel Victor, http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/, no license, with attribution
22 $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
23
24 /**
25 * The workhorse; converts an object to x-www-form-urlencoded serialization.
26 * @param {Object} obj
27 * @return {String}
28 */
29 var param = function(obj) {
30 var query = '', name, value, fullSubName, subName, subValue, innerObj, i;
31
32 for(name in obj) {
33 value = obj[name];
34
35 if(value instanceof Array) {
36 for(i=0; i<value.length; ++i) {
37 subValue = value[i];
38 fullSubName = name + '[' + i + ']';
39 innerObj = {};
40 innerObj[fullSubName] = subValue;
41 query += param(innerObj) + '&';
42 }
43 }
44 else if(value instanceof Object) {
45 for(subName in value) {
46 subValue = value[subName];
47 fullSubName = name + '[' + subName + ']';
48 innerObj = {};
49 innerObj[fullSubName] = subValue;
50 query += param(innerObj) + '&';
51 }
52 }
53 else if(value !== undefined && value !== null)
54 query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
55 }
56
57 return query.length ? query.substr(0, query.length - 1) : query;
58 };
59
60 // Override $http service's default transformRequest
61 $httpProvider.defaults.transformRequest = [function(data) {
62 return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
63 }];
64}
65
66
67/**
68 * Helper to execute callback on elements from array differences; useful for incremental UI updating.
69 * @param {Array} oldArray
70 * @param {Array} newArray
71 * @param {function} compareElements
72 * @param {function} onAdded
73 * @param {function} onDeleted
74 *
75 * no return
76 */
77function _diffArrays(oldArray, newArray, compareElements, onAdded, onDeleted ) {
78 if (onDeleted !== undefined) {
79 oldArray.filter(function (e) { var found = 0; newArray.map(function (f) { if (compareElements(e, f)) {found = 1};}); return !found;}).map(onDeleted);
80 }
81 if (onAdded !== undefined) {
82 newArray.filter(function (e) { var found = 0; oldArray.map(function (f) { if (compareElements(e, f)) {found = 1};}); return !found;}).map(onAdded);
83 }
84}
85
86
87var projectApp = angular.module('project', ['ui.bootstrap', 'ngCookies'], angular_formpost);
88
89// modify the template tag markers to prevent conflicts with Django
90projectApp.config(function($interpolateProvider) {
91 $interpolateProvider.startSymbol("{[");
92 $interpolateProvider.endSymbol("]}");
93});
94
95// main controller for the project page
96projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $q, $sce) {
97
98 $scope.getSuggestions = function(type, currentValue) {
99 var deffered = $q.defer();
100
101 $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: type, value: currentValue}})
102 .success(function (_data) {
103 if (_data.error != "ok") {
104 alert(_data.error);
105 deffered.reject(_data.error);
106 }
107 deffered.resolve(_data.list);
108 });
109
110 return deffered.promise;
111 }
112
113 var inXHRcall = false;
114
115 // default handling of XHR calls that handles errors and updates commonly-used pages
116 $scope._makeXHRCall = function(callparams) {
117 if (inXHRcall) {
118 if (callparams.data === undefined) {
119 // we simply skip the data refresh calls
120 console.log("race on XHR, aborted");
121 return;
122 } else {
123 // we return a promise that we'll solve by reissuing the command later
124 var delayed = $q.defer();
125 console.log("race on XHR, delayed");
126 $interval(function () {$scope._makeXHRCall(callparams).then(function (d) { delayed.resolve(d); });}, 100, 1);
127
128 return delayed.promise;
129 }
130
131 }
132 var deffered = $q.defer();
133
134 if (undefined === callparams.headers) { callparams.headers = {} };
135 callparams.headers['X-CSRFToken'] = $cookies.csrftoken;
136
137 $http(callparams).success(function(_data, _status, _headers, _config) {
138 if (_data.error != "ok") {
139 alert("Failed XHR request (" + _status + "): " + _data.error);
140 console.error("Failed XHR request: ", _data, _status, _headers, _config);
141 deffered.reject(_data.error);
142 }
143 else {
144 // TODO: update screen data if we have fields here
145
146 if (_data.builds !== undefined) {
147
148 var oldbuilds = $scope.builds;
149 $scope.builds = _data.builds;
150
151 // identify canceled builds here, so we can display them.
152 _diffArrays(oldbuilds, $scope.builds,
153 function (e,f) { return e.status == f.status && e.id == f.id }, // compare
154 undefined, // added
155 function (e) { // deleted
156 if (e.status == "deleted") return;
157 e.status = "deleted";
158 for (var i = 0; i < $scope.builds.length; i++) {
159 if ($scope.builds[i].status == "queued" && $scope.builds[i].id > e.id)
160 continue;
161 $scope.builds.splice(i, 0, e);
162 break;
163 }
164 });
165
166 }
167 if (_data.layers !== undefined) {
168 var oldlayers = $scope.layers;
169 $scope.layers = _data.layers;
170
171 // show added/deleted layer notifications
172 var addedLayers = [];
173 var deletedLayers = [];
174 _diffArrays( oldlayers, $scope.layers, function (e, f) { return e.id == f.id },
175 function (e) { console.log("new layer", e);addedLayers.push(e); },
176 function (e) { console.log("del layer", e);deletedLayers.push(e); });
177
178 if (addedLayers.length > 0) {
179 $scope.displayAlert($scope.zone2alerts, "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(", "), "alert-info");
180 }
181 if (deletedLayers.length > 0) {
182 $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");
183 }
184
185 }
186 if (_data.targets !== undefined) {
187 $scope.targets = _data.targets;
188 }
189 if (_data.machine !== undefined) {
190 $scope.machine = _data.machine;
191 }
192 if (_data.user !== undefined) {
193 $scope.user = _data.user;
194 }
195
196 if (_data.prj !== undefined) {
197 $scope.project = _data.prj;
198
199 // update breadcrumb, outside the controller
200 $('#project_name').text($scope.project.name);
201 }
202
203 $scope.validateData();
204 inXHRcall = false;
205 deffered.resolve(_data);
206 }
207 }).error(function(_data, _status, _headers, _config) {
208 alert("Failed HTTP XHR request (" + _status + ")" + _data);
209 console.error("Failed HTTP XHR request: ", _data, _status, _headers, _config);
210 inXHRcall = false;
211 deffered.reject(_data.error);
212 });
213
214 return deffered.promise;
215 }
216
217 $scope.layeralert = undefined;
218 // shows user alerts on invalid project data
219 $scope.validateData = function () {
220 if ($scope.layers.length == 0) {
221 $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>");
222 } else {
223 if ($scope.layeralert != undefined) {
224 $scope.layeralert.close();
225 $scope.layeralert = undefined;
226 }
227 }
228 }
229
230 $scope.targetExistingBuild = function(targets) {
231 var oldTargetName = $scope.targetName;
232 $scope.targetName = targets.map(function(v,i,a){return v.target}).join(' ');
233 $scope.targetNamedBuild();
234 $scope.targetName = oldTargetName;
235 }
236
237 $scope.targetNamedBuild = function(target) {
238 if ($scope.targetName === undefined){
239 alert("No target defined, please type in a target name");
240 return;
241 }
242
243 $scope.sanitizeTargetName();
244
245 $scope._makeXHRCall({
246 method: "POST", url: $scope.urls.xhr_build,
247 data : {
248 targets: $scope.targetName
249 }
250 }).then(function (data) {
251 console.log("received ", data);
252 $scope.targetName = undefined;
253 });
254 }
255
256 $scope.sanitizeTargetName = function() {
257 if (undefined === $scope.targetName) return;
258 $scope.targetName = $scope.targetName.replace(/\[.*\]/, '').trim();
259 }
260
261 $scope.buildCancel = function(id) {
262 $scope._makeXHRCall({
263 method: "POST", url: $scope.urls.xhr_build,
264 data: {
265 buildCancel: id,
266 }
267 });
268 }
269
270 $scope.onLayerSelect = function (item, model, label) {
271 $scope.layerAddId = item.id;
272 }
273
274 $scope.layerAdd = function() {
275
276 $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "layerdeps", value: $scope.layerAddId }})
277 .success(function (_data) {
278 if (_data.error != "ok") {
279 alert(_data.error);
280 } else {
281 if (_data.list.length > 0) {
282 // activate modal
283 var modalInstance = $modal.open({
284 templateUrl: 'dependencies_modal',
285 controller: function ($scope, $modalInstance, items, layerAddName) {
286 $scope.items = items;
287 $scope.layerAddName = layerAddName;
288 $scope.selectedItems = (function () { s = {}; for (var i = 0; i < items.length; i++) { s[items[i].id] = true; };return s; })();
289
290 $scope.ok = function() {
291 console.log("scope selected is ", $scope.selectedItems);
292 $modalInstance.close(Object.keys($scope.selectedItems).filter(function (e) { return $scope.selectedItems[e];}));
293 };
294
295 $scope.cancel = function() {
296 $modalInstance.dismiss('cancel');
297 };
298
299 $scope.update = function() {
300 console.log("updated ", $scope.selectedItems);
301 };
302 },
303 resolve: {
304 items: function () {
305 return _data.list;
306 },
307 layerAddName: function () {
308 return $scope.layerAddName;
309 },
310 }
311 });
312
313 modalInstance.result.then(function (selectedArray) {
314 selectedArray.push($scope.layerAddId);
315 console.log("selected", selectedArray);
316
317 $scope._makeXHRCall({
318 method: "POST", url: $scope.urls.xhr_edit,
319 data: {
320 layerAdd: selectedArray.join(","),
321 }
322 }).then(function () {
323 $scope.layerAddName = undefined;
324 });
325 });
326 }
327 else {
328 $scope._makeXHRCall({
329 method: "POST", url: $scope.urls.xhr_edit,
330 data: {
331 layerAdd: $scope.layerAddId,
332 }
333 }).then(function () {
334 $scope.layerAddName = undefined;
335 });
336 }
337 }
338 });
339 }
340
341 $scope.layerDel = function(id) {
342 $scope._makeXHRCall({
343 method: "POST", url: $scope.urls.xhr_edit,
344 data: {
345 layerDel: id,
346 }
347 });
348 }
349
350
351 $scope.test = function(elementid) {
352 $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "versionlayers", value: $scope.projectVersion }}).
353 success(function (_data) {
354 if (_data.error != "ok") {
355 alert (_data.error);
356 }
357 else {
358 if (_data.list.length > 0) {
359 // activate modal
360 var modalInstance = $modal.open({
361 templateUrl: 'change_version_modal',
362 controller: function ($scope, $modalInstance, items, releaseName) {
363 $scope.items = items;
364 $scope.releaseName = releaseName;
365
366 $scope.ok = function() {
367 $modalInstance.close();
368 };
369
370 $scope.cancel = function() {
371 $modalInstance.dismiss('cancel');
372 };
373
374 },
375 resolve: {
376 items: function () {
377 return _data.list;
378 },
379 releaseName: function () {
380 return $scope.releases.filter(function (e) { if (e.id == $scope.projectVersion) return e;})[0].name;
381 },
382 }
383 });
384
385 modalInstance.result.then(function () { $scope.edit(elementid)});
386 } else {
387 $scope.edit(elementid);
388 }
389 }
390 });
391 }
392
393 $scope.edit = function(elementid) {
394 var data = {};
395 console.log("edit with ", elementid);
396 var alertText = undefined;
397 var alertZone = undefined;
398 switch(elementid) {
399 case '#select-machine':
400 alertText = "You have changed the machine to: <b>" + $scope.machineName + "</b>";
401 alertZone = $scope.zone2alerts;
402 data['machineName'] = $scope.machineName;
403 break;
404 case '#change-project-name':
405 data['projectName'] = $scope.projectName;
406 alertText = "You have changed the project name to: <b>" + $scope.projectName + "</b>";
407 alertZone = $scope.zone3alerts;
408 break;
409 case '#change-project-version':
410 data['projectVersion'] = $scope.projectVersion;
411 alertText = "You have changed the release to: ";
412 alertZone = $scope.zone3alerts;
413 break;
414 default:
415 throw "FIXME: implement conversion for element " + elementid;
416 }
417
418 console.log("calling edit with ", data);
419 $scope._makeXHRCall({
420 method: "POST", url: $scope.urls.xhr_edit, data: data,
421 }).then( function () {
422 $scope.toggle(elementid);
423 if (data['projectVersion'] != undefined) {
424 alertText += "<b>" + $scope.release.name + "</b>";
425 }
426 $scope.displayAlert(alertZone, alertText, "alert-info");
427 });
428 }
429
430
431 $scope.executeCommands = function() {
432 cmd = $location.path();
433
434 function _cmdExecuteWithParam(param, f) {
435 if (cmd.indexOf(param)==0) {
436 if (cmd.indexOf("=") > -1) {
437 var parameter = cmd.split("=", 2)[1];
438 if (parameter != undefined && parameter.length > 0) {
439 f(parameter);
440 }
441 } else {
442 f();
443 };
444 }
445 }
446
447 _cmdExecuteWithParam("/newproject", function () {
448 $scope.displayAlert($scope.zone1alerts,
449 "Your project <strong>" + $scope.project.name +
450 "</strong> has been created. You can now <a href=\""+ $scope.urls.layers +
451 "\">add layers</a> and <a href=\""+ $scope.urls.targets +
452 "\">select targets</a> you want to build.", "alert-success");
453 });
454
455 _cmdExecuteWithParam("/targetbuild=", function (targets) {
456 var oldTargetName = $scope.targetName;
457 $scope.targetName = targets.split(",").join(" ");
458 $scope.targetNamedBuild();
459 $scope.targetName = oldTargetName;
460 });
461
462 _cmdExecuteWithParam("/machineselect=", function (machine) {
463 $scope.machineName = machine;
464 $scope.toggle('#select-machine');
465 });
466
467
468 _cmdExecuteWithParam("/layeradd=", function (layer) {
469 angular.forEach(layer.split(","), function (l) {
470 $scope.layerAddId = l;
471 $scope.layerAdd();
472 });
473 });
474 }
475
476 $scope.displayAlert = function(zone, text, type) {
477 if (zone.maxid === undefined) { zone.maxid = 0; }
478 var crtid = zone.maxid ++;
479 angular.forEach(zone, function (o) { o.close() });
480 o = {
481 id: crtid, text: $sce.trustAsHtml(text), type: type,
482 close: function() {
483 zone.splice((function(id){ for (var i = 0; i < zone.length; i++) if (id == zone[i].id) { return i}; return undefined;})(crtid), 1);
484 },
485 }
486 zone.push(o);
487 return o;
488 }
489
490 $scope.toggle = function(id) {
491 $scope.projectName = $scope.project.name;
492 $scope.projectVersion = $scope.project.release.id;
493 $scope.machineName = $scope.machine.name;
494
495 angular.element(id).toggle();
496 angular.element(id+"-opposite").toggle();
497 }
498
499 $scope.selectedMostBuildTargets = function () {
500 keys = Object.keys($scope.mostBuiltTargets);
501 keys = keys.filter(function (e) { if ($scope.mostBuiltTargets[e]) return e });
502 return keys.length == 0;
503
504 }
505
506 // init code
507 //
508 $scope.init = function() {
509 $scope.pollHandle = $interval(function () { $scope._makeXHRCall({method: "POST", url: $scope.urls.xhr_edit, data: undefined});}, 4000, 0);
510 }
511
512 $scope.init();
513});
514
515
516/**
517 TESTING CODE
518*/
519
520function test_diff_arrays() {
521 _diffArrays([1,2,3], [2,3,4], function(e,f) { return e==f; }, function(e) {console.log("added", e)}, function(e) {console.log("deleted", e);})
522}
523
524var s = undefined;
525
526function test_set_alert(text) {
527 s = angular.element("div#main").scope();
528 s.displayAlert(s.zone3alerts, text);
529 console.log(s.zone3alerts);
530 s.$digest();
531}
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html
index 9ef249aab3..d414bfbbde 100644
--- a/bitbake/lib/toaster/toastergui/templates/base.html
+++ b/bitbake/lib/toaster/toastergui/templates/base.html
@@ -61,18 +61,6 @@ function reload_params(params) {
61 {%if MANAGED %} 61 {%if MANAGED %}
62 <div class="btn-group pull-right"> 62 <div class="btn-group pull-right">
63 <a class="btn" href="{% url 'newproject' %}">New project</a> 63 <a class="btn" href="{% url 'newproject' %}">New project</a>
64 <button class="btn dropdown-toggle" data-toggle="dropdown">
65 <i class="icon-caret-down"></i>
66 </button>
67 <ul class="dropdown-menu">
68{% for prj in projects %}
69 <li><a href="{% url 'project' prj.id %}">{{prj.name}}</a></li>
70{% endfor %}
71 <li><hr/></li>
72 <li><a href="#">Clone project</a></li>
73 <li><a href="#">Export project</a></li>
74 <li><a href="#">Import project</a></li>
75 </ul>
76 </div> 64 </div>
77 {%endif%} 65 {%endif%}
78 <a class="pull-right manual" target="_blank" href="http://www.yoctoproject.org/documentation/toaster-manual"> 66 <a class="pull-right manual" target="_blank" href="http://www.yoctoproject.org/documentation/toaster-manual">
diff --git a/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html b/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html
index 54edaaf27c..95a9f470ba 100644
--- a/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html
+++ b/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html
@@ -12,7 +12,7 @@
12 {% block parentbreadcrumb %} 12 {% block parentbreadcrumb %}
13 {% if project %} 13 {% if project %}
14 <li> 14 <li>
15 <a href="{%url 'project' project.id %}">{{project.name}} 15 <a href="{%url 'project' project.id %}"><span id="project_name">{{project.name}}</span>
16 </a> 16 </a>
17 </li> 17 </li>
18 {% endif %} 18 {% endif %}
@@ -28,11 +28,11 @@
28 </script> 28 </script>
29 </div> 29 </div>
30 30
31 <div class="row-fluid"> 31 <div>
32 32
33 <!-- Begin right container --> 33 <!-- Begin main page container -->
34 {% block projectinfomain %}{% endblock %} 34 {% block projectinfomain %}{% endblock %}
35 <!-- End right container --> 35 <!-- End main container -->
36 36
37 37
38 </div> 38 </div>
diff --git a/bitbake/lib/toaster/toastergui/templates/build.html b/bitbake/lib/toaster/toastergui/templates/build.html
index faabd22f8b..bef1f15399 100644
--- a/bitbake/lib/toaster/toastergui/templates/build.html
+++ b/bitbake/lib/toaster/toastergui/templates/build.html
@@ -127,6 +127,13 @@
127 <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a> 127 <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a>
128 {% endif %} 128 {% endif %}
129 </td> 129 </td>
130 {% if MANAGED %}
131 <td class="project">
132 {% if build.project %}
133 <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a>
134 {% endif %}
135 </td>
136 {% endif %}
130 </tr> 137 </tr>
131 138
132 {% endfor %} 139 {% endfor %}
diff --git a/bitbake/lib/toaster/toastergui/templates/layers.html b/bitbake/lib/toaster/toastergui/templates/layers.html
index 281b72aec5..b32a7ed2e2 100644
--- a/bitbake/lib/toaster/toastergui/templates/layers.html
+++ b/bitbake/lib/toaster/toastergui/templates/layers.html
@@ -85,14 +85,6 @@
85 </div> 85 </div>
86 </div> 86 </div>
87 87
88 <script src="assets/js/jquery-1.9.1.min.js" type='text/javascript'></script>
89 <script src="assets/js/jquery.tablesorter.min.js" type='text/javascript'></script>
90 <script src="assets/js/jquery-ui-1.10.3.custom.min.js"></script>
91 <script src="assets/js/bootstrap.min.js" type='text/javascript'></script>
92 <script src="assets/js/prettify.js" type='text/javascript'></script>
93 <script src="assets/js/jit.js" type='text/javascript'></script>
94 <script src="assets/js/main.js" type='text/javascript'></script>
95
96 <script> 88 <script>
97 $(document).ready(function() { 89 $(document).ready(function() {
98 90
diff --git a/bitbake/lib/toaster/toastergui/templates/newproject.html b/bitbake/lib/toaster/toastergui/templates/newproject.html
index 12c4e9f07f..43c4e28bee 100644
--- a/bitbake/lib/toaster/toastergui/templates/newproject.html
+++ b/bitbake/lib/toaster/toastergui/templates/newproject.html
@@ -17,13 +17,6 @@
17 <label>Project name <span class="muted">(required)</span></label> 17 <label>Project name <span class="muted">(required)</span></label>
18 <input type="text" class="input-xlarge" required name="projectname" value="{{projectname}}"> 18 <input type="text" class="input-xlarge" required name="projectname" value="{{projectname}}">
19 <label class="project-form"> 19 <label class="project-form">
20 Project owner
21 <i class="icon-question-sign get-help" title="The go-to person for this project"></i>
22 </label>
23 <input type="text" name="username" value="{{username}}">
24 <label class="project-form">Owner's email</label>
25 <input type="email" class="input-large" name="email" value="{{email}}">
26 <label class="project-form">
27 Yocto Project version 20 Yocto Project version
28 <i class="icon-question-sign get-help" title="This sets the branch for the Yocto Project core layers (meta, meta-yocto and meta-yocto-bsp), and for the layers you use from the OpenEmbedded Metadata Index"></i> 21 <i class="icon-question-sign get-help" title="This sets the branch for the Yocto Project core layers (meta, meta-yocto and meta-yocto-bsp), and for the layers you use from the OpenEmbedded Metadata Index"></i>
29 </label> 22 </label>
diff --git a/bitbake/lib/toaster/toastergui/templates/project.html b/bitbake/lib/toaster/toastergui/templates/project.html
index c3a470c54a..9399b312ca 100644
--- a/bitbake/lib/toaster/toastergui/templates/project.html
+++ b/bitbake/lib/toaster/toastergui/templates/project.html
@@ -1,366 +1,354 @@
1{% extends "base.html" %} 1{% extends "baseprojectpage.html" %}
2<!--
3vim: expandtab tabstop=2
4-->
2{% load projecttags %} 5{% load projecttags %}
3{% load humanize %} 6{% load humanize %}
4{% block pagecontent %} 7{% load static %}
5
6<script>
7
8var buildrequests = [];
9
10function targetInPage(targetname) {
11 return targetname in $("ul#target-list > li > a").map(function (i, x) {return x.text});
12}
13
14function setEventHandlers() {
15 $("i#del-target-icon").unbind().click(function (evt) {
16 console.log("del target", evt.target.attributes["x-data"].value);
17 postEditAjaxRequest({"targetDel": evt.target.attributes["x-data"].value});
18 });
19 $("button#add-target-button").unbind().click( function (evt) {
20 if ( $("input#target")[0].value.length == 0) {
21 alert("cannot add empty target");
22 return;
23 }
24 postEditAjaxRequest({"targetAdd" : $("input#target")[0].value});
25 });
26}
27
28function onEditPageUpdate(data) {
29 // update targets
30 var i; var orightml = "";
31
32 $("span#target-count").html(data.targets.length);
33 for (i = 0; i < data.targets.length; i++) {
34 if (! targetInPage(data.targets[i].target)) {
35 orightml += '<li><a href="#">'+data.targets[i].target;
36 if (data.targets[i].task != "" && data.targets[i].task !== null) {
37 orightml += " ("+data.targets[i].task+")";
38 }
39 orightml += '</a><i title="" data-original-title="" class="icon-trash" id="del-target-icon" x-data="'+data.targets[i].pk+'"></i></li>';
40 }
41 }
42
43 $("ul#target-list").html(orightml);
44
45 // update recent builds
46
47 setEventHandlers();
48}
49
50function onEditAjaxSuccess(data, textstatus) {
51 console.log("XHR returned:", data, "(" + textstatus + ")");
52 if (data.error != "ok") {
53 alert("error on request:\n" + data.error);
54 return;
55 }
56 onEditPageUpdate(data);
57}
58
59function onEditAjaxError(jqXHR, textstatus, error) {
60 alert("XHR errored:\n" + error + "\n(" + textstatus + ")");
61}
62
63function postEditAjaxRequest(reqdata) {
64 var ajax = $.ajax({
65 type:"POST",
66 data: $.param(reqdata),
67 url:"{% url 'xhr_projectedit' project.id%}",
68 headers: { 'X-CSRFToken': $.cookie("csrftoken")},
69 success: onEditAjaxSuccess,
70 error: onEditAjaxError,
71 })
72}
73
74
75
76
77$(document).ready(function () {
78 setEventHandlers();
79
80 /* Provide XHR calls for the "build" buttons.*/
81 $("button#build-all-button").click( function (evt) {
82 var ajax = $.ajax({
83 type:"POST",
84 url:"{% url 'xhr_projectbuild' project.id %}",
85 headers: { 'X-CSRFToken': $.cookie("csrftoken")},
86 success: function (data, textstatus) {
87 if (data.error != "ok") {
88 alert("XHR fail: " + data.error );
89 }
90 },
91 error: function (jqXHR, textstatus, error) { alert("XHR errored:" + error + "(" + textstatus + ")"); },
92 })
93 });
94});
95
96
97</script>
98 8
99 9
100 <div class="page-header"> 10{% block projectinfomain %}
101 <h1> 11<script src="{% static "js/angular.min.js" %}"></script>
102 {{project.name}} 12<script src="{% static "js/angular-cookies.min.js" %}"></script>
103 {% if project.build_set.all.count == 0 %} 13<script src="{% static "js/ui-bootstrap-tpls-0.11.0.js" %}"></script>
104 <small>No builds yet</small>
105 {% else %}
106 <small><a href="#">{{project.build_set.all.count}} builds</a></small>
107 {% endif %}
108 </h1>
109 </div>
110 14
111 15
112 <div class="well"> 16<div id="main" role="main" ng-app="project" ng-controller="prjCtrl" class="top-padded">
113 <form class="build-form">
114 <div class="input-append input-prepend controls">
115 <input type="text" class="huge span7" placeholder="Type the target(s) you want to build" autocomplete="off" data-minLength="1" data-autocomplete="off"
116 data-provide="typeahead" data-source='["core-image-base [meta | daisy]",
117 "core-image-clutter [meta | daisy]",
118 "core-image-directfb [meta | daisy]",
119 "core-image-myimage [meta-imported-layer | 3e1dbabbf3&hellip;]",
120 "core-image-anotherimage [meta-imported-layer | master]",
121 "core-image-full-cmdline [meta | daisy]",
122 "core-image-lsb [meta | daisy]",
123 "core-image-lsb-dev [meta | daisy]",
124 "core-image-lsb-sdk [meta| daisy]",
125 "core-image-minimal [meta| daisy]"
126 ]'>
127 <a href="#" id="build-button" class="btn btn-large btn-primary" disabled>
128 Build
129 <i class="icon-question-sign get-help heading-help" style="margin-left: 5px;" title="Type the name of one or more targets you want to build, separated by a space. You can also specify a task by appending a semicolon and a task name to a target name, like so: <code>core-image-minimal:do_build</code>"></i>
130 </a>
131 </div>
132 <p>
133 <a href="all-targets.html" style="padding-right: 5px;">
134 View all targets
135 </a>
136 |
137 <a href="{% url 'projectbuilds' project.id%}" style="padding-left:5px;">
138 View all project builds ({{project.build_set.count}})
139 </a>
140 </form>
141 </div>
142 17
143 18
19 <!-- project name -->
20 <div class="page-header">
21 <h1>{[project.name]}</h1>
22 </div>
144 23
24 <!-- alerts section 1-->
25 <div ng-repeat="a in zone1alerts">
26 <div class="alert alert-dismissible lead" role="alert" ng-class="a.type"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span></button>
27 <span ng-bind-html="a.text"></span>
28 </div>
29 </div>
145 30
31 <!-- custom templates for ng -->
146 32
147 {% if builds|length > 0 or buildrequests|length > 0 %} 33 <script type="text/ng-template" id="suggestion_details">
148 <h2 class="air">Recent Builds</h2> 34 <a> {[match.model.name]} {[match.model.detail]} </a>
35 </script>
149 36
150 <div id="scheduled-builds"> 37 <!-- modal dialogs -->
151 {% for br in buildrequests %} 38 <script type="text/ng-template" id="dependencies_modal">
152<div class="alert {% if br.0.state == br.0.REQ_FAILED%}alert-error{%else%}alert-info{%endif%}" id="build-request"> 39 <div class="modal-header">
153 <div class="row-fluid"> 40 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
154 <div class="lead span4"> 41 <h3><span ng-bind="layerAddName"></span> dependencies</h3>
155 <span> 42 </div>
156 {{br.0.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} {{br.1.machine.value}} (Created {{br.0.created}}) 43 <div class="modal-body">
157 </span> 44 <p><strong>{[layerAddName]}</strong> depends on some layers that are not added to your project. Select the ones you want to add:</p>
45 <ul class="unstyled">
46 <li ng-repeat="ld in items">
47 <label class="checkbox">
48 <input type="checkbox" ng-model="selectedItems[ld.id]"> {[ld.name]}
49 </label>
50 </li>
51 </ul>
52 </div>
53 <div class="modal-footer">
54 <button class="btn btn-primary" ng-click="ok()">Add layers</button>
55 <button class="btn" ng-click="cancel()">Cancel</button>
56 </div>
57 </form>
58 </script>
59
60
61 <script type="text/ng-template" id="change_version_modal">
62 <div class="modal-header">
63 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
64 <h3>Changing release to {[releaseName]}</h3>
65 </div>
66 <div class="modal-body">
67 <p>The following project layers do not exist for {[releaseName]}:</p>
68 <ul>
69 <li ng-repeat="i in items"><span class="layer-info" data-toggle="tooltip" tooltip="{[i.detail]}">{[i.name]}</span></li>
70 </ul>
71 <p>If you change the release to {[releaseName]}, the above layers will be deleted from your project layers.</p>
72 </div>
73 <div class="modal-footer">
74 <button class="btn btn-primary" ng-click="ok()">Change release and delete layers</button>
75 <button class="btn" ng-click="cancel()">Cancel</button>
76 </div>
77 </script>
78
79 <!-- build form -->
80 <div class="well">
81 <form class="build-form" ng-submit="targetNamedBuild()">
82 <div class="input-append input-prepend controls">
83 <input type="text" class="huge span7 " placeholder="Type the target(s) you want to build" autocomplete="off" ng-model="targetName" typeahead="e.name for e in getSuggestions('targets', $viewValue)|filter:$viewValue" typeahead-template-url="suggestion_details" ng-disabled="!layers.length"/>
84 <button type="submit" id="build-button" class="btn btn-large btn-primary" ng-disabled="!targetName.length">
85 Build
86 <i class="icon-question-sign get-help heading-help" style="margin-left: 5px;" data-toggle="tooltip" title="Type the name of one or more targets you want to build, separated by a space. You can also specify a task by appending a semicolon and a task name to a target name, like so: <code>core-image-minimal:do_build</code>"></i>
87 </button>
88 </div>
89 <p>
90 <a href="{% url 'targets' %}" style="padding-right: 5px;">
91 View all targets
92 </a>
93 {% if completedbuilds.count %}
94 | <a href="{% url 'projectbuilds' project.id %}">View all project builds ({{completedbuilds.count}})</a>
95 {% endif %}
96 </p>
97 </form>
98 </div>
99
100 <h2 class="air" ng-if="builds.length">Latest builds</h2>
101
102 <div class="alert" ng-repeat="b in builds" ng-class="{'queued':'alert-info', 'deleted':'alert-info', 'in progress': 'alert-info', 'In Progress':'alert-info', 'Succeeded':'alert-success', 'failed':'alert-error', 'Failed':'alert-error'}[b.status]">
103 <div class="row-fluid">
104 <switch ng-switch="b.status">
105 <case ng-switch-when="failed">
106 <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
107 <div class="row-fluid">
108 <div class="air well" ng-repeat="e in b.errors">
109 {[e.type]}: <pre>{[e.msg]}</pre>
110 </div>
111 </div>
112 </case>
113 <case ng-switch-when="queued">
114 <div class="lead span5"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
115 <div class="span4 lead" >Build queued
116 <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>
117 </div>
118 <button class="btn pull-right btn-info" ng-click="buildCancel(b.id)">Cancel</button>
119 </case>
120 <case ng-switch-when="created">
121 <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
122 <div class="span6" >
123 <span class="lead">Creating build</span>
158 </div> 124 </div>
159 <div class="span2"> 125 <button class="btn pull-right btn-info" ng-click="buildCancel(b.id)">Cancel</button>
160 {{br.0.get_state_display}} 126 </case>
127 <case ng-switch-when="deleted">
128 <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
129 <div class="span6" id="{[b.id]}-deleted" >
130 <span class="lead">Build deleted</span>
161 </div> 131 </div>
162 <div class="span8"> 132 <button class="btn pull-right btn-info" ng-click="builds.splice(builds.indexOf(b), 1)">Close</button>
163{% if br.state == br.REQ_FAILED%} 133 </case>
164 {% for bre in br.0.brerror_set.all %} {{bre.errmsg}} ({{bre.errtype}}) <br/><hr/><code>{{bre.traceback}}</code>{%endfor%} 134 <case ng-switch-when="in progress">
165{%endif%} 135 <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
136 <div class="span4" >
166 </div> 137 </div>
138 <div class="lead pull-right">Build starting shortly</div>
139 </case>
140 <case ng-switch-when="In Progress">
141 <div class="span4" >
142 <div class="progress" style="margin-top:5px;" data-toggle="tooltip" tooltip="{[b.completeper]}% of tasks complete">
143 <div style="width: {[b.completeper]}%;" class="bar"></div>
144 </div>
145 </div>
146 <div class="lead pull-right">ETA: at {[b.eta]}</div>
147 </case>
148 <case ng-switch-default="">
149 <div class="lead span3"><a href="{[b.build_page_url]}"><span ng-repeat="t in b.targets">{[t.target]} </span> </div></a>
150 <div class="span2 lead">
151 {[b.completed_on|date:'dd/MM/yy HH:mm']}
152 </div>
153 <div class="span2"><span>{[b.errors.len]}</span></div>
154 <div class="span2"><span>{[b.warnings.len]}</span></div>
155 <div> <span class="lead">Build time: {[b.build_time|date:"HH:mm"]}</span>
156 <button class="btn pull-right" ng-class="{'Succeeded': 'btn-success', 'Failed': 'btn-danger'}[b.status]"
157 ng-click="targetExistingBuild(b.targets)">Run again</button>
167 158
168 </div> 159 </div>
169</div> 160 </case>
161 </switch>
162 <div class="lead pull-right">
163 </div>
164 </div>
165 </div>
170 166
171 {% endfor %} 167 <h2 class="air">Project configuration</h2>
172 168
169 <!-- alerts section 2 -->
170 <div ng-repeat="a in zone2alerts">
171 <div class="alert alert-dismissible lead" role="alert" ng-class="a.type"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span></button>
172 <span ng-bind-html="a.text"></span>
173 </div>
174 </div>
175
176 <div class="row-fluid">
177
178 <!-- project layers -->
179 <div id="layer-container" class="well well-transparent span4">
180 <h3>
181 Project layers <span class="muted counter">({[layers.length]})</span>
182 <i class="icon-question-sign get-help heading-help" 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>"></i>
183 </h3>
184 <div class="alert" ng-if="!layers.length">
185 <b>You need to add some layers </b>
186 <p>
187 You can:
188 <ul>
189 <li> <a href="{% url 'layers'%}">View all layers available in Toaster</a>
190 <li> <a href="{% url 'importlayer' %}">Import a layer</a>
191 <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>
192 </ul>
193 Or type a layer name below.
194 </p>
195 </div>
196 <form class="input-append" ng-submit="layerAdd()">
197 <input type="text" class="input-xlarge" id="layer" autocomplete="off" placeholder="Type a layer name" data-minLength="1" ng-model="layerAddName" typeahead="e.name for e in getSuggestions('layers', $viewValue)|filter:$viewValue" typeahead-template-url="suggestion_details" typeahead-on-select="onLayerSelect($item, $model, $label)" typeahead-editable="false" ng-class="{ 'has-error': layerAddName.$invalid }" />
198 <input type="submit" id="add-layer" class="btn" value="Add" ng-disabled="!layerAddName.length"/>
199 {% csrf_token %}
200 </form>
201 <p><a href="{% url 'layers' %}">View all layers</a> | <a href="{% url 'importlayer' %}">Import layer</a></p>
202 <ul class="unstyled configuration-list">
203 <li ng-repeat="l in layers">
204 <a href="{[l.layerdetailurl]}" target="_#" class="layer-info" data-toggle="tooltip" tooltip="{[l.branch.layersource]} | {[l.branch.name]}">{[l.name]} </a>
205 <i class="icon-trash" ng-click="layerDel(l.id)" tooltip="Delete"></i>
206 </li>
207 </ul>
173 </div> 208 </div>
174 209
175 210
211 <!-- project targets -->
212 <div id="target-container" class="well well-transparent span4">
213 <h3>
214 Targets
215 <i class="icon-question-sign get-help heading-help" title="What you build, often a recipe producing a root file system file (an image). Something like <code>core-image-minimal</code> or <code>core-image-sato</code>"></i>
216 </h3>
217 <form ng-submit="targetNamedBuild()" class="input-append">
218 <input type="text" class="input-xlarge" placeholder="Type the target(s) you want to build" autocomplete="off" data-minLength="1" ng-model="targetName" typeahead="e.name for e in getSuggestions('targets', $viewValue)|filter:$viewValue" typeahead-template-url="suggestion_details" ng-disabled="!layers.length">
219 <button type="submit" id="build-button" class="btn btn-primary" ng-disabled="!targetName.length">
220 Build </button>
221 {% csrf_token %}
222 </form>
223 <p><a href="{% url 'targets' %}">View all targets</a></p>
224 <div ng-if="frequenttargets.length">
225 <h4>
226 Most built targets
227 </h4>
228 <ul class="unstyled configuration-list">
229 <li ng-repeat="t in frequenttargets">
230 <label class="checkbox">
231 <input type="checkbox" ng-model="mostBuiltTargets[t]">{[t]}
232 </label>
233 </li>
234 </ul>
235 <button class="btn btn-large btn-primary" ng-disabled="selectedMostBuildTargets()">Build selected targets</button>
236 </div>
237 </div>
176 238
177<!-- Lifted from build.html --> 239 <!-- project configuration -->
178 {% for build in builds %} 240 <div id="machine-distro" class="well well-transparent span4">
179<div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}"> 241 <h3>
180 <div class="row-fluid"> 242 Project machine
181 <div class="lead span5"> 243 <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>
182 {%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%} 244 </h3>
183 {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} 245 <p class="lead" id="select-machine-opposite">
184 <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}"> 246 {[machine.name]}<i id="change-machine" class="icon-pencil" ng-click="toggle('#select-machine')" tooltip="Change"></i>
185 {% endif %} 247 </p>
186 <span data-toggle="tooltip" {%if build.target_set.all.count > 1%}title="Targets: {%for target in build.target_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%} {{build.machine}} ({{build.completed_on|naturaltime}})</span> 248 <div id="select-machine" style="display: none">
187 {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} 249 <div class="alert alert-info">
188 </a> 250 <strong>Machine changes have a big impact on build outcome.</strong>
189 {% endif %} 251 You cannot really compare the builds for the new machine with the previous ones.
190 </div>
191 {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
192 <div class="span2 lead">
193 {% if build.errors_no %}
194 <i class="icon-minus-sign red"></i> <a href="{%url 'builddashboard' build.pk%}#errors" class="error">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
195 {% endif %}
196 </div>
197 <div class="span2 lead">
198 {% if build.warnings_no %}
199 <i class="icon-warning-sign yellow"></i> <a href="{%url 'builddashboard' build.pk%}#warnings" class="warning">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>
200 {% endif %}
201 </div >
202 <div class="lead pull-right">
203 Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a>
204 </div>
205 {%endif%}{%if build.outcome == build.IN_PROGRESS %}
206 <div class="span4">
207 <div class="progress" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete">
208 <div style="width: {{build.completeper}}%;" class="bar"></div>
209 </div>
210 </div>
211 <div class="lead pull-right">ETA: in {{build.eta|naturaltime}}</div>
212 {%endif%}
213 </div> 252 </div>
253 <form ng-submit="edit('#select-machine')" class="input-append">
254 <input type="text" id="machine" autocomplete="off" ng-model="machineName" typeahead="m.name for m in getSuggestions('machines', $viewValue)"/>
255 <input type="submit" id="apply-change-machine" class="btn" type="button" ng-disabled="machineName == machine.name || machineName.length == 0" value="Save"></input>
256 <input type="reset" id="cancel-machine" class="btn btn-link" ng-click="toggle('#select-machine')" value="Cancel"></input>
257 {% csrf_token %}
258 </form>
259 <p><a href="{% url 'machines' %}" class="link">View all machines</a></p>
260 </div>
261 <p class="link-action">
262 <a href="{% url 'projectconf' project.id %}" class="link">Edit configuration variables</a>
263 <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>
264 </p>
214 </div> 265 </div>
215 {% endfor %} 266 </div>
216<!-- end of lift-->
217 {%endif%}
218 267
219 <h2 class="air">Project configuration</h2>
220 268
221 <div class="row-fluid"> 269 <h2>Project details</h2>
222 270
223 <div id="layer-container" class="well well-transparent span4"> 271 <!-- alerts section 3 -->
224 <h3> 272 <div ng-repeat="a in zone3alerts">
225 Add layers 273 <div class="alert alert-dismissible lead" role="alert" ng-class="a.type"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span></button>
226 <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/1.6.1/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> 274 <span ng-bind-html="a.text"></span>
227 </h3> 275 </div>
228 <form style="margin-top:20px;"> 276 </div>
229 <div class="input-append"> 277
230 <input class="input-xlarge" id="layer" autocomplete="off" placeholder="Type a layer name" data-provide="typeahead" data-source="" data-minlength="1" data-autocomplete="off" type="text"> 278
231 <button id="add-layer" class="btn" disabled="">Add</button> 279 <div id="project-details" class="well well-transparent">
232 </div> 280 <h3>Project name</h3>
233 <div id="import-alert" class="alert alert-info" style="display:none;"> 281 <p class="lead" id="change-project-name-opposite">
234 Toaster does not know about this layer. Please <a href="#">import it</a> 282 <span >{[project.name]}</span>
235 </div> 283 <i class="icon-pencil" ng-click="toggle('#change-project-name')" tooltip="Change"></i>
236 <div id="dependency-alert" class="alert alert-info" style="display:none;"> 284 </p>
237 <p><strong>meta-tizen</strong> depends on the layers below. Check the ones you want to add: </p> 285 <div id="change-project-name" style="display:none;">
238 <ul class="unstyled"> 286 <form ng-submit="edit('#change-project-name')" class="input-append">
239 {% for f in layer_dependency %} 287 <input type="text" class="input-xlarge" id="type-project-name" ng-model="projectName">
240 <li> 288 <input type="submit" class="btn" value="Save" ng-disabled="project.name == projectName"/>
241 <label class="checkbox"> 289 <input type="reset" class="btn btn-link" value="Cancel" ng-click="toggle('#change-project-name')">
242 <input checked="checked" type="checkbox"> 290 </form>
243 meta-ruby 291 </div>
244 </label>
245 </li>
246 {% endfor %}
247 </ul>
248 <button id="add-layer-dependencies" class="btn btn-info add-layer">Add layers</button>
249 </div>
250
251 <p><a href="{% url 'importlayer' %}">Import your layer</a> | <a href="{% url 'layers'%}">View all layers</a></p>
252 </form>
253
254 <h4 class="air">
255 Added layers
256 <span class="muted counter">{{project.projectlayer_set.count}}</span>
257 <i data-original-title="Your added layers will be listed in this same order in your <code>bblayers.conf</code> file" class="icon-question-sign get-help heading-help" title=""></i>
258 </h4>
259 <ul class="unstyled configuration-list">
260 {% for pl in project.projectlayer_set.all %}
261 <li>
262 <a href="#">{{pl.layercommit.layer.name}} (<span class="layer-version">{{pl.layercommit.layer.layer_index_url}}</span>)</a>
263 {% if pl.optional %}
264 <i title="" data-original-title="" class="icon-trash" id="del-layer-icon" x-data="{{pl.pk}}"></i>
265 {% endif %}
266 </li>
267 {% endfor %}
268 </ul>
269 </div>
270 292
271 <div id="target-container" class="well well-transparent span4">
272 <h3>
273 Add targets
274 <i data-original-title="A target is what you want to build, usually an image recipe that produces a root file system" class="icon-question-sign get-help heading-help" title=""></i>
275 </h3>
276 <form style="margin-top:20px;">
277 <div class="input-append">
278 <input id="target" class="input-xlarge" autocomplete="off" placeholder="Type a target name" data-provide="typeahead" data-source="" data-minlength="1" data-autocomplete="off" type="text">
279 <button id="add-target-button" class="btn" type="button">Add</button>
280 </div>
281
282 <p><a href="{% url 'targets' %}" class="link">View all targets</a></p>
283 </form>
284 <h4 class="air">
285 Added targets
286 <span id="target-count" class="muted counter">{{project.projecttarget_set.count}}</span>
287 </h4>
288 <ul class="unstyled configuration-list" id="target-list">
289 {% for target in project.projecttarget_set.all %}
290 {% if target %}
291 <li>
292 <a href="#">{{target.target}}{% if target.task%} (target.task){%endif%}</a>
293 {% if target.notprovided %}
294 <i title="" data-original-title="" id="msg1" class="icon-exclamation-sign get-help-yellow" data-title="<strong>Target may not be provided</strong>" data-content="From the layer information it currently has, Toaster thinks this target is not provided by any of your added layers. If a target is not provided by one of your added layers, the build will fail.<h5>What Toaster suggests</h5><p>The <a href='#'>meta-abc</a> and <a href='#'>meta-efg</a> layers provide core-image-notprovided. You could add one of them to your project.</p><button class='btn btn-block'>Add meta-abc</button><button class='btn btn-block'>Add meta-efg</button><button id='dismiss1' class='btn btn-block btn-info'>Stop showing this message</button>"></i>
295 {% elif target.notknown %}
296 <i title="" data-original-title="" id="msg2" class="icon-exclamation-sign get-help-yellow" data-title="<strong>Target may not be provided</strong>" data-content="From the layer information it currently has, Toaster thinks this target is not provided by any of your added layers. If a target is not provided by one of your added layers, the build will fail.<h5>What Toaster suggests</h5><p>Review your added layers to make sure one of them provides core-image-unknown. Clicking on a layer name will give you all the information Toaster has about the layer. </p> <button class='btn btn-block btn-info'>Stop showing this message</button>"></i>
297 {% endif %}
298 <i title="" data-original-title="" class="icon-trash" id="del-target-icon" x-data="{{target.pk}}"></i>
299 </li>
300 {% endif %}
301 {% endfor %}
302
303
304 </ul>
305 </div>
306 293
307 <div class="well well-transparent span4"> 294 <h3>
308 295 Release
309 <h3> 296 <i class="icon-question-sign get-help heading-help" title="The version of the build system you want to use"></i>
310 Project machine 297 </h3>
311 <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> 298 <p class="lead" id="change-project-version-opposite">
312 </h3> 299 <span id="project-version">{[project.release.name]}</span>
313 <p class="lead" id="selected-machine"> {{machine}} 300 <i id="change-version" class="icon-pencil" ng-click="toggle('#change-project-version')" tooltip="Change"></i>
314 <i id="change-machine" class="icon-pencil"></i> 301 </p>
315 </p> 302 <div class="div-inline" id="change-project-version" style="display:none;">
316 <form id="select-machine"> 303 <form ng-submit="test('#change-project-version')" class="input-append">
317 <div class="alert alert-info"> 304 <select id="select-version" ng-model="projectVersion">
318 <strong>Machine changes have a big impact on build outcome.</strong> 305 <option ng-repeat="r in releases" value="{[r.id]}" ng-selected="r.id == project.release.id">{[r.name]}</option>
319 You cannot really compare the builds for the new machine with the previous ones. 306 </select>
320 </div> 307 <input type="submit" class="btn" style="margin-left:5px;" value="Save" ng-disabled="project.release.id == projectVersion"/>
321 <div class="input-append"> 308 <input type="reset" class="btn btn-link" value="Cancel" ng-click="toggle('#change-project-version')" ng-disabled="project.release.id == projectVersion"/>
322 <input type="text" id="machine" autocomplete="off" value="qemux86" data-provide="typeahead" 309
323 data-minLength="1" 310 </form>
324 data-autocomplete="off" 311 </div>
325 data-source='[ 312 </div>
326 ]'>
327 <button id="apply-change-machine" class="btn" type="button">Save</button>
328 <a href="#" id="cancel-machine" class="btn btn-link">Cancel</a>
329 </div>
330 <p><a href="{% url 'machines' %}" class="link">View all machines</a></p>
331 </form>
332 <p class="link-action">
333 <a href="{% url 'projectconf' project.id %}" class="link">Edit configuration variables</a>
334 <i class="icon-question-sign get-help heading-help" title="You can set other project configuration options here. Each option, like everything else in the build system, is a variable - value pair"></i>
335 </p>
336 313
337 </div> 314<!-- end main -->
315</div>
338 316
339 317
340 </div> 318<!-- load application logic !-->
319<script src="{% static "js/projectapp.js" %}"></script>
320
321<!-- dump initial data for use in the angular app -->
322<script>
323angular.element(document).ready(function() {
324 scope = angular.element("#main").scope();
325 scope.urls = {};
326 scope.urls.xhr_build = "{% url 'xhr_projectbuild' project.id %}";
327 scope.urls.xhr_edit = "{% url 'xhr_projectedit' project.id %}";
328 scope.urls.xhr_datatypeahead = "{% url 'xhr_datatypeahead' %}";
329 scope.urls.layers = "{% url 'layers' %}";
330 scope.urls.targets = "{% url 'targets' %}";
331 scope.urls.importlayer = "{% url 'importlayer'%}"
332 scope.project = {{prj|safe}};
333 scope.builds = {{builds|safe}};
334 scope.layers = {{layers|safe}};
335 scope.targets = {{targets|safe}};
336 scope.frequenttargets = {{freqtargets|safe}};
337 scope.machine = {{machine|safe}};
338 scope.releases = {{releases|safe}};
339
340 scope.zone1alerts = [];
341 scope.zone2alerts = [];
342 scope.zone3alerts = [];
343
344 scope.mostBuiltTargets = {};
345
346 scope.executeCommands();
347 scope.validateData();
348
349 scope.$digest();
350
351 });
352</script>
341 353
342 <h2>Project details</h2>
343
344 <div class="well well-transparent">
345 <h3>Project name</h3>
346 <p class="lead">
347 {{project.name}}
348 <i title="" data-original-title="" class="icon-pencil"></i>
349 </p>
350 <h3>Project owner</h3>
351 <p class="lead">
352 {{puser.username}}
353 <i title="" data-original-title="" class="icon-pencil"></i>
354 </p>
355 <h3>Owner's email</h3>
356 <p class="lead">
357 {{puser.email}}
358 <i title="" data-original-title="" class="icon-pencil"></i>
359 </p>
360 <h3>Yocto Project version</h3>
361 <p class="lead">
362 {{project.release.name}} - {{project.release.description}}
363 <i title="" data-original-title="" class="icon-pencil"></i>
364 </p>
365 </div>
366{% endblock %} 354{% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py
index a9c05922c2..e642e32036 100644
--- a/bitbake/lib/toaster/toastergui/urls.py
+++ b/bitbake/lib/toaster/toastergui/urls.py
@@ -81,6 +81,8 @@ urlpatterns = patterns('toastergui.views',
81 url(r'^xhr_projectbuild/(?P<pid>\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'), 81 url(r'^xhr_projectbuild/(?P<pid>\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'),
82 url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'), 82 url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'),
83 83
84 url(r'^xhr_datatypeahead/$', 'xhr_datatypeahead', name='xhr_datatypeahead'),
85
84 86
85 # default redirection 87 # default redirection
86 url(r'^$', RedirectView.as_view( url= 'builds/')), 88 url(r'^$', RedirectView.as_view( url= 'builds/')),
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 3fde3c9a62..5fe4a9d869 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -387,6 +387,17 @@ def builds(request):
387 ] 387 ]
388 } 388 }
389 389
390 if toastermain.settings.MANAGED:
391 context['tablecols'].append(
392 {'name': 'Project', 'clclass': 'project',
393 'filter': {'class': 'project',
394 'label': 'Project:',
395 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()),
396
397 }
398 })
399
400
390 response = render(request, template, context) 401 response = render(request, template, context)
391 _save_parameters_cookies(response, pagesize, orderby, request) 402 _save_parameters_cookies(response, pagesize, orderby, request)
392 return response 403 return response
@@ -1799,7 +1810,7 @@ if toastermain.settings.MANAGED:
1799 from django.contrib.auth.decorators import login_required 1810 from django.contrib.auth.decorators import login_required
1800 1811
1801 from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable 1812 from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable
1802 from orm.models import Branch, LayerSource, ToasterSetting, Release, Machine 1813 from orm.models import Branch, LayerSource, ToasterSetting, Release, Machine, LayerVersionDependency
1803 from bldcontrol.models import BuildRequest 1814 from bldcontrol.models import BuildRequest
1804 1815
1805 import traceback 1816 import traceback
@@ -1831,7 +1842,7 @@ if toastermain.settings.MANAGED:
1831 # render new project page 1842 # render new project page
1832 return render(request, template, context) 1843 return render(request, template, context)
1833 elif request.method == "POST": 1844 elif request.method == "POST":
1834 mandatory_fields = ['projectname', 'email', 'username', 'projectversion'] 1845 mandatory_fields = ['projectname', 'projectversion']
1835 try: 1846 try:
1836 # make sure we have values for all mandatory_fields 1847 # make sure we have values for all mandatory_fields
1837 if reduce( lambda x, y: x or y, map(lambda x: len(request.POST.get(x, '')) == 0, mandatory_fields)): 1848 if reduce( lambda x, y: x or y, map(lambda x: len(request.POST.get(x, '')) == 0, mandatory_fields)):
@@ -1840,9 +1851,9 @@ if toastermain.settings.MANAGED:
1840 ", ".join([x for x in mandatory_fields if len(request.POST.get(x, '')) == 0 ])) 1851 ", ".join([x for x in mandatory_fields if len(request.POST.get(x, '')) == 0 ]))
1841 1852
1842 if not request.user.is_authenticated(): 1853 if not request.user.is_authenticated():
1843 user = authenticate(username = request.POST['username'], password = 'nopass') 1854 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1844 if user is None: 1855 if user is None:
1845 user = User.objects.create_user(username = request.POST['username'], email = request.POST['email'], password = "nopass") 1856 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1846 1857
1847 user = authenticate(username = user.username, password = 'nopass') 1858 user = authenticate(username = user.username, password = 'nopass')
1848 login(request, user) 1859 login(request, user)
@@ -1852,7 +1863,7 @@ if toastermain.settings.MANAGED:
1852 release = Release.objects.get(pk = request.POST['projectversion'])) 1863 release = Release.objects.get(pk = request.POST['projectversion']))
1853 prj.user_id = request.user.pk 1864 prj.user_id = request.user.pk
1854 prj.save() 1865 prj.save()
1855 return redirect(reverse(project, args = (prj.pk,))) 1866 return redirect(reverse(project, args = (prj.pk,)) + "#/newproject")
1856 1867
1857 except (IntegrityError, BadParameterException) as e: 1868 except (IntegrityError, BadParameterException) as e:
1858 # fill in page with previously submitted values 1869 # fill in page with previously submitted values
@@ -1865,6 +1876,40 @@ if toastermain.settings.MANAGED:
1865 1876
1866 raise Exception("Invalid HTTP method for this page") 1877 raise Exception("Invalid HTTP method for this page")
1867 1878
1879
1880 def _project_recent_build_list(prj):
1881 # build requests not yet started
1882 return (map(lambda x: {
1883 "id": x.pk,
1884 "targets" : map(lambda y: {"target": y.target }, x.brtarget_set.all()),
1885 "status": x.get_state_display(),
1886 }, prj.buildrequest_set.filter(state__lt = BuildRequest.REQ_INPROGRESS).order_by("-pk")) +
1887 # build requests started, but with no build yet
1888 map(lambda x: {
1889 "id": x.pk,
1890 "targets" : map(lambda y: {"target": y.target }, x.brtarget_set.all()),
1891 "status": x.get_state_display(),
1892 }, prj.buildrequest_set.filter(state = BuildRequest.REQ_INPROGRESS, build = None).order_by("-pk")) +
1893 # build requests that failed
1894 map(lambda x: {
1895 "id": x.pk,
1896 "targets" : map(lambda y: {"target": y.target }, x.brtarget_set.all()),
1897 "status": x.get_state_display(),
1898 "errors": map(lambda y: {"type": y.errtype, "msg": y.errmsg, "tb": y.traceback}, x.brerror_set.all()),
1899 }, prj.buildrequest_set.filter(state = BuildRequest.REQ_FAILED).order_by("-pk")) +
1900 # and already made builds
1901 map(lambda x: {
1902 "id": x.pk,
1903 "targets": map(lambda y: {"target": y.target }, x.target_set.all()),
1904 "status": x.get_outcome_display(),
1905 "completed_on" : x.completed_on.strftime('%s')+"000",
1906 "build_time" : (x.completed_on - x.started_on).total_seconds(),
1907 "build_page_url" : reverse('builddashboard', args=(x.pk,)),
1908 "completeper": x.completeper(),
1909 "eta": x.eta().ctime(),
1910 }, prj.build_set.all()))
1911
1912
1868 # Shows the edit project page 1913 # Shows the edit project page
1869 def project(request, pid): 1914 def project(request, pid):
1870 template = "project.html" 1915 template = "project.html"
@@ -1881,27 +1926,40 @@ if toastermain.settings.MANAGED:
1881 # we use implicit knowledge of the current user's project to filter layer information, e.g. 1926 # we use implicit knowledge of the current user's project to filter layer information, e.g.
1882 request.session['project_id'] = prj.id 1927 request.session['project_id'] = prj.id
1883 1928
1929 from collections import Counter
1930 freqtargets = []
1931 try:
1932 freqtargets += map(lambda x: x.target, reduce(lambda x, y: x + y, map(lambda x: list(x.target_set.all()), Build.objects.filter(project = prj, outcome__lt = Build.IN_PROGRESS))))
1933 freqtargets += map(lambda x: x.target, reduce(lambda x, y: x + y, map(lambda x: list(x.brtarget_set.all()), BuildRequest.objects.filter(project = prj, state__lte = BuildRequest.REQ_QUEUED))))
1934 except TypeError:
1935 pass
1936 freqtargets = Counter(freqtargets)
1937 freqtargets = sorted(freqtargets, key = lambda x: freqtargets[x])
1938
1884 context = { 1939 context = {
1885 "project" : prj, 1940 "project" : prj,
1941 "completedbuilds": Build.objects.filter(project = prj).exclude(outcome = Build.IN_PROGRESS),
1942 "prj" : json.dumps({"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name}}),
1886 #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED), 1943 #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED),
1887 "buildrequests" : map(lambda x: (x, {"machine" : x.brvariable_set.filter(name="MACHINE")[0]}), prj.buildrequest_set.filter(state__lt = BuildRequest.REQ_INPROGRESS).order_by("-pk")), 1944 "builds" : json.dumps(_project_recent_build_list(prj)),
1888 "builds" : prj.build_set.all(), 1945 "layers" : json.dumps(map(lambda x: {"id": x.layercommit.pk, "name" : x.layercommit.layer.name, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all())),
1889 "puser": puser, 1946 "targets" : json.dumps(map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all())),
1947 "freqtargets": json.dumps(freqtargets),
1948 "releases": json.dumps(map(lambda x: {"id": x.pk, "name": x.name}, Release.objects.all())),
1890 } 1949 }
1891 try: 1950 try:
1892 context["machine"] = prj.projectvariable_set.get(name="MACHINE").value 1951 context["machine"] = json.dumps({"name": prj.projectvariable_set.get(name="MACHINE").value})
1893 except ProjectVariable.DoesNotExist: 1952 except ProjectVariable.DoesNotExist:
1894 context["machine"] = "-- not set yet" 1953 context["machine"] = json.dumps(None)
1895
1896 try: 1954 try:
1897 context["distro"] = prj.projectvariable_set.get(name="DISTRO").value 1955 context["distro"] = prj.projectvariable_set.get(name="DISTRO").value
1898 except ProjectVariable.DoesNotExist: 1956 except ProjectVariable.DoesNotExist:
1899 context["distro"] = "-- not set yet" 1957 context["distro"] = "-- not set yet"
1900 1958
1901 1959 response = render(request, template, context)
1902 return render(request, template, context) 1960 response['Cache-Control'] = "no-cache, must-revalidate, no-store"
1903 1961 response['Pragma'] = "no-cache"
1904 import json 1962 return response
1905 1963
1906 def xhr_projectbuild(request, pid): 1964 def xhr_projectbuild(request, pid):
1907 try: 1965 try:
@@ -1909,57 +1967,155 @@ if toastermain.settings.MANAGED:
1909 raise BadParameterException("invalid method") 1967 raise BadParameterException("invalid method")
1910 prj = Project.objects.get(id = pid) 1968 prj = Project.objects.get(id = pid)
1911 1969
1912 if prj.projecttarget_set.count() == 0:
1913 raise BadParameterException("no targets selected")
1914 1970
1915 br = prj.schedule_build() 1971 if 'buildCancel' in request.POST:
1916 return HttpResponse(json.dumps({"error":"ok", 1972 for i in request.POST['buildCancel'].strip().split(" "):
1917 "brtarget" : map(lambda x: x.target, br.brtarget_set.all()), 1973 br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_QUEUED)
1918 "machine" : br.brvariable_set.get(name="MACHINE").value, 1974 print "selected for delete", br.pk
1975 br.delete()
1976 print "selected for delete", br.pk
1919 1977
1920 }), content_type = "application/json") 1978 if 'targets' in request.POST:
1921 except Exception as e: 1979 ProjectTarget.objects.filter(project = prj).delete()
1922 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") 1980 for t in request.POST['targets'].strip().split(" "):
1923
1924 def xhr_projectedit(request, pid):
1925 try:
1926 prj = Project.objects.get(id = pid)
1927 # add targets
1928 if 'targetAdd' in request.POST:
1929 for t in request.POST['targetAdd'].strip().split(" "):
1930 if ":" in t: 1981 if ":" in t:
1931 target, task = t.split(":") 1982 target, task = t.split(":")
1932 else: 1983 else:
1933 target = t 1984 target = t
1934 task = "" 1985 task = ""
1986 ProjectTarget.objects.create(project = prj, target = target, task = task)
1935 1987
1936 pt, created = ProjectTarget.objects.get_or_create(project = prj, target = target, task = task) 1988 br = prj.schedule_build()
1937 # remove targets
1938 if 'targetDel' in request.POST:
1939 for t in request.POST['targetDel'].strip().split(" "):
1940 pt = ProjectTarget.objects.get(pk = int(t)).delete()
1941 1989
1990 return HttpResponse(json.dumps({"error":"ok",
1991 "builds" : _project_recent_build_list(prj),
1992 }), content_type = "application/json")
1993 except Exception as e:
1994 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1995
1996 def xhr_projectedit(request, pid):
1997 try:
1998 prj = Project.objects.get(id = pid)
1942 # add layers 1999 # add layers
2000 if 'layerAdd' in request.POST:
2001 for lc in Layer_Version.objects.filter(pk__in=request.POST['layerAdd'].split(",")):
2002 ProjectLayer.objects.get_or_create(project = prj, layercommit = lc)
1943 2003
1944 # remove layers 2004 # remove layers
2005 if 'layerDel' in request.POST:
2006 for t in request.POST['layerDel'].strip().split(" "):
2007 pt = ProjectLayer.objects.get(project = prj, layercommit_id = int(t)).delete()
2008
2009 if 'projectName' in request.POST:
2010 prj.name = request.POST['projectName']
2011 prj.save();
2012
2013
2014 if 'projectVersion' in request.POST:
2015 prj.release = Release.objects.get(pk = request.POST['projectVersion'])
2016 prj.save()
2017 # we need to change the layers
2018 for i in prj.projectlayer_set.all():
2019 # find and add a similarly-named layer from the same layer source on the new branch
2020 lv = Layer_Version.objects.filter(layer_source = i.layercommit.layer_source, layer__name = i.layercommit.layer.name, up_branch__in = Branch.objects.filter(name = prj.release.branch))
2021 if lv.count() == 1:
2022 ProjectLayer.objects.get_or_create(project = prj, layercommit = lv[0])
2023 # get rid of the old entry
2024 i.delete()
1945 2025
1946 # return all project settings 2026 # return all project settings
1947 return HttpResponse(json.dumps( { 2027 return HttpResponse(json.dumps( {
1948 "error": "ok", 2028 "error": "ok",
1949 "layers": map(lambda x: (x.layercommit.layer.name, x.layercommit.layer.layer_index_url), prj.projectlayer_set.all()), 2029 "layers" : map(lambda x: {"id": x.layercommit.pk, "name" : x.layercommit.layer.name, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all()),
1950 "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()), 2030 "builds" : _project_recent_build_list(prj),
1951 "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()), 2031 "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()),
2032 "prj": {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name}},
1952 }), content_type = "application/json") 2033 }), content_type = "application/json")
1953 2034
1954 except Exception as e: 2035 except Exception as e:
1955 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") 2036 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1956 2037
2038
2039 from django.views.decorators.csrf import csrf_exempt
2040 @csrf_exempt
2041 def xhr_datatypeahead(request):
2042 try:
2043 if request.GET['type'] == "layers":
2044 queryset_all = Layer_Version.objects.all()
2045 if 'project_id' in request.session:
2046 prj = Project.objects.get(pk = request.session['project_id'])
2047 queryset_all = queryset_all.filter(up_branch__in = Branch.objects.filter(name = prj.release.name)).exclude(pk__in = map(lambda x: x.layercommit_id, prj.projectlayer_set.all()))
2048 queryset_all = queryset_all.filter(layer__name__icontains=request.GET.get('value',''))
2049 return HttpResponse(json.dumps( { "error":"ok",
2050 "list" : map( lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")")},
2051 queryset_all[:8])
2052 }), content_type = "application/json")
2053
2054 if request.GET['type'] == "layerdeps":
2055 queryset_all = LayerVersionDependency.objects.filter(layer_version_id = request.GET['value'])
2056
2057 if 'project_id' in request.session:
2058 prj = Project.objects.get(pk = request.session['project_id'])
2059 queryset_all = queryset_all.exclude(depends_on__in = map(lambda x: x.layercommit, prj.projectlayer_set.all()))
2060
2061 queryset_all.order_by("-up_id");
2062
2063 return HttpResponse(json.dumps( { "error":"ok",
2064 "list" : map(
2065 lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")")},
2066 map(lambda x: x.depends_on, queryset_all))
2067 }), content_type = "application/json")
2068
2069 if request.GET['type'] == "versionlayers":
2070 if not 'project_id' in request.session:
2071 raise Exception("This call cannot makes no sense outside a project context")
2072
2073 retval = []
2074 prj = Project.objects.get(pk = request.session['project_id'])
2075 for i in prj.projectlayer_set.all():
2076 lv = Layer_Version.objects.filter(layer_source = i.layercommit.layer_source, layer__name = i.layercommit.layer.name, up_branch__in = Branch.objects.filter(name = Release.objects.get(pk=request.GET['value']).branch))
2077 if lv.count() != 1:
2078 retval.append(i)
2079
2080 return HttpResponse(json.dumps( {"error":"ok",
2081 "list": map(
2082 lambda x: {"id": x.layercommit.pk, "name": x.layercommit.layer.name, "detail": "(" + x.layercommit.layer.layer_source.name + (")" if x.layercommit.up_branch == None else " | "+x.layercommit.up_branch.name+")")},
2083 retval) }), content_type = "application/json")
2084
2085
2086 if request.GET['type'] == "targets":
2087 queryset_all = Recipe.objects.all()
2088 if 'project_id' in request.session:
2089 queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id'])))
2090 return HttpResponse(json.dumps({ "error":"ok",
2091 "list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")},
2092 queryset_all.filter(name__istartswith=request.GET.get('value',''))[:8]),
2093
2094 }), content_type = "application/json")
2095
2096 if request.GET['type'] == "machines":
2097 queryset_all = Machine.objects.all()
2098 if 'project_id' in request.session:
2099 queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id'])))
2100 return HttpResponse(json.dumps({ "error":"ok",
2101 "list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")},
2102 queryset_all.filter(name__istartswith=request.GET.get('value',''))[:8]),
2103
2104 }), content_type = "application/json")
2105
2106 raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied"))
2107 except Exception as e:
2108 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
2109
2110
2111
1957 def importlayer(request): 2112 def importlayer(request):
1958 template = "importlayer.html" 2113 template = "importlayer.html"
1959 context = { 2114 context = {
1960 } 2115 }
1961 return render(request, template, context) 2116 return render(request, template, context)
1962 2117
2118
1963 def layers(request): 2119 def layers(request):
1964 template = "layers.html" 2120 template = "layers.html"
1965 # define here what parameters the view needs in the GET portion in order to 2121 # define here what parameters the view needs in the GET portion in order to
@@ -2220,7 +2376,7 @@ if toastermain.settings.MANAGED:
2220 # boilerplate code that takes a request for an object type and returns a queryset 2376 # boilerplate code that takes a request for an object type and returns a queryset
2221 # for that object type. copypasta for all needed table searches 2377 # for that object type. copypasta for all needed table searches
2222 (filter_string, search_term, ordering_string) = _search_tuple(request, Build) 2378 (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
2223 queryset_all = Build.objects.all.exclude(outcome = Build.IN_PROGRESS) 2379 queryset_all = Build.objects.all().exclude(outcome = Build.IN_PROGRESS)
2224 queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on') 2380 queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
2225 queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on') 2381 queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
2226 2382
@@ -2388,6 +2544,9 @@ else:
2388 def xhr_projectedit(request, pid): 2544 def xhr_projectedit(request, pid):
2389 raise Exception("page not available in interactive mode") 2545 raise Exception("page not available in interactive mode")
2390 2546
2547 def xhr_datatypeahead(request):
2548 raise Exception("page not available in interactive mode")
2549
2391 def importlayer(request): 2550 def importlayer(request):
2392 raise Exception("page not available in interactive mode") 2551 raise Exception("page not available in interactive mode")
2393 2552