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>
This commit is contained in:
Alexandru DAMIAN
2014-09-09 11:47:13 +01:00
committed by Richard Purdie
parent 1a463ab98e
commit 960580cb70
11 changed files with 1148 additions and 433 deletions

View File

@@ -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)
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)
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()
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,

View File

@@ -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; }

View File

@@ -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();
}

View File

@@ -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">

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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() {

View File

@@ -16,13 +16,6 @@
<fieldset>
<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>

View File

@@ -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,
})
}
{% load static %}
{% 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>
$(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 + ")"); },
})
});
});
<div id="main" role="main" ng-app="project" ng-controller="prjCtrl" class="top-padded">
</script>
<!-- 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 -->
<script type="text/ng-template" id="suggestion_details">
<a> {[match.model.name]} {[match.model.detail]} </a>
</script>
<!-- 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>
<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>
<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>
<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>
</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>
{% if builds|length > 0 or buildrequests|length > 0 %}
<h2 class="air">Recent Builds</h2>
<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>
<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>
</case>
</switch>
<div class="lead pull-right">
</div>
</div>
</div>
<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>
<!-- 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>
</div>
<h2>Project details</h2>
<!-- 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>
<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>
<!-- end main -->
</div>
{% endfor %}
</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 = [];
<!-- 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%}
</div>
</div>
{% endfor %}
<!-- end of lift-->
{%endif%}
scope.mostBuiltTargets = {};
<h2 class="air">Project configuration</h2>
scope.executeCommands();
scope.validateData();
<div class="row-fluid">
scope.$digest();
<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>
});
</script>
<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>
<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>
</div>
</div>
<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 %}

View File

@@ -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/')),

View File

@@ -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,14 +1967,28 @@ 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()
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
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)
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,
"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")
@@ -1924,42 +1996,126 @@ if toastermain.settings.MANAGED:
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 ":" in t:
target, task = t.split(":")
else:
target = t
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()
# 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")