summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster
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/lib/toaster
parent1a463ab98ec1220b22a721a1f88ff5af61c33dc3 (diff)
downloadast2050-yocto-poky-960580cb70ab1c775b49f8a40d41632c080b3cbb.zip
ast2050-yocto-poky-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/lib/toaster')
-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 1b3bb22..1521717 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -76,21 +76,25 @@ class Project(models.Model):
def schedule_build(self):
from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake
br = BuildRequest.objects.create(project = self)
+ try:
- BRBitbake.objects.create(req = br,
- giturl = self.bitbake_version.giturl,
- commit = self.bitbake_version.branch,
- dirpath = self.bitbake_version.dirpath)
-
- for l in self.projectlayer_set.all():
- BRLayer.objects.create(req = br, name = l.layercommit.layer.name, giturl = l.layercommit.layer.vcs_url, commit = l.layercommit.commit, dirpath = l.layercommit.dirpath)
- for t in self.projecttarget_set.all():
- BRTarget.objects.create(req = br, target = t.target, task = t.task)
- for v in self.projectvariable_set.all():
- BRVariable.objects.create(req = br, name = v.name, value = v.value)
-
- br.state = BuildRequest.REQ_QUEUED
- br.save()
+ BRBitbake.objects.create(req = br,
+ giturl = self.bitbake_version.giturl,
+ commit = self.bitbake_version.branch,
+ dirpath = self.bitbake_version.dirpath)
+
+ for l in self.projectlayer_set.all():
+ BRLayer.objects.create(req = br, name = l.layercommit.layer.name, giturl = l.layercommit.layer.vcs_url, commit = l.layercommit.commit, dirpath = l.layercommit.dirpath)
+ for t in self.projecttarget_set.all():
+ BRTarget.objects.create(req = br, target = t.target, task = t.task)
+ for v in self.projectvariable_set.all():
+ BRVariable.objects.create(req = br, name = v.name, value = v.value)
+
+ br.state = BuildRequest.REQ_QUEUED
+ br.save()
+ except Exception as e:
+ br.delete()
+ raise e
return br
class Build(models.Model):
@@ -131,7 +135,7 @@ class Build(models.Model):
def eta(self):
from django.utils import timezone
- eta = 0
+ eta = timezone.now()
completeper = self.completeper()
if self.completeper() > 0:
eta = timezone.now() + ((timezone.now() - self.started_on)*(100-completeper)/completeper)
@@ -534,12 +538,16 @@ class LayerIndexLayerSource(LayerSource):
def _get_json_response(apiurl = self.apiurl):
import httplib, urlparse, json
parsedurl = urlparse.urlparse(apiurl)
- (host, port) = parsedurl.netloc.split(":")
+ try:
+ (host, port) = parsedurl.netloc.split(":")
+ except ValueError:
+ host = parsedurl.netloc
+ port = None
+
if port is None:
port = 80
else:
port = int(port)
- #print "-- connect to: http://%s:%s%s?%s" % (host, port, parsedurl.path, parsedurl.query)
conn = httplib.HTTPConnection(host, port)
conn.request("GET", parsedurl.path + "?" + parsedurl.query)
r = conn.getresponse()
@@ -550,8 +558,9 @@ class LayerIndexLayerSource(LayerSource):
# verify we can get the basic api
try:
apilinks = _get_json_response()
- except:
- print "EE: could not connect to %s, skipping update" % self.apiurl
+ except Exception as e:
+ import traceback
+ print "EE: could not connect to %s, skipping update: %s\n%s" % (self.apiurl, e, traceback.format_exc(e))
return
# update branches; only those that we already have names listed in the database
@@ -582,7 +591,7 @@ class LayerIndexLayerSource(LayerSource):
# update layerbranches/layer_versions
layerbranches_info = _get_json_response(apilinks['layerBranches']
- + "?filter=branch:%s" % "OR".join(map(lambda x: str(x.up_id), Branch.objects.filter(layer_source = self)))
+ + "?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] ))
)
for lbi in layerbranches_info:
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 8e0df59..8780c4f 100644
--- a/bitbake/lib/toaster/toastergui/static/css/default.css
+++ b/bitbake/lib/toaster/toastergui/static/css/default.css
@@ -8,10 +8,14 @@
/* Styles for the help information */
.get-help { color: #CCCCCC; }
-.get-help:hover { color: #999999; cursor: pointer; }
+.get-help:hover, .icon-plus-sign:hover { color: #999999; cursor: pointer; }
.get-help-blue { color: #3A87AD; }
.get-help-blue:hover { color: #005580; cursor: pointer; }
-.manual { margin-top: 11px; }
+.get-help-yellow { color: #C09853; }
+.get-help-yellow:hover { color: #B38942; cursor: pointer; }
+.get-help-red { color: #B94A48; font-size: 16px; padding-left: 2px; }
+.get-help-red:hover { color: #943A38; cursor: pointer; }
+.manual { margin: 11px 15px;}
.heading-help { font-size: 14px; }
/* Styles for the external link */
@@ -44,6 +48,7 @@ dd p { line-height: 20px; }
/* Some extra space before headings when needed */
.details { margin-top: 30px; }
+.air { margin-top: 30px; }
/* Required classes for the highlight behaviour in tables */
.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; }
.content-directory a:hover { color: #005580; text-decoration: underline; }
.symlink { color: #CCCCCC; }
+/* Styles for the navbar actions */
+.btn-group + .btn-group { margin-right: 10px; }
+.navbar-inner > .btn-group { margin-top: 6px; }
+
/* Other styles */
.dropdown-menu { padding: 10px; }
select { width: auto; }
@@ -104,6 +113,7 @@ select { width: auto; }
.progress { margin-bottom: 0px; }
.lead .badge { font-size: 18px; font-weight: normal; border-radius: 15px; padding: 9px; }
.well > .lead, .alert .lead { margin-bottom: 0px; }
+.well-transparent { background-color: transparent; }
.no-results { margin: 10px 0; }
.task-name { margin-left: 7px; }
.icon-hand-right {color: #CCCCCC; }
@@ -119,9 +129,14 @@ select { width: auto; }
/* Configuration styles */
.icon-trash { color: #B94A48; font-size: 16px; padding-left: 2px; }
.icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; }
-.icon-pencil, .icon-download-alt { font-size: 16px; color: #0088CC; padding-left: 2px; }
-.icon-pencil:hover, .icon-download-alt:hover { color: #005580; text-decoration: none; cursor: pointer; }
-.configuration-list li { line-height: 35px; font-size: 21px; font-weight: 200; }
+.icon-pencil, .icon-download-alt, .icon-refresh, .icon-star-empty, .icon-star, .icon-tasks { font-size: 16px; color: #0088CC; padding-left: 2px; }
+.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; }
+.icon-share { padding-left: 2px; }
+.alert-success .icon-refresh, .alert-success .icon-tasks { color: #468847; }
+.alert-success .icon-refresh:hover, .alert-success .icon-tasks:hover { color: #347132; }
+.alert-error .icon-refresh, .alert-error .icon-tasks { color: #b94a48; }
+.alert-error .icon-refresh:hover, .alert-error .icon-tasks:hover { color: #943A38; }
+.configuration-list li, .configuration-list label { line-height: 35px; font-size: 21px; font-weight: 200; margin-bottom: 0px;}
.configuration-list { font-size: 16px; margin-bottom: 1.5em; }
.configuration-list i { font-size: 16px; }
/*.configuration-layers { height: 135px; overflow: scroll; }*/
@@ -132,15 +147,46 @@ select { width: auto; }
.configuration-alert p { margin-bottom: 0px; }
fieldset { padding-left: 19px; }
.project-form { margin-top: 10px; }
-.add-layers .btn-block + .btn-block { margin-top: 0px; }
+.add-layers .btn-block + .btn-block, .build .btn-block + .btn-block { margin-top: 0px; }
input.huge { font-size: 17.5px; padding: 11px 19px; }
-.build-form { margin-bottom: 0px; padding-left: 20px; }
+.build-form { margin-bottom: 0px; }
+.build-form .input-append { margin-bottom: 0px; }
+.build-form .btn-large { padding: 11px 35px; }
+.build-form p { font-size:17.5px ;margin:12px 0 0 10px;}
+.btn-primary .icon-question-sign, .btn-danger .icon-question-sign { color: #fff; }
+.btn-primary .icon-question-sign:hover, .btn-danger .icon-question-sign:hover { color: #999; }
a code { color: #0088CC; }
a code:hover { color: #005580; }
.localconf { font-size: 17.5px; margin-top: 40px; }
.localconf code { font-size: 17.5px; }
#add-layer-dependencies { margin-top: 5px; }
+.link-action { font-size: 17.5px; margin-top: 40px; }
+.link-action code { font-size: 17.5px; }
.artifact { width: 9em; }
.control-group { margin-bottom: 0px; }
#project-details form { margin: 0px; }
dd form { margin: 10px 0 0 0; }
+dd form { margin-bottom: 0px; }
+dl textarea { resize: vertical; }
+.navbar-fixed-top { z-index: 1; }
+.popover { z-index: 2; }
+.btn-danger .icon-trash { color: #fff; }
+.bbappends { list-style-type: none; margin-left: 0; }
+.bbappends li { line-height: 25px; }
+.configuration-list input[type="checkbox"] { margin-top:13px;margin-right:10px; }
+.alert input[type="checkbox"] { margin-top: 0px; margin-right: 3px; }
+.alert ol { padding: 10px 0px 0px 20px; }
+.alert ol > li { line-height: 35px; }
+.dl-vertical form { margin-top: 10px; }
+.scrolling { border: 1px solid #dddddd; height: 154px; overflow: auto; padding: 8px; width: 27.5%; margin-bottom: 10px; }
+.lead .help-block { font-size: 14px; line-height: 20px; font-weight: normal; }
+.button-place .btn { margin: 0 0 20px 0; }
+.tooltip-inner { max-width: 250px; }
+dd > span { line-height: 20px; }
+.new-build { padding: 20px; }
+.new-build li { line-height: 30px; }
+.new-build h6 { margin: 10px 0 0 0; color: #5a5a5a; }
+.new-build h3 { margin: 0; color: #5a5a5a; }
+.new-build form { margin: 5px 0 0; }
+.new-build .input-append { margin-bottom: 0; }
+#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 0000000..e674d8f
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/static/js/projectapp.js
@@ -0,0 +1,531 @@
+// vim: set tabstop=4 expandtab ai:
+// BitBake Toaster Implementation
+//
+// Copyright (C) 2013 Intel Corporation
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+angular_formpost = function($httpProvider) {
+ // Use x-www-form-urlencoded Content-Type
+ // By Ezekiel Victor, http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/, no license, with attribution
+ $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
+
+ /**
+ * The workhorse; converts an object to x-www-form-urlencoded serialization.
+ * @param {Object} obj
+ * @return {String}
+ */
+ var param = function(obj) {
+ var query = '', name, value, fullSubName, subName, subValue, innerObj, i;
+
+ for(name in obj) {
+ value = obj[name];
+
+ if(value instanceof Array) {
+ for(i=0; i<value.length; ++i) {
+ subValue = value[i];
+ fullSubName = name + '[' + i + ']';
+ innerObj = {};
+ innerObj[fullSubName] = subValue;
+ query += param(innerObj) + '&';
+ }
+ }
+ else if(value instanceof Object) {
+ for(subName in value) {
+ subValue = value[subName];
+ fullSubName = name + '[' + subName + ']';
+ innerObj = {};
+ innerObj[fullSubName] = subValue;
+ query += param(innerObj) + '&';
+ }
+ }
+ else if(value !== undefined && value !== null)
+ query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
+ }
+
+ return query.length ? query.substr(0, query.length - 1) : query;
+ };
+
+ // Override $http service's default transformRequest
+ $httpProvider.defaults.transformRequest = [function(data) {
+ return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
+ }];
+}
+
+
+/**
+ * Helper to execute callback on elements from array differences; useful for incremental UI updating.
+ * @param {Array} oldArray
+ * @param {Array} newArray
+ * @param {function} compareElements
+ * @param {function} onAdded
+ * @param {function} onDeleted
+ *
+ * no return
+ */
+function _diffArrays(oldArray, newArray, compareElements, onAdded, onDeleted ) {
+ if (onDeleted !== undefined) {
+ oldArray.filter(function (e) { var found = 0; newArray.map(function (f) { if (compareElements(e, f)) {found = 1};}); return !found;}).map(onDeleted);
+ }
+ if (onAdded !== undefined) {
+ newArray.filter(function (e) { var found = 0; oldArray.map(function (f) { if (compareElements(e, f)) {found = 1};}); return !found;}).map(onAdded);
+ }
+}
+
+
+var projectApp = angular.module('project', ['ui.bootstrap', 'ngCookies'], angular_formpost);
+
+// modify the template tag markers to prevent conflicts with Django
+projectApp.config(function($interpolateProvider) {
+ $interpolateProvider.startSymbol("{[");
+ $interpolateProvider.endSymbol("]}");
+});
+
+// main controller for the project page
+projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $q, $sce) {
+
+ $scope.getSuggestions = function(type, currentValue) {
+ var deffered = $q.defer();
+
+ $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: type, value: currentValue}})
+ .success(function (_data) {
+ if (_data.error != "ok") {
+ alert(_data.error);
+ deffered.reject(_data.error);
+ }
+ deffered.resolve(_data.list);
+ });
+
+ return deffered.promise;
+ }
+
+ var inXHRcall = false;
+
+ // default handling of XHR calls that handles errors and updates commonly-used pages
+ $scope._makeXHRCall = function(callparams) {
+ if (inXHRcall) {
+ if (callparams.data === undefined) {
+ // we simply skip the data refresh calls
+ console.log("race on XHR, aborted");
+ return;
+ } else {
+ // we return a promise that we'll solve by reissuing the command later
+ var delayed = $q.defer();
+ console.log("race on XHR, delayed");
+ $interval(function () {$scope._makeXHRCall(callparams).then(function (d) { delayed.resolve(d); });}, 100, 1);
+
+ return delayed.promise;
+ }
+
+ }
+ var deffered = $q.defer();
+
+ if (undefined === callparams.headers) { callparams.headers = {} };
+ callparams.headers['X-CSRFToken'] = $cookies.csrftoken;
+
+ $http(callparams).success(function(_data, _status, _headers, _config) {
+ if (_data.error != "ok") {
+ alert("Failed XHR request (" + _status + "): " + _data.error);
+ console.error("Failed XHR request: ", _data, _status, _headers, _config);
+ deffered.reject(_data.error);
+ }
+ else {
+ // TODO: update screen data if we have fields here
+
+ if (_data.builds !== undefined) {
+
+ var oldbuilds = $scope.builds;
+ $scope.builds = _data.builds;
+
+ // identify canceled builds here, so we can display them.
+ _diffArrays(oldbuilds, $scope.builds,
+ function (e,f) { return e.status == f.status && e.id == f.id }, // compare
+ undefined, // added
+ function (e) { // deleted
+ if (e.status == "deleted") return;
+ e.status = "deleted";
+ for (var i = 0; i < $scope.builds.length; i++) {
+ if ($scope.builds[i].status == "queued" && $scope.builds[i].id > e.id)
+ continue;
+ $scope.builds.splice(i, 0, e);
+ break;
+ }
+ });
+
+ }
+ if (_data.layers !== undefined) {
+ var oldlayers = $scope.layers;
+ $scope.layers = _data.layers;
+
+ // show added/deleted layer notifications
+ var addedLayers = [];
+ var deletedLayers = [];
+ _diffArrays( oldlayers, $scope.layers, function (e, f) { return e.id == f.id },
+ function (e) { console.log("new layer", e);addedLayers.push(e); },
+ function (e) { console.log("del layer", e);deletedLayers.push(e); });
+
+ if (addedLayers.length > 0) {
+ $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");
+ }
+ if (deletedLayers.length > 0) {
+ $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");
+ }
+
+ }
+ if (_data.targets !== undefined) {
+ $scope.targets = _data.targets;
+ }
+ if (_data.machine !== undefined) {
+ $scope.machine = _data.machine;
+ }
+ if (_data.user !== undefined) {
+ $scope.user = _data.user;
+ }
+
+ if (_data.prj !== undefined) {
+ $scope.project = _data.prj;
+
+ // update breadcrumb, outside the controller
+ $('#project_name').text($scope.project.name);
+ }
+
+ $scope.validateData();
+ inXHRcall = false;
+ deffered.resolve(_data);
+ }
+ }).error(function(_data, _status, _headers, _config) {
+ alert("Failed HTTP XHR request (" + _status + ")" + _data);
+ console.error("Failed HTTP XHR request: ", _data, _status, _headers, _config);
+ inXHRcall = false;
+ deffered.reject(_data.error);
+ });
+
+ return deffered.promise;
+ }
+
+ $scope.layeralert = undefined;
+ // shows user alerts on invalid project data
+ $scope.validateData = function () {
+ if ($scope.layers.length == 0) {
+ $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>");
+ } else {
+ if ($scope.layeralert != undefined) {
+ $scope.layeralert.close();
+ $scope.layeralert = undefined;
+ }
+ }
+ }
+
+ $scope.targetExistingBuild = function(targets) {
+ var oldTargetName = $scope.targetName;
+ $scope.targetName = targets.map(function(v,i,a){return v.target}).join(' ');
+ $scope.targetNamedBuild();
+ $scope.targetName = oldTargetName;
+ }
+
+ $scope.targetNamedBuild = function(target) {
+ if ($scope.targetName === undefined){
+ alert("No target defined, please type in a target name");
+ return;
+ }
+
+ $scope.sanitizeTargetName();
+
+ $scope._makeXHRCall({
+ method: "POST", url: $scope.urls.xhr_build,
+ data : {
+ targets: $scope.targetName
+ }
+ }).then(function (data) {
+ console.log("received ", data);
+ $scope.targetName = undefined;
+ });
+ }
+
+ $scope.sanitizeTargetName = function() {
+ if (undefined === $scope.targetName) return;
+ $scope.targetName = $scope.targetName.replace(/\[.*\]/, '').trim();
+ }
+
+ $scope.buildCancel = function(id) {
+ $scope._makeXHRCall({
+ method: "POST", url: $scope.urls.xhr_build,
+ data: {
+ buildCancel: id,
+ }
+ });
+ }
+
+ $scope.onLayerSelect = function (item, model, label) {
+ $scope.layerAddId = item.id;
+ }
+
+ $scope.layerAdd = function() {
+
+ $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "layerdeps", value: $scope.layerAddId }})
+ .success(function (_data) {
+ if (_data.error != "ok") {
+ alert(_data.error);
+ } else {
+ if (_data.list.length > 0) {
+ // activate modal
+ var modalInstance = $modal.open({
+ templateUrl: 'dependencies_modal',
+ controller: function ($scope, $modalInstance, items, layerAddName) {
+ $scope.items = items;
+ $scope.layerAddName = layerAddName;
+ $scope.selectedItems = (function () { s = {}; for (var i = 0; i < items.length; i++) { s[items[i].id] = true; };return s; })();
+
+ $scope.ok = function() {
+ console.log("scope selected is ", $scope.selectedItems);
+ $modalInstance.close(Object.keys($scope.selectedItems).filter(function (e) { return $scope.selectedItems[e];}));
+ };
+
+ $scope.cancel = function() {
+ $modalInstance.dismiss('cancel');
+ };
+
+ $scope.update = function() {
+ console.log("updated ", $scope.selectedItems);
+ };
+ },
+ resolve: {
+ items: function () {
+ return _data.list;
+ },
+ layerAddName: function () {
+ return $scope.layerAddName;
+ },
+ }
+ });
+
+ modalInstance.result.then(function (selectedArray) {
+ selectedArray.push($scope.layerAddId);
+ console.log("selected", selectedArray);
+
+ $scope._makeXHRCall({
+ method: "POST", url: $scope.urls.xhr_edit,
+ data: {
+ layerAdd: selectedArray.join(","),
+ }
+ }).then(function () {
+ $scope.layerAddName = undefined;
+ });
+ });
+ }
+ else {
+ $scope._makeXHRCall({
+ method: "POST", url: $scope.urls.xhr_edit,
+ data: {
+ layerAdd: $scope.layerAddId,
+ }
+ }).then(function () {
+ $scope.layerAddName = undefined;
+ });
+ }
+ }
+ });
+ }
+
+ $scope.layerDel = function(id) {
+ $scope._makeXHRCall({
+ method: "POST", url: $scope.urls.xhr_edit,
+ data: {
+ layerDel: id,
+ }
+ });
+ }
+
+
+ $scope.test = function(elementid) {
+ $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "versionlayers", value: $scope.projectVersion }}).
+ success(function (_data) {
+ if (_data.error != "ok") {
+ alert (_data.error);
+ }
+ else {
+ if (_data.list.length > 0) {
+ // activate modal
+ var modalInstance = $modal.open({
+ templateUrl: 'change_version_modal',
+ controller: function ($scope, $modalInstance, items, releaseName) {
+ $scope.items = items;
+ $scope.releaseName = releaseName;
+
+ $scope.ok = function() {
+ $modalInstance.close();
+ };
+
+ $scope.cancel = function() {
+ $modalInstance.dismiss('cancel');
+ };
+
+ },
+ resolve: {
+ items: function () {
+ return _data.list;
+ },
+ releaseName: function () {
+ return $scope.releases.filter(function (e) { if (e.id == $scope.projectVersion) return e;})[0].name;
+ },
+ }
+ });
+
+ modalInstance.result.then(function () { $scope.edit(elementid)});
+ } else {
+ $scope.edit(elementid);
+ }
+ }
+ });
+ }
+
+ $scope.edit = function(elementid) {
+ var data = {};
+ console.log("edit with ", elementid);
+ var alertText = undefined;
+ var alertZone = undefined;
+ switch(elementid) {
+ case '#select-machine':
+ alertText = "You have changed the machine to: <b>" + $scope.machineName + "</b>";
+ alertZone = $scope.zone2alerts;
+ data['machineName'] = $scope.machineName;
+ break;
+ case '#change-project-name':
+ data['projectName'] = $scope.projectName;
+ alertText = "You have changed the project name to: <b>" + $scope.projectName + "</b>";
+ alertZone = $scope.zone3alerts;
+ break;
+ case '#change-project-version':
+ data['projectVersion'] = $scope.projectVersion;
+ alertText = "You have changed the release to: ";
+ alertZone = $scope.zone3alerts;
+ break;
+ default:
+ throw "FIXME: implement conversion for element " + elementid;
+ }
+
+ console.log("calling edit with ", data);
+ $scope._makeXHRCall({
+ method: "POST", url: $scope.urls.xhr_edit, data: data,
+ }).then( function () {
+ $scope.toggle(elementid);
+ if (data['projectVersion'] != undefined) {
+ alertText += "<b>" + $scope.release.name + "</b>";
+ }
+ $scope.displayAlert(alertZone, alertText, "alert-info");
+ });
+ }
+
+
+ $scope.executeCommands = function() {
+ cmd = $location.path();
+
+ function _cmdExecuteWithParam(param, f) {
+ if (cmd.indexOf(param)==0) {
+ if (cmd.indexOf("=") > -1) {
+ var parameter = cmd.split("=", 2)[1];
+ if (parameter != undefined && parameter.length > 0) {
+ f(parameter);
+ }
+ } else {
+ f();
+ };
+ }
+ }
+
+ _cmdExecuteWithParam("/newproject", function () {
+ $scope.displayAlert($scope.zone1alerts,
+ "Your project <strong>" + $scope.project.name +
+ "</strong> has been created. You can now <a href=\""+ $scope.urls.layers +
+ "\">add layers</a> and <a href=\""+ $scope.urls.targets +
+ "\">select targets</a> you want to build.", "alert-success");
+ });
+
+ _cmdExecuteWithParam("/targetbuild=", function (targets) {
+ var oldTargetName = $scope.targetName;
+ $scope.targetName = targets.split(",").join(" ");
+ $scope.targetNamedBuild();
+ $scope.targetName = oldTargetName;
+ });
+
+ _cmdExecuteWithParam("/machineselect=", function (machine) {
+ $scope.machineName = machine;
+ $scope.toggle('#select-machine');
+ });
+
+
+ _cmdExecuteWithParam("/layeradd=", function (layer) {
+ angular.forEach(layer.split(","), function (l) {
+ $scope.layerAddId = l;
+ $scope.layerAdd();
+ });
+ });
+ }
+
+ $scope.displayAlert = function(zone, text, type) {
+ if (zone.maxid === undefined) { zone.maxid = 0; }
+ var crtid = zone.maxid ++;
+ angular.forEach(zone, function (o) { o.close() });
+ o = {
+ id: crtid, text: $sce.trustAsHtml(text), type: type,
+ close: function() {
+ zone.splice((function(id){ for (var i = 0; i < zone.length; i++) if (id == zone[i].id) { return i}; return undefined;})(crtid), 1);
+ },
+ }
+ zone.push(o);
+ return o;
+ }
+
+ $scope.toggle = function(id) {
+ $scope.projectName = $scope.project.name;
+ $scope.projectVersion = $scope.project.release.id;
+ $scope.machineName = $scope.machine.name;
+
+ angular.element(id).toggle();
+ angular.element(id+"-opposite").toggle();
+ }
+
+ $scope.selectedMostBuildTargets = function () {
+ keys = Object.keys($scope.mostBuiltTargets);
+ keys = keys.filter(function (e) { if ($scope.mostBuiltTargets[e]) return e });
+ return keys.length == 0;
+
+ }
+
+ // init code
+ //
+ $scope.init = function() {
+ $scope.pollHandle = $interval(function () { $scope._makeXHRCall({method: "POST", url: $scope.urls.xhr_edit, data: undefined});}, 4000, 0);
+ }
+
+ $scope.init();
+});
+
+
+/**
+ TESTING CODE
+*/
+
+function test_diff_arrays() {
+ _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);})
+}
+
+var s = undefined;
+
+function test_set_alert(text) {
+ s = angular.element("div#main").scope();
+ s.displayAlert(s.zone3alerts, text);
+ console.log(s.zone3alerts);
+ s.$digest();
+}
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html
index 9ef249a..d414bfb 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) {
{%if MANAGED %}
<div class="btn-group pull-right">
<a class="btn" href="{% url 'newproject' %}">New project</a>
- <button class="btn dropdown-toggle" data-toggle="dropdown">
- <i class="icon-caret-down"></i>
- </button>
- <ul class="dropdown-menu">
-{% for prj in projects %}
- <li><a href="{% url 'project' prj.id %}">{{prj.name}}</a></li>
-{% endfor %}
- <li><hr/></li>
- <li><a href="#">Clone project</a></li>
- <li><a href="#">Export project</a></li>
- <li><a href="#">Import project</a></li>
- </ul>
</div>
{%endif%}
<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 54edaaf..95a9f47 100644
--- a/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html
+++ b/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html
@@ -12,7 +12,7 @@
{% block parentbreadcrumb %}
{% if project %}
<li>
- <a href="{%url 'project' project.id %}">{{project.name}}
+ <a href="{%url 'project' project.id %}"><span id="project_name">{{project.name}}</span>
</a>
</li>
{% endif %}
@@ -28,11 +28,11 @@
</script>
</div>
- <div class="row-fluid">
+ <div>
- <!-- Begin right container -->
+ <!-- Begin main page container -->
{% block projectinfomain %}{% endblock %}
- <!-- End right container -->
+ <!-- End main container -->
</div>
diff --git a/bitbake/lib/toaster/toastergui/templates/build.html b/bitbake/lib/toaster/toastergui/templates/build.html
index faabd22..bef1f15 100644
--- a/bitbake/lib/toaster/toastergui/templates/build.html
+++ b/bitbake/lib/toaster/toastergui/templates/build.html
@@ -127,6 +127,13 @@
<a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a>
{% endif %}
</td>
+ {% if MANAGED %}
+ <td class="project">
+ {% if build.project %}
+ <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a>
+ {% endif %}
+ </td>
+ {% endif %}
</tr>
{% endfor %}
diff --git a/bitbake/lib/toaster/toastergui/templates/layers.html b/bitbake/lib/toaster/toastergui/templates/layers.html
index 281b72a..b32a7ed 100644
--- a/bitbake/lib/toaster/toastergui/templates/layers.html
+++ b/bitbake/lib/toaster/toastergui/templates/layers.html
@@ -85,14 +85,6 @@
</div>
</div>
- <script src="assets/js/jquery-1.9.1.min.js" type='text/javascript'></script>
- <script src="assets/js/jquery.tablesorter.min.js" type='text/javascript'></script>
- <script src="assets/js/jquery-ui-1.10.3.custom.min.js"></script>
- <script src="assets/js/bootstrap.min.js" type='text/javascript'></script>
- <script src="assets/js/prettify.js" type='text/javascript'></script>
- <script src="assets/js/jit.js" type='text/javascript'></script>
- <script src="assets/js/main.js" type='text/javascript'></script>
-
<script>
$(document).ready(function() {
diff --git a/bitbake/lib/toaster/toastergui/templates/newproject.html b/bitbake/lib/toaster/toastergui/templates/newproject.html
index 12c4e9f..43c4e28 100644
--- a/bitbake/lib/toaster/toastergui/templates/newproject.html
+++ b/bitbake/lib/toaster/toastergui/templates/newproject.html
@@ -17,13 +17,6 @@
<label>Project name <span class="muted">(required)</span></label>
<input type="text" class="input-xlarge" required name="projectname" value="{{projectname}}">
<label class="project-form">
- Project owner
- <i class="icon-question-sign get-help" title="The go-to person for this project"></i>
- </label>
- <input type="text" name="username" value="{{username}}">
- <label class="project-form">Owner's email</label>
- <input type="email" class="input-large" name="email" value="{{email}}">
- <label class="project-form">
Yocto Project version
<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>
</label>
diff --git a/bitbake/lib/toaster/toastergui/templates/project.html b/bitbake/lib/toaster/toastergui/templates/project.html
index c3a470c..9399b31 100644
--- a/bitbake/lib/toaster/toastergui/templates/project.html
+++ b/bitbake/lib/toaster/toastergui/templates/project.html
@@ -1,366 +1,354 @@
-{% extends "base.html" %}
+{% extends "baseprojectpage.html" %}
+<!--
+vim: expandtab tabstop=2
+-->
{% load projecttags %}
{% load humanize %}
-{% block pagecontent %}
-
-<script>
-
-var buildrequests = [];
-
-function targetInPage(targetname) {
- return targetname in $("ul#target-list > li > a").map(function (i, x) {return x.text});
-}
-
-function setEventHandlers() {
- $("i#del-target-icon").unbind().click(function (evt) {
- console.log("del target", evt.target.attributes["x-data"].value);
- postEditAjaxRequest({"targetDel": evt.target.attributes["x-data"].value});
- });
- $("button#add-target-button").unbind().click( function (evt) {
- if ( $("input#target")[0].value.length == 0) {
- alert("cannot add empty target");
- return;
- }
- postEditAjaxRequest({"targetAdd" : $("input#target")[0].value});
- });
-}
-
-function onEditPageUpdate(data) {
- // update targets
- var i; var orightml = "";
-
- $("span#target-count").html(data.targets.length);
- for (i = 0; i < data.targets.length; i++) {
- if (! targetInPage(data.targets[i].target)) {
- orightml += '<li><a href="#">'+data.targets[i].target;
- if (data.targets[i].task != "" && data.targets[i].task !== null) {
- orightml += " ("+data.targets[i].task+")";
- }
- orightml += '</a><i title="" data-original-title="" class="icon-trash" id="del-target-icon" x-data="'+data.targets[i].pk+'"></i></li>';
- }
- }
-
- $("ul#target-list").html(orightml);
-
- // update recent builds
-
- setEventHandlers();
-}
-
-function onEditAjaxSuccess(data, textstatus) {
- console.log("XHR returned:", data, "(" + textstatus + ")");
- if (data.error != "ok") {
- alert("error on request:\n" + data.error);
- return;
- }
- onEditPageUpdate(data);
-}
-
-function onEditAjaxError(jqXHR, textstatus, error) {
- alert("XHR errored:\n" + error + "\n(" + textstatus + ")");
-}
-
-function postEditAjaxRequest(reqdata) {
- var ajax = $.ajax({
- type:"POST",
- data: $.param(reqdata),
- url:"{% url 'xhr_projectedit' project.id%}",
- headers: { 'X-CSRFToken': $.cookie("csrftoken")},
- success: onEditAjaxSuccess,
- error: onEditAjaxError,
- })
-}
-
-
-
-
-$(document).ready(function () {
- setEventHandlers();
-
- /* Provide XHR calls for the "build" buttons.*/
- $("button#build-all-button").click( function (evt) {
- var ajax = $.ajax({
- type:"POST",
- url:"{% url 'xhr_projectbuild' project.id %}",
- headers: { 'X-CSRFToken': $.cookie("csrftoken")},
- success: function (data, textstatus) {
- if (data.error != "ok") {
- alert("XHR fail: " + data.error );
- }
- },
- error: function (jqXHR, textstatus, error) { alert("XHR errored:" + error + "(" + textstatus + ")"); },
- })
- });
-});
-
-
-</script>
+{% load static %}
- <div class="page-header">
- <h1>
- {{project.name}}
- {% if project.build_set.all.count == 0 %}
- <small>No builds yet</small>
- {% else %}
- <small><a href="#">{{project.build_set.all.count}} builds</a></small>
- {% endif %}
- </h1>
- </div>
+{% block projectinfomain %}
+<script src="{% static "js/angular.min.js" %}"></script>
+<script src="{% static "js/angular-cookies.min.js" %}"></script>
+<script src="{% static "js/ui-bootstrap-tpls-0.11.0.js" %}"></script>
- <div class="well">
- <form class="build-form">
- <div class="input-append input-prepend controls">
- <input type="text" class="huge span7" placeholder="Type the target(s) you want to build" autocomplete="off" data-minLength="1" data-autocomplete="off"
- data-provide="typeahead" data-source='["core-image-base [meta | daisy]",
- "core-image-clutter [meta | daisy]",
- "core-image-directfb [meta | daisy]",
- "core-image-myimage [meta-imported-layer | 3e1dbabbf3&hellip;]",
- "core-image-anotherimage [meta-imported-layer | master]",
- "core-image-full-cmdline [meta | daisy]",
- "core-image-lsb [meta | daisy]",
- "core-image-lsb-dev [meta | daisy]",
- "core-image-lsb-sdk [meta| daisy]",
- "core-image-minimal [meta| daisy]"
- ]'>
- <a href="#" id="build-button" class="btn btn-large btn-primary" disabled>
- Build
- <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>
- </a>
- </div>
- <p>
- <a href="all-targets.html" style="padding-right: 5px;">
- View all targets
- </a>
- |
- <a href="{% url 'projectbuilds' project.id%}" style="padding-left:5px;">
- View all project builds ({{project.build_set.count}})
- </a>
- </form>
- </div>
+<div id="main" role="main" ng-app="project" ng-controller="prjCtrl" class="top-padded">
+ <!-- project name -->
+ <div class="page-header">
+ <h1>{[project.name]}</h1>
+ </div>
+ <!-- alerts section 1-->
+ <div ng-repeat="a in zone1alerts">
+ <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>
+ <span ng-bind-html="a.text"></span>
+ </div>
+ </div>
+ <!-- custom templates for ng -->
- {% if builds|length > 0 or buildrequests|length > 0 %}
- <h2 class="air">Recent Builds</h2>
+ <script type="text/ng-template" id="suggestion_details">
+ <a> {[match.model.name]} {[match.model.detail]} </a>
+ </script>
- <div id="scheduled-builds">
- {% for br in buildrequests %}
-<div class="alert {% if br.0.state == br.0.REQ_FAILED%}alert-error{%else%}alert-info{%endif%}" id="build-request">
- <div class="row-fluid">
- <div class="lead span4">
- <span>
- {{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}})
- </span>
+ <!-- modal dialogs -->
+ <script type="text/ng-template" id="dependencies_modal">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
+ <h3><span ng-bind="layerAddName"></span> dependencies</h3>
+ </div>
+ <div class="modal-body">
+ <p><strong>{[layerAddName]}</strong> depends on some layers that are not added to your project. Select the ones you want to add:</p>
+ <ul class="unstyled">
+ <li ng-repeat="ld in items">
+ <label class="checkbox">
+ <input type="checkbox" ng-model="selectedItems[ld.id]"> {[ld.name]}
+ </label>
+ </li>
+ </ul>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" ng-click="ok()">Add layers</button>
+ <button class="btn" ng-click="cancel()">Cancel</button>
+ </div>
+ </form>
+ </script>
+
+
+ <script type="text/ng-template" id="change_version_modal">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
+ <h3>Changing release to {[releaseName]}</h3>
+ </div>
+ <div class="modal-body">
+ <p>The following project layers do not exist for {[releaseName]}:</p>
+ <ul>
+ <li ng-repeat="i in items"><span class="layer-info" data-toggle="tooltip" tooltip="{[i.detail]}">{[i.name]}</span></li>
+ </ul>
+ <p>If you change the release to {[releaseName]}, the above layers will be deleted from your project layers.</p>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" ng-click="ok()">Change release and delete layers</button>
+ <button class="btn" ng-click="cancel()">Cancel</button>
+ </div>
+ </script>
+
+ <!-- build form -->
+ <div class="well">
+ <form class="build-form" ng-submit="targetNamedBuild()">
+ <div class="input-append input-prepend controls">
+ <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"/>
+ <button type="submit" id="build-button" class="btn btn-large btn-primary" ng-disabled="!targetName.length">
+ Build
+ <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>
+ </button>
+ </div>
+ <p>
+ <a href="{% url 'targets' %}" style="padding-right: 5px;">
+ View all targets
+ </a>
+ {% if completedbuilds.count %}
+ | <a href="{% url 'projectbuilds' project.id %}">View all project builds ({{completedbuilds.count}})</a>
+ {% endif %}
+ </p>
+ </form>
+ </div>
+
+ <h2 class="air" ng-if="builds.length">Latest builds</h2>
+
+ <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]">
+ <div class="row-fluid">
+ <switch ng-switch="b.status">
+ <case ng-switch-when="failed">
+ <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
+ <div class="row-fluid">
+ <div class="air well" ng-repeat="e in b.errors">
+ {[e.type]}: <pre>{[e.msg]}</pre>
+ </div>
+ </div>
+ </case>
+ <case ng-switch-when="queued">
+ <div class="lead span5"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
+ <div class="span4 lead" >Build queued
+ <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>
+ </div>
+ <button class="btn pull-right btn-info" ng-click="buildCancel(b.id)">Cancel</button>
+ </case>
+ <case ng-switch-when="created">
+ <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
+ <div class="span6" >
+ <span class="lead">Creating build</span>
</div>
- <div class="span2">
- {{br.0.get_state_display}}
+ <button class="btn pull-right btn-info" ng-click="buildCancel(b.id)">Cancel</button>
+ </case>
+ <case ng-switch-when="deleted">
+ <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
+ <div class="span6" id="{[b.id]}-deleted" >
+ <span class="lead">Build deleted</span>
</div>
- <div class="span8">
-{% if br.state == br.REQ_FAILED%}
- {% for bre in br.0.brerror_set.all %} {{bre.errmsg}} ({{bre.errtype}}) <br/><hr/><code>{{bre.traceback}}</code>{%endfor%}
-{%endif%}
+ <button class="btn pull-right btn-info" ng-click="builds.splice(builds.indexOf(b), 1)">Close</button>
+ </case>
+ <case ng-switch-when="in progress">
+ <div class="lead span3"> <span ng-repeat="t in b.targets">{[t.target]} </span> </div>
+ <div class="span4" >
</div>
+ <div class="lead pull-right">Build starting shortly</div>
+ </case>
+ <case ng-switch-when="In Progress">
+ <div class="span4" >
+ <div class="progress" style="margin-top:5px;" data-toggle="tooltip" tooltip="{[b.completeper]}% of tasks complete">
+ <div style="width: {[b.completeper]}%;" class="bar"></div>
+ </div>
+ </div>
+ <div class="lead pull-right">ETA: at {[b.eta]}</div>
+ </case>
+ <case ng-switch-default="">
+ <div class="lead span3"><a href="{[b.build_page_url]}"><span ng-repeat="t in b.targets">{[t.target]} </span> </div></a>
+ <div class="span2 lead">
+ {[b.completed_on|date:'dd/MM/yy HH:mm']}
+ </div>
+ <div class="span2"><span>{[b.errors.len]}</span></div>
+ <div class="span2"><span>{[b.warnings.len]}</span></div>
+ <div> <span class="lead">Build time: {[b.build_time|date:"HH:mm"]}</span>
+ <button class="btn pull-right" ng-class="{'Succeeded': 'btn-success', 'Failed': 'btn-danger'}[b.status]"
+ ng-click="targetExistingBuild(b.targets)">Run again</button>
- </div>
-</div>
+ </div>
+ </case>
+ </switch>
+ <div class="lead pull-right">
+ </div>
+ </div>
+ </div>
- {% endfor %}
+ <h2 class="air">Project configuration</h2>
+ <!-- alerts section 2 -->
+ <div ng-repeat="a in zone2alerts">
+ <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>
+ <span ng-bind-html="a.text"></span>
+ </div>
+ </div>
+
+ <div class="row-fluid">
+
+ <!-- project layers -->
+ <div id="layer-container" class="well well-transparent span4">
+ <h3>
+ Project layers <span class="muted counter">({[layers.length]})</span>
+ <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>
+ </h3>
+ <div class="alert" ng-if="!layers.length">
+ <b>You need to add some layers </b>
+ <p>
+ You can:
+ <ul>
+ <li> <a href="{% url 'layers'%}">View all layers available in Toaster</a>
+ <li> <a href="{% url 'importlayer' %}">Import a layer</a>
+ <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>
+ </ul>
+ Or type a layer name below.
+ </p>
+ </div>
+ <form class="input-append" ng-submit="layerAdd()">
+ <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 }" />
+ <input type="submit" id="add-layer" class="btn" value="Add" ng-disabled="!layerAddName.length"/>
+ {% csrf_token %}
+ </form>
+ <p><a href="{% url 'layers' %}">View all layers</a> | <a href="{% url 'importlayer' %}">Import layer</a></p>
+ <ul class="unstyled configuration-list">
+ <li ng-repeat="l in layers">
+ <a href="{[l.layerdetailurl]}" target="_#" class="layer-info" data-toggle="tooltip" tooltip="{[l.branch.layersource]} | {[l.branch.name]}">{[l.name]} </a>
+ <i class="icon-trash" ng-click="layerDel(l.id)" tooltip="Delete"></i>
+ </li>
+ </ul>
</div>
+ <!-- project targets -->
+ <div id="target-container" class="well well-transparent span4">
+ <h3>
+ Targets
+ <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>
+ </h3>
+ <form ng-submit="targetNamedBuild()" class="input-append">
+ <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">
+ <button type="submit" id="build-button" class="btn btn-primary" ng-disabled="!targetName.length">
+ Build </button>
+ {% csrf_token %}
+ </form>
+ <p><a href="{% url 'targets' %}">View all targets</a></p>
+ <div ng-if="frequenttargets.length">
+ <h4>
+ Most built targets
+ </h4>
+ <ul class="unstyled configuration-list">
+ <li ng-repeat="t in frequenttargets">
+ <label class="checkbox">
+ <input type="checkbox" ng-model="mostBuiltTargets[t]">{[t]}
+ </label>
+ </li>
+ </ul>
+ <button class="btn btn-large btn-primary" ng-disabled="selectedMostBuildTargets()">Build selected targets</button>
+ </div>
+ </div>
-<!-- Lifted from build.html -->
- {% for build in builds %}
-<div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}">
- <div class="row-fluid">
- <div class="lead span5">
- {%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%}
- {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
- <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}">
- {% endif %}
- <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>
- {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
- </a>
- {% endif %}
- </div>
- {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
- <div class="span2 lead">
- {% if build.errors_no %}
- <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>
- {% endif %}
- </div>
- <div class="span2 lead">
- {% if build.warnings_no %}
- <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>
- {% endif %}
- </div >
- <div class="lead pull-right">
- Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a>
- </div>
- {%endif%}{%if build.outcome == build.IN_PROGRESS %}
- <div class="span4">
- <div class="progress" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete">
- <div style="width: {{build.completeper}}%;" class="bar"></div>
- </div>
- </div>
- <div class="lead pull-right">ETA: in {{build.eta|naturaltime}}</div>
- {%endif%}
+ <!-- project configuration -->
+ <div id="machine-distro" class="well well-transparent span4">
+ <h3>
+ Project machine
+ <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>
+ </h3>
+ <p class="lead" id="select-machine-opposite">
+ {[machine.name]}<i id="change-machine" class="icon-pencil" ng-click="toggle('#select-machine')" tooltip="Change"></i>
+ </p>
+ <div id="select-machine" style="display: none">
+ <div class="alert alert-info">
+ <strong>Machine changes have a big impact on build outcome.</strong>
+ You cannot really compare the builds for the new machine with the previous ones.
</div>
+ <form ng-submit="edit('#select-machine')" class="input-append">
+ <input type="text" id="machine" autocomplete="off" ng-model="machineName" typeahead="m.name for m in getSuggestions('machines', $viewValue)"/>
+ <input type="submit" id="apply-change-machine" class="btn" type="button" ng-disabled="machineName == machine.name || machineName.length == 0" value="Save"></input>
+ <input type="reset" id="cancel-machine" class="btn btn-link" ng-click="toggle('#select-machine')" value="Cancel"></input>
+ {% csrf_token %}
+ </form>
+ <p><a href="{% url 'machines' %}" class="link">View all machines</a></p>
+ </div>
+ <p class="link-action">
+ <a href="{% url 'projectconf' project.id %}" class="link">Edit configuration variables</a>
+ <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>
+ </p>
</div>
- {% endfor %}
-<!-- end of lift-->
- {%endif%}
+ </div>
- <h2 class="air">Project configuration</h2>
- <div class="row-fluid">
+ <h2>Project details</h2>
- <div id="layer-container" class="well well-transparent span4">
- <h3>
- Add layers
- <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>
- </h3>
- <form style="margin-top:20px;">
- <div class="input-append">
- <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">
- <button id="add-layer" class="btn" disabled="">Add</button>
- </div>
- <div id="import-alert" class="alert alert-info" style="display:none;">
- Toaster does not know about this layer. Please <a href="#">import it</a>
- </div>
- <div id="dependency-alert" class="alert alert-info" style="display:none;">
- <p><strong>meta-tizen</strong> depends on the layers below. Check the ones you want to add: </p>
- <ul class="unstyled">
- {% for f in layer_dependency %}
- <li>
- <label class="checkbox">
- <input checked="checked" type="checkbox">
- meta-ruby
- </label>
- </li>
- {% endfor %}
- </ul>
- <button id="add-layer-dependencies" class="btn btn-info add-layer">Add layers</button>
- </div>
-
- <p><a href="{% url 'importlayer' %}">Import your layer</a> | <a href="{% url 'layers'%}">View all layers</a></p>
- </form>
-
- <h4 class="air">
- Added layers
- <span class="muted counter">{{project.projectlayer_set.count}}</span>
- <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>
- </h4>
- <ul class="unstyled configuration-list">
- {% for pl in project.projectlayer_set.all %}
- <li>
- <a href="#">{{pl.layercommit.layer.name}} (<span class="layer-version">{{pl.layercommit.layer.layer_index_url}}</span>)</a>
- {% if pl.optional %}
- <i title="" data-original-title="" class="icon-trash" id="del-layer-icon" x-data="{{pl.pk}}"></i>
- {% endif %}
- </li>
- {% endfor %}
- </ul>
- </div>
+ <!-- alerts section 3 -->
+ <div ng-repeat="a in zone3alerts">
+ <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>
+ <span ng-bind-html="a.text"></span>
+ </div>
+ </div>
+
+
+ <div id="project-details" class="well well-transparent">
+ <h3>Project name</h3>
+ <p class="lead" id="change-project-name-opposite">
+ <span >{[project.name]}</span>
+ <i class="icon-pencil" ng-click="toggle('#change-project-name')" tooltip="Change"></i>
+ </p>
+ <div id="change-project-name" style="display:none;">
+ <form ng-submit="edit('#change-project-name')" class="input-append">
+ <input type="text" class="input-xlarge" id="type-project-name" ng-model="projectName">
+ <input type="submit" class="btn" value="Save" ng-disabled="project.name == projectName"/>
+ <input type="reset" class="btn btn-link" value="Cancel" ng-click="toggle('#change-project-name')">
+ </form>
+ </div>
- <div id="target-container" class="well well-transparent span4">
- <h3>
- Add targets
- <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>
- </h3>
- <form style="margin-top:20px;">
- <div class="input-append">
- <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">
- <button id="add-target-button" class="btn" type="button">Add</button>
- </div>
-
- <p><a href="{% url 'targets' %}" class="link">View all targets</a></p>
- </form>
- <h4 class="air">
- Added targets
- <span id="target-count" class="muted counter">{{project.projecttarget_set.count}}</span>
- </h4>
- <ul class="unstyled configuration-list" id="target-list">
- {% for target in project.projecttarget_set.all %}
- {% if target %}
- <li>
- <a href="#">{{target.target}}{% if target.task%} (target.task){%endif%}</a>
- {% if target.notprovided %}
- <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>
- {% elif target.notknown %}
- <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>
- {% endif %}
- <i title="" data-original-title="" class="icon-trash" id="del-target-icon" x-data="{{target.pk}}"></i>
- </li>
- {% endif %}
- {% endfor %}
-
-
- </ul>
- </div>
- <div class="well well-transparent span4">
-
- <h3>
- Project machine
- <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>
- </h3>
- <p class="lead" id="selected-machine"> {{machine}}
- <i id="change-machine" class="icon-pencil"></i>
- </p>
- <form id="select-machine">
- <div class="alert alert-info">
- <strong>Machine changes have a big impact on build outcome.</strong>
- You cannot really compare the builds for the new machine with the previous ones.
- </div>
- <div class="input-append">
- <input type="text" id="machine" autocomplete="off" value="qemux86" data-provide="typeahead"
- data-minLength="1"
- data-autocomplete="off"
- data-source='[
- ]'>
- <button id="apply-change-machine" class="btn" type="button">Save</button>
- <a href="#" id="cancel-machine" class="btn btn-link">Cancel</a>
- </div>
- <p><a href="{% url 'machines' %}" class="link">View all machines</a></p>
- </form>
- <p class="link-action">
- <a href="{% url 'projectconf' project.id %}" class="link">Edit configuration variables</a>
- <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>
- </p>
+ <h3>
+ Release
+ <i class="icon-question-sign get-help heading-help" title="The version of the build system you want to use"></i>
+ </h3>
+ <p class="lead" id="change-project-version-opposite">
+ <span id="project-version">{[project.release.name]}</span>
+ <i id="change-version" class="icon-pencil" ng-click="toggle('#change-project-version')" tooltip="Change"></i>
+ </p>
+ <div class="div-inline" id="change-project-version" style="display:none;">
+ <form ng-submit="test('#change-project-version')" class="input-append">
+ <select id="select-version" ng-model="projectVersion">
+ <option ng-repeat="r in releases" value="{[r.id]}" ng-selected="r.id == project.release.id">{[r.name]}</option>
+ </select>
+ <input type="submit" class="btn" style="margin-left:5px;" value="Save" ng-disabled="project.release.id == projectVersion"/>
+ <input type="reset" class="btn btn-link" value="Cancel" ng-click="toggle('#change-project-version')" ng-disabled="project.release.id == projectVersion"/>
+
+ </form>
+ </div>
+ </div>
- </div>
+<!-- end main -->
+</div>
- </div>
+<!-- load application logic !-->
+<script src="{% static "js/projectapp.js" %}"></script>
+
+<!-- dump initial data for use in the angular app -->
+<script>
+angular.element(document).ready(function() {
+ scope = angular.element("#main").scope();
+ scope.urls = {};
+ scope.urls.xhr_build = "{% url 'xhr_projectbuild' project.id %}";
+ scope.urls.xhr_edit = "{% url 'xhr_projectedit' project.id %}";
+ scope.urls.xhr_datatypeahead = "{% url 'xhr_datatypeahead' %}";
+ scope.urls.layers = "{% url 'layers' %}";
+ scope.urls.targets = "{% url 'targets' %}";
+ scope.urls.importlayer = "{% url 'importlayer'%}"
+ scope.project = {{prj|safe}};
+ scope.builds = {{builds|safe}};
+ scope.layers = {{layers|safe}};
+ scope.targets = {{targets|safe}};
+ scope.frequenttargets = {{freqtargets|safe}};
+ scope.machine = {{machine|safe}};
+ scope.releases = {{releases|safe}};
+
+ scope.zone1alerts = [];
+ scope.zone2alerts = [];
+ scope.zone3alerts = [];
+
+ scope.mostBuiltTargets = {};
+
+ scope.executeCommands();
+ scope.validateData();
+
+ scope.$digest();
+
+ });
+</script>
- <h2>Project details</h2>
-
- <div class="well well-transparent">
- <h3>Project name</h3>
- <p class="lead">
- {{project.name}}
- <i title="" data-original-title="" class="icon-pencil"></i>
- </p>
- <h3>Project owner</h3>
- <p class="lead">
- {{puser.username}}
- <i title="" data-original-title="" class="icon-pencil"></i>
- </p>
- <h3>Owner's email</h3>
- <p class="lead">
- {{puser.email}}
- <i title="" data-original-title="" class="icon-pencil"></i>
- </p>
- <h3>Yocto Project version</h3>
- <p class="lead">
- {{project.release.name}} - {{project.release.description}}
- <i title="" data-original-title="" class="icon-pencil"></i>
- </p>
- </div>
{% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py
index a9c0592..e642e32 100644
--- a/bitbake/lib/toaster/toastergui/urls.py
+++ b/bitbake/lib/toaster/toastergui/urls.py
@@ -81,6 +81,8 @@ urlpatterns = patterns('toastergui.views',
url(r'^xhr_projectbuild/(?P<pid>\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'),
url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'),
+ url(r'^xhr_datatypeahead/$', 'xhr_datatypeahead', name='xhr_datatypeahead'),
+
# default redirection
url(r'^$', RedirectView.as_view( url= 'builds/')),
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 3fde3c9..5fe4a9d 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -387,6 +387,17 @@ def builds(request):
]
}
+ if toastermain.settings.MANAGED:
+ context['tablecols'].append(
+ {'name': 'Project', 'clclass': 'project',
+ 'filter': {'class': 'project',
+ 'label': 'Project:',
+ 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()),
+
+ }
+ })
+
+
response = render(request, template, context)
_save_parameters_cookies(response, pagesize, orderby, request)
return response
@@ -1799,7 +1810,7 @@ if toastermain.settings.MANAGED:
from django.contrib.auth.decorators import login_required
from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable
- from orm.models import Branch, LayerSource, ToasterSetting, Release, Machine
+ from orm.models import Branch, LayerSource, ToasterSetting, Release, Machine, LayerVersionDependency
from bldcontrol.models import BuildRequest
import traceback
@@ -1831,7 +1842,7 @@ if toastermain.settings.MANAGED:
# render new project page
return render(request, template, context)
elif request.method == "POST":
- mandatory_fields = ['projectname', 'email', 'username', 'projectversion']
+ mandatory_fields = ['projectname', 'projectversion']
try:
# make sure we have values for all mandatory_fields
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:
", ".join([x for x in mandatory_fields if len(request.POST.get(x, '')) == 0 ]))
if not request.user.is_authenticated():
- user = authenticate(username = request.POST['username'], password = 'nopass')
+ user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
if user is None:
- user = User.objects.create_user(username = request.POST['username'], email = request.POST['email'], password = "nopass")
+ user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
user = authenticate(username = user.username, password = 'nopass')
login(request, user)
@@ -1852,7 +1863,7 @@ if toastermain.settings.MANAGED:
release = Release.objects.get(pk = request.POST['projectversion']))
prj.user_id = request.user.pk
prj.save()
- return redirect(reverse(project, args = (prj.pk,)))
+ return redirect(reverse(project, args = (prj.pk,)) + "#/newproject")
except (IntegrityError, BadParameterException) as e:
# fill in page with previously submitted values
@@ -1865,6 +1876,40 @@ if toastermain.settings.MANAGED:
raise Exception("Invalid HTTP method for this page")
+
+ def _project_recent_build_list(prj):
+ # build requests not yet started
+ return (map(lambda x: {
+ "id": x.pk,
+ "targets" : map(lambda y: {"target": y.target }, x.brtarget_set.all()),
+ "status": x.get_state_display(),
+ }, prj.buildrequest_set.filter(state__lt = BuildRequest.REQ_INPROGRESS).order_by("-pk")) +
+ # build requests started, but with no build yet
+ map(lambda x: {
+ "id": x.pk,
+ "targets" : map(lambda y: {"target": y.target }, x.brtarget_set.all()),
+ "status": x.get_state_display(),
+ }, prj.buildrequest_set.filter(state = BuildRequest.REQ_INPROGRESS, build = None).order_by("-pk")) +
+ # build requests that failed
+ map(lambda x: {
+ "id": x.pk,
+ "targets" : map(lambda y: {"target": y.target }, x.brtarget_set.all()),
+ "status": x.get_state_display(),
+ "errors": map(lambda y: {"type": y.errtype, "msg": y.errmsg, "tb": y.traceback}, x.brerror_set.all()),
+ }, prj.buildrequest_set.filter(state = BuildRequest.REQ_FAILED).order_by("-pk")) +
+ # and already made builds
+ map(lambda x: {
+ "id": x.pk,
+ "targets": map(lambda y: {"target": y.target }, x.target_set.all()),
+ "status": x.get_outcome_display(),
+ "completed_on" : x.completed_on.strftime('%s')+"000",
+ "build_time" : (x.completed_on - x.started_on).total_seconds(),
+ "build_page_url" : reverse('builddashboard', args=(x.pk,)),
+ "completeper": x.completeper(),
+ "eta": x.eta().ctime(),
+ }, prj.build_set.all()))
+
+
# Shows the edit project page
def project(request, pid):
template = "project.html"
@@ -1881,27 +1926,40 @@ if toastermain.settings.MANAGED:
# we use implicit knowledge of the current user's project to filter layer information, e.g.
request.session['project_id'] = prj.id
+ from collections import Counter
+ freqtargets = []
+ try:
+ 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))))
+ 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))))
+ except TypeError:
+ pass
+ freqtargets = Counter(freqtargets)
+ freqtargets = sorted(freqtargets, key = lambda x: freqtargets[x])
+
context = {
"project" : prj,
+ "completedbuilds": Build.objects.filter(project = prj).exclude(outcome = Build.IN_PROGRESS),
+ "prj" : json.dumps({"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name}}),
#"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED),
- "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")),
- "builds" : prj.build_set.all(),
- "puser": puser,
+ "builds" : json.dumps(_project_recent_build_list(prj)),
+ "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())),
+ "targets" : json.dumps(map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all())),
+ "freqtargets": json.dumps(freqtargets),
+ "releases": json.dumps(map(lambda x: {"id": x.pk, "name": x.name}, Release.objects.all())),
}
try:
- context["machine"] = prj.projectvariable_set.get(name="MACHINE").value
+ context["machine"] = json.dumps({"name": prj.projectvariable_set.get(name="MACHINE").value})
except ProjectVariable.DoesNotExist:
- context["machine"] = "-- not set yet"
-
+ context["machine"] = json.dumps(None)
try:
context["distro"] = prj.projectvariable_set.get(name="DISTRO").value
except ProjectVariable.DoesNotExist:
context["distro"] = "-- not set yet"
-
- return render(request, template, context)
-
- import json
+ response = render(request, template, context)
+ response['Cache-Control'] = "no-cache, must-revalidate, no-store"
+ response['Pragma'] = "no-cache"
+ return response
def xhr_projectbuild(request, pid):
try:
@@ -1909,57 +1967,155 @@ if toastermain.settings.MANAGED:
raise BadParameterException("invalid method")
prj = Project.objects.get(id = pid)
- if prj.projecttarget_set.count() == 0:
- raise BadParameterException("no targets selected")
- br = prj.schedule_build()
- return HttpResponse(json.dumps({"error":"ok",
- "brtarget" : map(lambda x: x.target, br.brtarget_set.all()),
- "machine" : br.brvariable_set.get(name="MACHINE").value,
+ if 'buildCancel' in request.POST:
+ for i in request.POST['buildCancel'].strip().split(" "):
+ br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_QUEUED)
+ print "selected for delete", br.pk
+ br.delete()
+ print "selected for delete", br.pk
- }), content_type = "application/json")
- except Exception as e:
- return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
-
- def xhr_projectedit(request, pid):
- try:
- prj = Project.objects.get(id = pid)
- # add targets
- if 'targetAdd' in request.POST:
- for t in request.POST['targetAdd'].strip().split(" "):
+ if 'targets' in request.POST:
+ ProjectTarget.objects.filter(project = prj).delete()
+ for t in request.POST['targets'].strip().split(" "):
if ":" in t:
target, task = t.split(":")
else:
target = t
task = ""
+ ProjectTarget.objects.create(project = prj, target = target, task = task)
- pt, created = ProjectTarget.objects.get_or_create(project = prj, target = target, task = task)
- # remove targets
- if 'targetDel' in request.POST:
- for t in request.POST['targetDel'].strip().split(" "):
- pt = ProjectTarget.objects.get(pk = int(t)).delete()
+ br = prj.schedule_build()
+ return HttpResponse(json.dumps({"error":"ok",
+ "builds" : _project_recent_build_list(prj),
+ }), content_type = "application/json")
+ except Exception as e:
+ return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
+
+ def xhr_projectedit(request, pid):
+ try:
+ prj = Project.objects.get(id = pid)
# add layers
+ if 'layerAdd' in request.POST:
+ for lc in Layer_Version.objects.filter(pk__in=request.POST['layerAdd'].split(",")):
+ ProjectLayer.objects.get_or_create(project = prj, layercommit = lc)
# remove layers
+ if 'layerDel' in request.POST:
+ for t in request.POST['layerDel'].strip().split(" "):
+ pt = ProjectLayer.objects.get(project = prj, layercommit_id = int(t)).delete()
+
+ if 'projectName' in request.POST:
+ prj.name = request.POST['projectName']
+ prj.save();
+
+
+ if 'projectVersion' in request.POST:
+ prj.release = Release.objects.get(pk = request.POST['projectVersion'])
+ prj.save()
+ # we need to change the layers
+ for i in prj.projectlayer_set.all():
+ # find and add a similarly-named layer from the same layer source on the new branch
+ 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))
+ if lv.count() == 1:
+ ProjectLayer.objects.get_or_create(project = prj, layercommit = lv[0])
+ # get rid of the old entry
+ i.delete()
# return all project settings
return HttpResponse(json.dumps( {
"error": "ok",
- "layers": map(lambda x: (x.layercommit.layer.name, x.layercommit.layer.layer_index_url), prj.projectlayer_set.all()),
- "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()),
+ "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()),
+ "builds" : _project_recent_build_list(prj),
"variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()),
+ "prj": {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name}},
}), content_type = "application/json")
except Exception as e:
return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
+
+ from django.views.decorators.csrf import csrf_exempt
+ @csrf_exempt
+ def xhr_datatypeahead(request):
+ try:
+ if request.GET['type'] == "layers":
+ queryset_all = Layer_Version.objects.all()
+ if 'project_id' in request.session:
+ prj = Project.objects.get(pk = request.session['project_id'])
+ 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()))
+ queryset_all = queryset_all.filter(layer__name__icontains=request.GET.get('value',''))
+ return HttpResponse(json.dumps( { "error":"ok",
+ "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+")")},
+ queryset_all[:8])
+ }), content_type = "application/json")
+
+ if request.GET['type'] == "layerdeps":
+ queryset_all = LayerVersionDependency.objects.filter(layer_version_id = request.GET['value'])
+
+ if 'project_id' in request.session:
+ prj = Project.objects.get(pk = request.session['project_id'])
+ queryset_all = queryset_all.exclude(depends_on__in = map(lambda x: x.layercommit, prj.projectlayer_set.all()))
+
+ queryset_all.order_by("-up_id");
+
+ return HttpResponse(json.dumps( { "error":"ok",
+ "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+")")},
+ map(lambda x: x.depends_on, queryset_all))
+ }), content_type = "application/json")
+
+ if request.GET['type'] == "versionlayers":
+ if not 'project_id' in request.session:
+ raise Exception("This call cannot makes no sense outside a project context")
+
+ retval = []
+ prj = Project.objects.get(pk = request.session['project_id'])
+ for i in prj.projectlayer_set.all():
+ 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))
+ if lv.count() != 1:
+ retval.append(i)
+
+ return HttpResponse(json.dumps( {"error":"ok",
+ "list": map(
+ 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+")")},
+ retval) }), content_type = "application/json")
+
+
+ if request.GET['type'] == "targets":
+ queryset_all = Recipe.objects.all()
+ if 'project_id' in request.session:
+ queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id'])))
+ return HttpResponse(json.dumps({ "error":"ok",
+ "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 "]")},
+ queryset_all.filter(name__istartswith=request.GET.get('value',''))[:8]),
+
+ }), content_type = "application/json")
+
+ if request.GET['type'] == "machines":
+ queryset_all = Machine.objects.all()
+ if 'project_id' in request.session:
+ queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id'])))
+ return HttpResponse(json.dumps({ "error":"ok",
+ "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 "]")},
+ queryset_all.filter(name__istartswith=request.GET.get('value',''))[:8]),
+
+ }), content_type = "application/json")
+
+ raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied"))
+ except Exception as e:
+ return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
+
+
+
def importlayer(request):
template = "importlayer.html"
context = {
}
return render(request, template, context)
+
def layers(request):
template = "layers.html"
# define here what parameters the view needs in the GET portion in order to
@@ -2220,7 +2376,7 @@ if toastermain.settings.MANAGED:
# boilerplate code that takes a request for an object type and returns a queryset
# for that object type. copypasta for all needed table searches
(filter_string, search_term, ordering_string) = _search_tuple(request, Build)
- queryset_all = Build.objects.all.exclude(outcome = Build.IN_PROGRESS)
+ queryset_all = Build.objects.all().exclude(outcome = Build.IN_PROGRESS)
queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
@@ -2388,6 +2544,9 @@ else:
def xhr_projectedit(request, pid):
raise Exception("page not available in interactive mode")
+ def xhr_datatypeahead(request):
+ raise Exception("page not available in interactive mode")
+
def importlayer(request):
raise Exception("page not available in interactive mode")
OpenPOWER on IntegriCloud