mirror of
https://git.yoctoproject.org/poky
synced 2026-04-04 23:02:22 +02:00
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:
committed by
Richard Purdie
parent
1a463ab98e
commit
960580cb70
@@ -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,
|
||||
|
||||
@@ -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; }
|
||||
|
||||
531
bitbake/lib/toaster/toastergui/static/js/projectapp.js
Normal file
531
bitbake/lib/toaster/toastergui/static/js/projectapp.js
Normal 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();
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">×</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…]",
|
||||
"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">×</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">×</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 %}
|
||||
|
||||
@@ -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/')),
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user