+ + + +
+ +
+ - {% if builds|length > 0 or buildrequests|length > 0 %} -

Recent Builds

+ -
- {% for br in buildrequests %} -
-
-
- - {{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}}) - + + + + + + + +
+
+
+ + +
+

+ + View all targets + + {% if completedbuilds.count %} + | View all project builds ({{completedbuilds.count}}) + {% endif %} +

+
+
+ +

Latest builds

+ +
+
+ + +
{[t.target]}
+
+
+ {[e.type]}:
{[e.msg]}
+
+
+
+ +
{[t.target]}
+
Build queued + +
+ +
+ +
{[t.target]}
+
+ Creating build
-
- {{br.0.get_state_display}} + + + +
{[t.target]}
+
+ Build deleted
-
-{% if br.state == br.REQ_FAILED%} - {% for bre in br.0.brerror_set.all %} {{bre.errmsg}} ({{bre.errtype}})

{{bre.traceback}}{%endfor%} -{%endif%} + + + +
{[t.target]}
+
+
Build starting shortly
+
+ +
+
+
+
+
+
ETA: at {[b.eta]}
+
+ + +
+ {[b.completed_on|date:'dd/MM/yy HH:mm']} +
+
{[b.errors.len]}
+
{[b.warnings.len]}
+
Build time: {[b.build_time|date:"HH:mm"]} + -
-
+
+
+
+
+
+
+
- {% endfor %} +

Project configuration

+ +
+ +
+ +
+ + +
+

+ Project layers ({[layers.length]}) + +

+
+ You need to add some layers +

+ You can: +

+ Or type a layer name below. +

+
+
+ + + {% csrf_token %} +
+

View all layers | Import layer

+
+ +
+

+ Targets + +

+
+ + + {% csrf_token %} +
+

View all targets

+
+

+ Most built targets +

+
    +
  • + +
  • +
+ +
+
- - {% for build in builds %} -
-
- - {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} -
- {% if build.errors_no %} - {{build.errors_no}} error{{build.errors_no|pluralize}} - {% endif %} -
-
- {% if build.warnings_no %} - {{build.warnings_no}} warning{{build.warnings_no|pluralize}} - {% endif %} -
- - {%endif%}{%if build.outcome == build.IN_PROGRESS %} -
-
-
-
-
-
ETA: in {{build.eta|naturaltime}}
- {%endif%} + +
+

+ Project machine + +

+

+ {[machine.name]} +

+ +
- {% endfor %} - - {%endif%} +
-

Project configuration

-
+

Project details

-
-

- Add layers - -

-
-
- - -
- - - -

Import your layer | View all layers

-
- -

- Added layers - {{project.projectlayer_set.count}} - -

- -
+ +
+ +
+ + +
+

Project name

+

+ {[project.name]} + +

+ -
-

- Add targets - -

-
-
- - -
- -

View all targets

-
-

- Added targets - {{project.projecttarget_set.count}} -

- -
-
- -

- Project machine - -

-

{{machine}} - -

-
-
- Machine changes have a big impact on build outcome. - You cannot really compare the builds for the new machine with the previous ones. -
-
- - - Cancel -
-

View all machines

-
- +

+ Release + +

+

+ {[project.release.name]} + +

+ +
-
+ +
-
+ + + + + -

Project details

- -
-

Project name

-

- {{project.name}} - -

-

Project owner

-

- {{puser.username}} - -

-

Owner's email

-

- {{puser.email}} - -

-

Yocto Project version

-

- {{project.release.name}} - {{project.release.description}} - -

-
{% endblock %} diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index a9c05922c2..e642e32036 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py @@ -81,6 +81,8 @@ urlpatterns = patterns('toastergui.views', url(r'^xhr_projectbuild/(?P\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'), url(r'^xhr_projectedit/(?P\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 3fde3c9a62..5fe4a9d869 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") -- cgit v1.2.3-54-g00ecf