diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html index d58cbeaed5..3508962e67 100644 --- a/bitbake/lib/toaster/toastergui/templates/base.html +++ b/bitbake/lib/toaster/toastergui/templates/base.html @@ -1,30 +1,64 @@ {% load static %} - - Toaster Simple Explorer - - - - + + + + -
- - -{% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/basetable_bottom.html b/bitbake/lib/toaster/toastergui/templates/basetable_bottom.html new file mode 100644 index 0000000000..2a6f084929 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/basetable_bottom.html @@ -0,0 +1,60 @@ + + + + + + + + diff --git a/bitbake/lib/toaster/toastergui/templates/basetable_top.html b/bitbake/lib/toaster/toastergui/templates/basetable_top.html new file mode 100644 index 0000000000..b9277b4a3d --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/basetable_top.html @@ -0,0 +1,66 @@ + + + + + + + + + diff --git a/bitbake/lib/toaster/toastergui/templates/bpackage.html b/bitbake/lib/toaster/toastergui/templates/bpackage.html index 67fc65ca3e..3329ddae51 100644 --- a/bitbake/lib/toaster/toastergui/templates/bpackage.html +++ b/bitbake/lib/toaster/toastergui/templates/bpackage.html @@ -1,7 +1,12 @@ {% extends "basebuildpage.html" %} -{% block pagetitle %}Packages{% endblock %} -{% block pagetable %} +{% block localbreadcrumb %} +
  • Packages
  • +{% endblock %} + +{% block buildinfomain %} +{% include "basetable_top.html" %} + {% if not objects %}

    No packages were recorded for this target!

    {% else %} @@ -21,7 +26,7 @@ {% for package in objects %} - + @@ -41,4 +46,5 @@ {% endif %} +{% include "basetable_bottom.html" %} {% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/build.html b/bitbake/lib/toaster/toastergui/templates/build.html index 4fa87d5271..27ce1ccbc5 100644 --- a/bitbake/lib/toaster/toastergui/templates/build.html +++ b/bitbake/lib/toaster/toastergui/templates/build.html @@ -1,43 +1,96 @@ -{% extends "basetable.html" %} +{% extends "base.html" %} -{% block pagename %} -

    Toaster - Builds

    -{% endblock %} -{% block pagetable %} +{% load projecttags %} +{% load humanize %} + +{% block pagecontent %} +
    + + +{{build_mru}} +{% for build in mru %} +
    +
    + +{%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 %} +
    +
    + Build time: {{ build|timespent }} +
    +{%endif%}{%if build.outcome == build.IN_PROGRESS %} +
    +
    +
    +
    +
    +
    ETA: in {{build.eta|naturaltime}}
    +{%endif%} +
    +
    + +{% endfor %} + + + + +{% include "basetable_top.html" %} - {% load projecttags %}
    - - - - - - - - - - - - + + + + + + + + + + + + {% for build in objects %} - - - - - - - - - - - - + + + + + + + + + + + {% endfor %} + + +{% include "basetable_bottom.html" %} + + {% endblock %} - - diff --git a/bitbake/lib/toaster/toastergui/templates/builddashboard.html b/bitbake/lib/toaster/toastergui/templates/builddashboard.html new file mode 100644 index 0000000000..7c58cc0b25 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/builddashboard.html @@ -0,0 +1,8 @@ +{% extends "basebuildpage.html" %} +{% block localbreadcrumb %} +
  • Dashboard
  • +{% endblock %} + +{% block buildinfomain %} + +{% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/buildtime.html b/bitbake/lib/toaster/toastergui/templates/buildtime.html new file mode 100644 index 0000000000..ea84ae797c --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/buildtime.html @@ -0,0 +1,4 @@ +{% extends "basebuildpage.html" %} +{% block localbreadcrumb %} +
  • Build Time
  • +{% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/configuration.html b/bitbake/lib/toaster/toastergui/templates/configuration.html index 521620fdce..e390a95ff5 100644 --- a/bitbake/lib/toaster/toastergui/templates/configuration.html +++ b/bitbake/lib/toaster/toastergui/templates/configuration.html @@ -1,7 +1,11 @@ {% extends "basebuildpage.html" %} +{% block localbreadcrumb %} +
  • Configuration
  • +{% endblock %} -{% block pagetitle %}Configuration{% endblock %} -{% block pagetable %} +{% block buildinfomain %} + +{% include "basetable_top.html" %} @@ -19,4 +23,6 @@ {% endfor %} +{% include "basetable_bottom.html" %} + {% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/cpuusage.html b/bitbake/lib/toaster/toastergui/templates/cpuusage.html new file mode 100644 index 0000000000..02f07b7605 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/cpuusage.html @@ -0,0 +1,4 @@ +{% extends "basebuildpage.html" %} +{% block localbreadcrumb %} +
  • Cpu Usage
  • +{% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/diskio.html b/bitbake/lib/toaster/toastergui/templates/diskio.html new file mode 100644 index 0000000000..c5cef6f385 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/diskio.html @@ -0,0 +1,4 @@ +{% extends "basebuildpage.html" %} +{% block localbreadcrumb %} +
  • Disk I/O
  • +{% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/recipe.html b/bitbake/lib/toaster/toastergui/templates/recipe.html index d7f57eb9ea..87c69b8d3c 100644 --- a/bitbake/lib/toaster/toastergui/templates/recipe.html +++ b/bitbake/lib/toaster/toastergui/templates/recipe.html @@ -1,14 +1,11 @@ -{% extends "basetable.html" %} +{% extends "basebuildpage.html" %} -{% block pagename %} - -

    Toaster - Recipes for a Layer

    +{% block localbreadcrumb %} +
  • Recipes
  • {% endblock %} -{% block pagetable %} - {% load projecttags %} +{% block buildinfomain %} +{% include "basetable_top.html" %} @@ -49,4 +46,6 @@ {% endfor %} +{% include "basetable_bottom.html" %} + {% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/target.html b/bitbake/lib/toaster/toastergui/templates/target.html new file mode 100644 index 0000000000..f2d0ad461b --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/target.html @@ -0,0 +1,8 @@ +{% extends "basebuildpage.html" %} + +{% block localbreadcrumb %} +
  • Target
  • +{% endblock %} + +{% block buildinfomain %} +{% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templates/task.html b/bitbake/lib/toaster/toastergui/templates/task.html index de965ab797..6af2c51277 100644 --- a/bitbake/lib/toaster/toastergui/templates/task.html +++ b/bitbake/lib/toaster/toastergui/templates/task.html @@ -1,7 +1,13 @@ {% extends "basebuildpage.html" %} -{% block pagetitle %}Tasks{% endblock %} -{% block pagetable %} +{% block localbreadcrumb %} +
  • Tasks
  • +{% endblock %} + +{% block buildinfomain %} +{% include "basetable_top.html" %} + + {% if not objects %}

    No tasks were executed in this build!

    {% else %} @@ -64,4 +70,5 @@ {% endif %} +{% include "basetable_bottom.html" %} {% endblock %} diff --git a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py index 0c0d804c0c..5f60379932 100644 --- a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py +++ b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py @@ -24,3 +24,8 @@ register = template.Library() @register.simple_tag def time_difference(start_time, end_time): return end_time - start_time + +@register.filter(name = 'timespent') +def timespent(build_object): + tdsec = (build_object.completed_on - build_object.started_on).total_seconds() + return "%02d:%02d:%02d" % (int(tdsec/3600), int((tdsec - tdsec/ 3600)/ 60), int(tdsec) % 60) diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index b84c95f08b..f531eb0137 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py @@ -19,14 +19,34 @@ from django.conf.urls import patterns, include, url from django.views.generic import RedirectView -urlpatterns = patterns('bldviewer.views', - url(r'^builds/$', 'build', name='all-builds'), - url(r'^build/(?P\d+)/task/$', 'task', name='task'), - url(r'^build/(?P\d+)/packages/$', 'bpackage', name='bpackage'), - url(r'^build/(?P\d+)/package/(?P\d+)/files/$', 'bfile', name='bfile'), - url(r'^build/(?P\d+)/target/(?P\d+)/packages/$', 'tpackage', name='tpackage'), - url(r'^build/(?P\d+)/configuration/$', 'configuration', name='configuration'), +urlpatterns = patterns('toastergui.views', + # landing page + url(r'^builds/$', 'builds', name='all-builds'), + # build info navigation + url(r'^build/(?P\d+)$', 'builddashboard', name="builddashboard"), + + url(r'^build/(?P\d+)/tasks/$', 'tasks', name='tasks'), + url(r'^build/(?P\d+)/task/(?P\d+)$', 'task', name='task'), + + url(r'^build/(?P\d+)/recipes/$', 'recipes', name='recipes'), + url(r'^build/(?P\d+)/recipe/(?P\d+)$', 'recipe', name='recipe'), + + url(r'^build/(?P\d+)/packages/$', 'bpackage', name='packages'), + url(r'^build/(?P\d+)/package/(?P\d+)$', 'bfile', name='package'), + + # images are known as targets in the internal model + url(r'^build/(?P\d+)/target/(?P\d+)$', 'target', name='target'), + url(r'^build/(?P\d+)/target/(?P\d+)/packages$', 'tpackage', name='targetpackages'), + + url(r'^build/(?P\d+)/configuration$', 'configuration', name='configuration'), + url(r'^build/(?P\d+)/buildtime$', 'buildtime', name='buildtime'), + url(r'^build/(?P\d+)/cpuusage$', 'cpuusage', name='cpuusage'), + url(r'^build/(?P\d+)/diskio$', 'diskio', name='diskio'), + + + # urls not linked from the dashboard url(r'^layers/$', 'layer', name='all-layers'), url(r'^layerversions/(?P\d+)/recipes/.*$', 'layer_versions_recipes', name='layer_versions_recipes'), + # 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 7cb9b42379..663e03dfd2 100644 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py @@ -19,7 +19,7 @@ import operator from django.db.models import Q -from django.shortcuts import render +from django.shortcuts import render, redirect from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency from orm.models import Target_Installed_Package @@ -35,6 +35,7 @@ def _build_page_range(paginator, index = 1): except EmptyPage: page = paginator.page(paginator.num_pages) + page.page_range = [page.number] crt_range = 0 for i in range(1,5): @@ -48,22 +49,124 @@ def _build_page_range(paginator, index = 1): break return page -@cache_control(no_store=True) -def build(request): + +def _verify_parameters(g, mandatory_parameters): + miss = [] + for mp in mandatory_parameters: + if not mp in g: + miss.append(mp) + if len(miss): + return miss + return None + +def _redirect_parameters(view, g, mandatory_parameters): + import urllib + from django.core.urlresolvers import reverse + url = reverse(view) + params = {} + for i in g: + params[i] = g[i] + for i in mandatory_parameters: + if not i in params: + params[i] = mandatory_parameters[i] + + return redirect(url + "?%s" % urllib.urlencode(params)) + + +# shows the "all builds" page +def builds(request): template = 'build.html' - logs = LogMessage.objects.all() + # define here what parameters the view needs in the GET portion in order to + # be able to display something. 'count' and 'page' are mandatory for all views + # that use paginators. + mandatory_parameters = { 'count': 10, 'page' : 1}; + retval = _verify_parameters( request.GET, mandatory_parameters ) + if retval: + return _redirect_parameters( builds, request.GET, mandatory_parameters) - build_info = _build_page_range(Paginator(Build.objects.order_by("-id"), 10),request.GET.get('page', 1)) + # retrieve the objects that will be displayed in the table + build_info = _build_page_range(Paginator(Build.objects.exclude(outcome = Build.IN_PROGRESS).order_by("-id"), request.GET.get('count', 10)),request.GET.get('page', 1)) - context = {'objects': build_info, 'logs': logs , - 'hideshowcols' : [ - {'name': 'Output', 'order':10}, - {'name': 'Log', 'order':11}, + # build view-specific information; this is rendered specifically in the builds page + build_mru = Build.objects.order_by("-started_on")[:3] + for b in [ x for x in build_mru if x.outcome == Build.IN_PROGRESS ]: + tf = Task.objects.filter(build = b) + b.completeper = tf.exclude(order__isnull=True).count()*100/tf.count() + from django.utils import timezone + b.eta = timezone.now() + ((timezone.now() - b.started_on)*100/b.completeper) + + # send the data to the template + context = { + # specific info for + 'mru' : build_mru, + # TODO: common objects for all table views, adapt as needed + 'objects' : build_info, + 'tablecols' : [ + {'name': 'Target ', 'clclass': 'target',}, + {'name': 'Machine ', 'clclass': 'machine'}, + {'name': 'Completed on ', 'clclass': 'completed_on'}, + {'name': 'Failed tasks ', 'clclass': 'failed_tasks'}, + {'name': 'Errors ', 'clclass': 'errors_no'}, + {'name': 'Warnings', 'clclass': 'warnings_no'}, + {'name': 'Output ', 'clclass': 'output'}, + {'name': 'Started on ', 'clclass': 'started_on', 'hidden' : 1}, + {'name': 'Time ', 'clclass': 'time', 'hidden' : 1}, + {'name': 'Output', 'clclass': 'output'}, + {'name': 'Log', 'clclass': 'log', 'hidden': 1}, ]} return render(request, template, context) +# build dashboard for a single build, coming in as argument +def builddashboard(request, build_id): + template = "builddashboard.html" + if Build.objects.filter(pk=build_id).count() == 0 : + return redirect(builds) + context = { + 'build' : Build.objects.filter(pk=build_id)[0], + } + return render(request, template, context) + +def task(request, build_id, task_id): + template = "singletask.html" + if Build.objects.filter(pk=build_id).count() == 0 : + return redirect(builds) + context = { + 'build' : Build.objects.filter(pk=build_id)[0], + } + return render(request, template, context) + +def recipe(request, build_id, recipe_id): + template = "recipe.html" + if Recipe.objects.filter(pk=recipe_id).count() == 0 : + return redirect(builds) + context = { + 'build' : Build.objects.filter(pk=build_id)[0], + 'object' : Recipe.objects.filter(pk=recipe_id)[0], + } + return render(request, template, context) + +def package(request, build_id, package_id): + template = "singlepackage.html" + if Build.objects.filter(pk=build_id).count() == 0 : + return redirect(builds) + context = { + 'build' : Build.objects.filter(pk=build_id)[0], + } + return render(request, template, context) + +def target(request, build_id, target_id): + template = "target.html" + if Build.objects.filter(pk=build_id).count() == 0 : + return redirect(builds) + context = { + 'build' : Build.objects.filter(pk=build_id)[0], + } + return render(request, template, context) + + + def _find_task_revdep(task): tp = [] for p in Task_Dependency.objects.filter(depends_on=task): @@ -81,7 +184,7 @@ def _find_task_provider(task): return trc return None -def task(request, build_id): +def tasks(request, build_id): template = 'task.html' tasks = _build_page_range(Paginator(Task.objects.filter(build=build_id), 100),request.GET.get('page', 1)) @@ -94,12 +197,52 @@ def task(request, build_id): return render(request, template, context) +def recipes(request, build_id): + template = 'recipe.html' + + recipes = _build_page_range(Paginator(Recipe.objects.filter(build_recipe=build_id), 100),request.GET.get('page', 1)) + + context = {'build': Build.objects.filter(pk=build_id)[0], 'objects': recipes} + + return render(request, template, context) + + def configuration(request, build_id): template = 'configuration.html' variables = _build_page_range(Paginator(Variable.objects.filter(build=build_id), 50), request.GET.get('page', 1)) context = {'build': Build.objects.filter(pk=build_id)[0], 'objects' : variables} return render(request, template, context) +def buildtime(request, build_id): + template = "buildtime.html" + if Build.objects.filter(pk=build_id).count() == 0 : + return redirect(builds) + context = { + 'build' : Build.objects.filter(pk=build_id)[0], + } + return render(request, template, context) + +def cpuusage(request, build_id): + template = "cpuusage.html" + if Build.objects.filter(pk=build_id).count() == 0 : + return redirect(builds) + context = { + 'build' : Build.objects.filter(pk=build_id)[0], + } + return render(request, template, context) + +def diskio(request, build_id): + template = "diskio.html" + if Build.objects.filter(pk=build_id).count() == 0 : + return redirect(builds) + context = { + 'build' : Build.objects.filter(pk=build_id)[0], + } + return render(request, template, context) + + + + def bpackage(request, build_id): template = 'bpackage.html' packages = Package.objects.filter(build = build_id) @@ -227,8 +370,8 @@ def model_explorer(request, model_name): response_data['count'] = queryset.count() else: response_data['count'] = 0 - response_data['list'] = serializers.serialize('json', queryset) +# response_data = serializers.serialize('json', queryset) return HttpResponse(json.dumps(response_data), content_type='application/json') diff --git a/bitbake/lib/toaster/toastermain/settings.py b/bitbake/lib/toaster/toastermain/settings.py index b76218b959..679035e887 100644 --- a/bitbake/lib/toaster/toastermain/settings.py +++ b/bitbake/lib/toaster/toastermain/settings.py @@ -133,6 +133,15 @@ TEMPLATE_DIRS = ( # Don't forget to use absolute paths, not relative paths. ) +TEMPLATE_CONTEXT_PROCESSORS = ('django.contrib.auth.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', + 'django.core.context_processors.media', + 'django.core.context_processors.static', + 'django.core.context_processors.tz', + 'django.contrib.messages.context_processors.messages', + "django.core.context_processors.request") + INSTALLED_APPS = ( #'django.contrib.auth', #'django.contrib.contenttypes', @@ -144,6 +153,7 @@ INSTALLED_APPS = ( # 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', + 'django.contrib.humanize', 'orm', 'toastermain', 'toastergui',
    {{package.name}} ({{package.filelist_bpackage.count}} files){{package.name}} ({{package.filelist_bpackage.count}} files) {{package.version}}-{{package.revision}} {%if package.recipe%}{{package.recipe.name}}{{package.package_name}}{%endif%}
    OutcomeStarted OnCompleted OnTargetMachineTimeErrorsWarningsOutputLogBitbake VersionBuild Name Outcome Target Machine Started on Completed on Failed tasks Errors Warnings Time Log Output
    {{build.get_outcome_display}}{{build.started_on}}{{build.completed_on}}{% for t in build.target_set.all %}{%if t.is_image %}{% endif %}{{t.target}}{% if t.is_image %}{% endif %}
    {% endfor %}
    {{build.machine}}{% time_difference build.started_on build.completed_on %}{{build.errors_no}}:{% if build.errors_no %}{% for error in logs %}{% if error.build == build %}{% if error.level == 2 %}

    {{error.message}}

    {% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}
    {{build.warnings_no}}:{% if build.warnings_no %}{% for warning in logs %}{% if warning.build == build %}{% if warning.level == 1 %}

    {{warning.message}}

    {% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}
    {% if build.outcome == 0 %}{% for t in build.target_set.all %}{% if t.is_image %}{{build.image_fstypes}}{% endif %}{% endfor %}{% endif %}{{build.cooker_log_path}}{{build.bitbake_version}}{{build.build_name}}{%if build.outcome == build.SUCCEEDED%}{%elif build.outcome == build.FAILED%}{%else%}{%endif%}{% for t in build.target_set.all %}{%if t.is_image %}{% endif %}{{t.target}}{% if t.is_image %}{% endif %}
    {% endfor %}
    {{build.machine}}{{build.started_on}}{{build.completed_on}}{% 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%}{{build|timespent}}{{build.log}}{% if build.outcome == 0 %}{% for t in build.target_set.all %}{% if t.is_image %}{{build.image_fstypes}}{% endif %}{% endfor %}{% endif %}
    Name{{variable.variable_value}}