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