=== modified file 'dashboard_app/__init__.py'
@@ -20,4 +20,4 @@
Dashboard Application (package)
"""
-__version__ = (0, 5, 2, "final", 0)
+__version__ = (0, 6, 0, "beta", 1)
=== added file 'dashboard_app/bread_crumbs.py'
@@ -0,0 +1,86 @@
+# Copyright (C) 2010 Linaro Limited
+#
+# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
+#
+# This file is part of Launch Control.
+#
+# Launch Control is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# Launch Control 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 Affero General Public License
+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
+
+from django.core.urlresolvers import reverse
+import logging
+
+
+class BreadCrumb(object):
+
+ def __init__(self, name, parent=None, needs=None):
+ self.name = name
+ self.view = None
+ self.parent = parent
+ self.needs = needs or []
+
+ def __repr__(self):
+ return "<BreadCrumb name=%r view=%r parent=%r>" % (
+ self.name, self.view, self.parent)
+
+ def __call__(self, view):
+ self.view = view
+ view._bread_crumb = self
+ return view
+
+ def get_name(self, kwargs):
+ try:
+ return self.name.format(**kwargs)
+ except:
+ logging.exception("Unable to construct breadcrumb name for view %r", self.view)
+ raise
+
+ def get_absolute_url(self, kwargs):
+ try:
+ return reverse(self.view, args=[kwargs[name] for name in self.needs])
+ except:
+ logging.exception("Unable to construct breadcrumb URL for view %r", self.view)
+ raise
+
+
+class LiveBreadCrumb(object):
+
+ def __init__(self, bread_crumb, kwargs):
+ self.bread_crumb = bread_crumb
+ self.kwargs = kwargs
+
+ def get_name(self):
+ return self.bread_crumb.get_name(self.kwargs)
+
+ def get_absolute_url(self):
+ return self.bread_crumb.get_absolute_url(self.kwargs)
+
+
+class BreadCrumbTrail(object):
+
+ def __init__(self, bread_crumb_list, kwargs):
+ self.bread_crumb_list = bread_crumb_list
+ self.kwargs = kwargs
+
+ def __iter__(self):
+ for bread_crumb in self.bread_crumb_list:
+ yield LiveBreadCrumb(bread_crumb, self.kwargs)
+
+ @classmethod
+ def leading_to(cls, view, **kwargs):
+ lst = []
+ while view is not None:
+ lst.append(view._bread_crumb)
+ view = view._bread_crumb.parent
+ lst.reverse()
+ return cls(lst, kwargs or {})
+
=== modified file 'dashboard_app/dataview.py'
@@ -110,7 +110,13 @@
raise LookupError("Specified data view has no SQL implementation "
"for current database")
# Replace SQL aruments with django placeholders (connection agnostic)
- sql = query.sql_template.format(**dict([(arg_name, "%s") for arg_name in query.argument_list]))
+ template = query.sql_template
+ template = template.replace("%", "%%")
+ # template = template.replace("{", "{{").replace("}", "}}")
+ sql = template.format(
+ **dict([
+ (arg_name, "%s")
+ for arg_name in query.argument_list]))
# Construct argument list using defaults for missing values
sql_args = [
arguments.get(arg_name, self.lookup_argument(arg_name).default)
=== modified file 'dashboard_app/extension.py'
@@ -1,3 +1,4 @@
+import os
from lava_server.extension import LavaServerExtension
@@ -13,7 +14,7 @@
@property
def main_view_name(self):
- return "dashboard_app.views.bundle_stream_list"
+ return "dashboard_app.views.index"
@property
def description(self):
@@ -25,19 +26,33 @@
import dashboard_app
return versiontools.format_version(dashboard_app.__version__)
- def contribute_to_settings(self, settings):
- super(DashboardExtension, self).contribute_to_settings(settings)
- settings['INSTALLED_APPS'].extend([
+ def contribute_to_settings(self, settings_module):
+ super(DashboardExtension, self).contribute_to_settings(settings_module)
+ settings_module['INSTALLED_APPS'].extend([
"linaro_django_pagination",
"south",
])
- settings['MIDDLEWARE_CLASSES'].append(
+ settings_module['MIDDLEWARE_CLASSES'].append(
'linaro_django_pagination.middleware.PaginationMiddleware')
- settings['RESTRUCTUREDTEXT_FILTER_SETTINGS'] = {
- "initial_header_level": 4}
+ root_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
+ settings_module['DATAVIEW_DIRS'] = [
+ os.path.join(root_dir, 'examples/views'),
+ os.path.join(root_dir, 'production/views')]
+ settings_module['DATAREPORT_DIRS'] = [
+ os.path.join(root_dir, 'examples/reports'),
+ os.path.join(root_dir, 'production/reports')]
def contribute_to_settings_ex(self, settings_module, settings_object):
settings_module['DATAVIEW_DIRS'] = settings_object._settings.get(
"DATAVIEW_DIRS", [])
settings_module['DATAREPORT_DIRS'] = settings_object._settings.get(
"DATAREPORT_DIRS", [])
+
+ # Enable constrained dataview database if requested
+ if settings_object._settings.get("use_dataview_database"):
+ # Copy everything from the default database and append _dataview to user
+ # name. The rest is out of scope (making sure it's actually setup
+ # properly, having permissions to login, permissions to view proper data)
+ settings_module['DATABASES']['dataview'] = dict(settings_module['DATABASES']['default'])
+ settings_module['DATABASES']['dataview']['USER'] += "_dataview"
+
=== modified file 'dashboard_app/managers.py'
@@ -31,6 +31,7 @@
bundle_stream=bundle_stream,
uploaded_by=uploaded_by,
content_filename=content_filename)
+ # XXX: this _can_ fail -- if content_sha1 is a duplicate
logging.debug("Saving bundle object (this is safe so far)")
bundle.save()
try:
=== modified file 'dashboard_app/models.py'
@@ -24,6 +24,7 @@
import hashlib
import logging
import os
+import simplejson
import traceback
from django.contrib.auth.models import User
@@ -43,6 +44,10 @@
from dashboard_app.repositories import RepositoryItem
from dashboard_app.repositories.data_report import DataReportRepository
+# Fix some django issues we ran into
+from dashboard_app.patches import patch
+patch()
+
def _help_max_length(max_length):
return ungettext(
@@ -184,7 +189,7 @@
@models.permalink
def get_absolute_url(self):
- return ("dashboard_app.test_run_list", [self.pathname])
+ return ("dashboard_app.views.bundle_list", [self.pathname])
def get_test_run_count(self):
return TestRun.objects.filter(bundle__bundle_stream=self).count()
@@ -341,7 +346,7 @@
@models.permalink
def get_absolute_url(self):
- return ("dashboard_app.bundle.detail", [self.pk])
+ return ("dashboard_app.views.bundle_detail", [self.bundle_stream.pathname, self.content_sha1])
def save(self, *args, **kwargs):
if self.content:
@@ -413,6 +418,41 @@
for attachment in test_run.attachments.all():
attachment.content.delete(save=save)
+ def get_sanitized_bundle(self):
+ self.content.open()
+ try:
+ return SanitizedBundle(self.content)
+ finally:
+ self.content.close()
+
+
+class SanitizedBundle(object):
+
+ def __init__(self, stream):
+ try:
+ self.bundle_json = simplejson.load(stream)
+ self.deserialization_error = None
+ except simplejson.JSONDeserializationError as ex:
+ self.bundle_json = None
+ self.deserialization_error = ex
+ self.did_remove_attachments = False
+ self._sanitize()
+
+ def get_human_readable_json(self):
+ return simplejson.dumps(self.bundle_json, indent=4)
+
+ def _sanitize(self):
+ for test_run in self.bundle_json.get("test_runs", []):
+ attachments = test_run.get("attachments")
+ if isinstance(attachments, list):
+ for attachment in attachments:
+ attachment["content"] = None
+ self.did_remove_attachments = True
+ elif isinstance(attachments, dict):
+ for name in attachments:
+ attachments[name] = None
+ self.did_remove_attachments = True
+
class BundleDeserializationError(models.Model):
"""
@@ -466,7 +506,18 @@
@models.permalink
def get_absolute_url(self):
- return ('dashboard_app.test.detail', [self.test_id])
+ return ('dashboard_app.views.test_detail', [self.test_id])
+
+ def count_results_without_test_case(self):
+ return TestResult.objects.filter(
+ test_run__test=self,
+ test_case=None).count()
+
+ def count_failures_without_test_case(self):
+ return TestResult.objects.filter(
+ test_run__test=self,
+ test_case=None,
+ result=TestResult.RESULT_FAIL).count()
class TestCase(models.Model):
@@ -511,6 +562,9 @@
def get_absolute_url(self):
return ("dashboard_app.test_case.details", [self.test.test_id, self.test_case_id])
+ def count_failures(self):
+ return self.test_results.filter(result=TestResult.RESULT_FAIL).count()
+
class SoftwareSource(models.Model):
"""
@@ -675,7 +729,9 @@
@models.permalink
def get_absolute_url(self):
return ("dashboard_app.views.test_run_detail",
- [self.analyzer_assigned_uuid])
+ [self.bundle.bundle_stream.pathname,
+ self.bundle.content_sha1,
+ self.analyzer_assigned_uuid])
def get_summary_results(self):
stats = self.test_results.values('result').annotate(
@@ -723,10 +779,6 @@
def __unicode__(self):
return self.content_filename
- @models.permalink
- def get_absolute_url(self):
- return ("dashboard_app.views.attachment_detail", [self.pk])
-
def get_content_if_possible(self, mirror=False):
if self.content:
self.content.open()
@@ -752,6 +804,25 @@
data = None
return data
+ def is_test_run_attachment(self):
+ if (self.content_type.app_label == 'dashboard_app' and
+ self.content_type.model == 'testrun'):
+ return True
+
+ @property
+ def test_run(self):
+ if self.is_test_run_attachment():
+ return self.content_object
+
+ @models.permalink
+ def get_absolute_url(self):
+ if self.is_test_run_attachment():
+ return ("dashboard_app.views.attachment_detail",
+ [self.test_run.bundle.bundle_stream.pathname,
+ self.test_run.bundle.content_sha1,
+ self.test_run.analyzer_assigned_uuid,
+ self.pk])
+
class TestResult(models.Model):
"""
@@ -888,8 +959,12 @@
@models.permalink
def get_absolute_url(self):
- return ("dashboard_app.views.test_result_detail",
- [self.pk])
+ return ("dashboard_app.views.test_result_detail", [
+ self.test_run.bundle.bundle_stream.pathname,
+ self.test_run.bundle.content_sha1,
+ self.test_run.analyzer_assigned_uuid,
+ self.relative_index,
+ ])
def related_attachment_available(self):
"""
@@ -920,7 +995,7 @@
def __init__(self, **kwargs):
self._html = None
- self.__dict__.update(kwargs)
+ self._data = kwargs
def _get_raw_html(self):
pathname = os.path.join(self.base_path, self.path)
@@ -935,7 +1010,9 @@
return Template(self._get_raw_html())
def _get_html_template_context(self):
- return Context({"API_URL": reverse("dashboard_app.views.dashboard_xml_rpc_handler")})
+ return Context({
+ "API_URL": reverse("dashboard_app.views.dashboard_xml_rpc_handler")
+ })
def get_html(self):
from django.conf import settings
@@ -952,3 +1029,23 @@
@models.permalink
def get_absolute_url(self):
return ("dashboard_app.views.report_detail", [self.name])
+
+ @property
+ def title(self):
+ return self._data['title']
+
+ @property
+ def path(self):
+ return self._data['path']
+
+ @property
+ def name(self):
+ return self._data['name']
+
+ @property
+ def bug_report_url(self):
+ return self._data.get('bug_report_url')
+
+ @property
+ def author(self):
+ return self._data.get('author')
=== added file 'dashboard_app/patches.py'
@@ -0,0 +1,112 @@
+"""
+Patches for django bugs that affect this package
+"""
+
+class PatchDjangoTicket1476(object):
+ """
+ Patch for bug http://code.djangoproject.com/ticket/1476
+ """
+
+ @classmethod
+ def apply_if_needed(patch):
+ import django
+ if django.VERSION[0:3] <= (1, 2, 4):
+ patch.apply()
+
+ @classmethod
+ def apply(patch):
+ from django.utils.decorators import method_decorator
+ from django.views.decorators.csrf import csrf_protect
+
+ @method_decorator(csrf_protect)
+ def __call__(self, request, *args, **kwargs):
+ """
+ Main method that does all the hard work, conforming to the Django view
+ interface.
+ """
+ if 'extra_context' in kwargs:
+ self.extra_context.update(kwargs['extra_context'])
+ current_step = self.determine_step(request, *args, **kwargs)
+ self.parse_params(request, *args, **kwargs)
+
+ # Sanity check.
+ if current_step >= self.num_steps():
+ raise Http404('Step %s does not exist' % current_step)
+
+ # Process the current step. If it's valid, go to the next step or call
+ # done(), depending on whether any steps remain.
+ if request.method == 'POST':
+ form = self.get_form(current_step, request.POST)
+ else:
+ form = self.get_form(current_step)
+
+ if form.is_valid():
+ # Validate all the forms. If any of them fail validation, that
+ # must mean the validator relied on some other input, such as
+ # an external Web site.
+
+ # It is also possible that validation might fail under certain
+ # attack situations: an attacker might be able to bypass previous
+ # stages, and generate correct security hashes for all the
+ # skipped stages by virtue of:
+ # 1) having filled out an identical form which doesn't have the
+ # validation (and does something different at the end),
+ # 2) or having filled out a previous version of the same form
+ # which had some validation missing,
+ # 3) or previously having filled out the form when they had
+ # more privileges than they do now.
+ #
+ # Since the hashes only take into account values, and not other
+ # other validation the form might do, we must re-do validation
+ # now for security reasons.
+ previous_form_list = [self.get_form(i, request.POST) for i in range(current_step)]
+
+ for i, f in enumerate(previous_form_list):
+ if request.POST.get("hash_%d" % i, '') != self.security_hash(request, f):
+ return self.render_hash_failure(request, i)
+
+ if not f.is_valid():
+ return self.render_revalidation_failure(request, i, f)
+ else:
+ self.process_step(request, f, i)
+
+ # Now progress to processing this step:
+ self.process_step(request, form, current_step)
+ next_step = current_step + 1
+
+
+ if next_step == self.num_steps():
+ return self.done(request, previous_form_list + [form])
+ else:
+ form = self.get_form(next_step)
+ self.step = current_step = next_step
+
+ return self.render(form, request, current_step)
+
+ from django.contrib.formtools.wizard import FormWizard
+ FormWizard.__call__ = __call__
+
+
+class PatchDjangoTicket15155(object):
+ """
+ Patch for bug http://code.djangoproject.com/ticket/15155
+ """
+
+ PROPER_FORMAT = r'(?<!%)%s'
+
+ @classmethod
+ def apply_if_needed(patch):
+ from django.db.backends.sqlite3 import base
+ if base.FORMAT_QMARK_REGEX != patch.PROPER_FORMAT:
+ patch.apply()
+
+ @classmethod
+ def apply(cls):
+ from django.db.backends.sqlite3 import base
+ import re
+ base.FORMAT_QMARK_REGEX = re.compile(cls.PROPER_FORMAT)
+
+
+def patch():
+ PatchDjangoTicket1476.apply_if_needed()
+ PatchDjangoTicket15155.apply_if_needed()
=== added file 'dashboard_app/static/css/demo_table_jui.css'
@@ -0,0 +1,516 @@
+/*
+ * File: demo_table_jui.css
+ * CVS: $Id$
+ * Description: CSS descriptions for DataTables demo pages
+ * Author: Allan Jardine
+ * Created: Tue May 12 06:47:22 BST 2009
+ * Modified: $Date$ by $Author$
+ * Language: CSS
+ * Project: DataTables
+ *
+ * Copyright 2009 Allan Jardine. All Rights Reserved.
+ *
+ * ***************************************************************************
+ * DESCRIPTION
+ *
+ * The styles given here are suitable for the demos that are used with the standard DataTables
+ * distribution (see www.datatables.net). You will most likely wish to modify these styles to
+ * meet the layout requirements of your site.
+ *
+ * Common issues:
+ * 'full_numbers' pagination - I use an extra selector on the body tag to ensure that there is
+ * no conflict between the two pagination types. If you want to use full_numbers pagination
+ * ensure that you either have "example_alt_pagination" as a body class name, or better yet,
+ * modify that selector.
+ * Note that the path used for Images is relative. All images are by default located in
+ * ../images/ - relative to this CSS file.
+ */
+
+
+/*
+ * jQuery UI specific styling
+ */
+
+.paging_two_button .ui-button {
+ float: left;
+ cursor: pointer;
+ * cursor: hand;
+}
+
+.paging_full_numbers .ui-button {
+ padding: 2px 6px;
+ margin: 0;
+ cursor: pointer;
+ * cursor: hand;
+}
+
+.dataTables_paginate .ui-button {
+ margin-right: -0.1em !important;
+}
+
+.paging_full_numbers {
+ width: 350px !important;
+}
+
+.dataTables_wrapper .ui-toolbar {
+ padding: 5px;
+}
+
+.dataTables_paginate {
+ width: auto;
+}
+
+.dataTables_info {
+ padding-top: 3px;
+}
+
+table.display thead th {
+ padding: 3px 0px 3px 10px;
+ cursor: pointer;
+ * cursor: hand;
+}
+
+div.dataTables_wrapper .ui-widget-header {
+ font-weight: normal;
+}
+
+
+/*
+ * Sort arrow icon positioning
+ */
+table.display thead th div.DataTables_sort_wrapper {
+ position: relative;
+ padding-right: 20px;
+ padding-right: 20px;
+}
+
+table.display thead th div.DataTables_sort_wrapper span {
+ position: absolute;
+ top: 50%;
+ margin-top: -8px;
+ right: 0;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *
+ * Everything below this line is the same as demo_table.css. This file is
+ * required for 'cleanliness' of the markup
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables features
+ */
+
+.dataTables_wrapper {
+ position: relative;
+ min-height: 302px;
+ _height: 302px;
+ clear: both;
+}
+
+.dataTables_processing {
+ position: absolute;
+ top: 0px;
+ left: 50%;
+ width: 250px;
+ margin-left: -125px;
+ border: 1px solid #ddd;
+ text-align: center;
+ color: #999;
+ font-size: 11px;
+ padding: 2px 0;
+}
+
+.dataTables_length {
+ width: 40%;
+ float: left;
+}
+
+.dataTables_filter {
+ width: 50%;
+ float: right;
+ text-align: right;
+}
+
+.dataTables_info {
+ width: 50%;
+ float: left;
+}
+
+.dataTables_paginate {
+ float: right;
+ text-align: right;
+}
+
+/* Pagination nested */
+.paginate_disabled_previous, .paginate_enabled_previous, .paginate_disabled_next, .paginate_enabled_next {
+ height: 19px;
+ width: 19px;
+ margin-left: 3px;
+ float: left;
+}
+
+.paginate_disabled_previous {
+ background-image: url('../images/back_disabled.jpg');
+}
+
+.paginate_enabled_previous {
+ background-image: url('../images/back_enabled.jpg');
+}
+
+.paginate_disabled_next {
+ background-image: url('../images/forward_disabled.jpg');
+}
+
+.paginate_enabled_next {
+ background-image: url('../images/forward_enabled.jpg');
+}
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables display
+ */
+table.display {
+ margin: 0 auto;
+ width: 100%;
+ clear: both;
+ border-collapse: collapse;
+}
+
+table.display tfoot th {
+ padding: 3px 0px 3px 10px;
+ font-weight: bold;
+ font-weight: normal;
+}
+
+table.display tr.heading2 td {
+ border-bottom: 1px solid #aaa;
+}
+
+table.display td {
+ padding: 3px 10px;
+}
+
+table.display td.center {
+ text-align: center;
+}
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables sorting
+ */
+
+.sorting_asc {
+ background: url('../images/sort_asc.png') no-repeat center right;
+}
+
+.sorting_desc {
+ background: url('../images/sort_desc.png') no-repeat center right;
+}
+
+.sorting {
+ background: url('../images/sort_both.png') no-repeat center right;
+}
+
+.sorting_asc_disabled {
+ background: url('../images/sort_asc_disabled.png') no-repeat center right;
+}
+
+.sorting_desc_disabled {
+ background: url('../images/sort_desc_disabled.png') no-repeat center right;
+}
+
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables row classes
+ */
+table.display tr.odd.gradeA {
+ background-color: #ddffdd;
+}
+
+table.display tr.even.gradeA {
+ background-color: #eeffee;
+}
+
+
+
+
+table.display tr.odd.gradeA {
+ background-color: #ddffdd;
+}
+
+table.display tr.even.gradeA {
+ background-color: #eeffee;
+}
+
+table.display tr.odd.gradeC {
+ background-color: #ddddff;
+}
+
+table.display tr.even.gradeC {
+ background-color: #eeeeff;
+}
+
+table.display tr.odd.gradeX {
+ background-color: #ffdddd;
+}
+
+table.display tr.even.gradeX {
+ background-color: #ffeeee;
+}
+
+table.display tr.odd.gradeU {
+ background-color: #ddd;
+}
+
+table.display tr.even.gradeU {
+ background-color: #eee;
+}
+
+
+tr.odd {
+ background-color: #E2E4FF;
+}
+
+tr.even {
+ background-color: white;
+}
+
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Misc
+ */
+.dataTables_scroll {
+ clear: both;
+}
+
+.top, .bottom {
+ padding: 15px;
+ background-color: #F5F5F5;
+ border: 1px solid #CCCCCC;
+}
+
+.top .dataTables_info {
+ float: none;
+}
+
+.clear {
+ clear: both;
+}
+
+.dataTables_empty {
+ text-align: center;
+}
+
+tfoot input {
+ margin: 0.5em 0;
+ width: 100%;
+ color: #444;
+}
+
+tfoot input.search_init {
+ color: #999;
+}
+
+td.group {
+ background-color: #d1cfd0;
+ border-bottom: 2px solid #A19B9E;
+ border-top: 2px solid #A19B9E;
+}
+
+td.details {
+ background-color: #d1cfd0;
+ border: 2px solid #A19B9E;
+}
+
+
+.example_alt_pagination div.dataTables_info {
+ width: 40%;
+}
+
+.paging_full_numbers span.paginate_button,
+ .paging_full_numbers span.paginate_active {
+ border: 1px solid #aaa;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ padding: 2px 5px;
+ margin: 0 3px;
+ cursor: pointer;
+ *cursor: hand;
+}
+
+.paging_full_numbers span.paginate_button {
+ background-color: #ddd;
+}
+
+.paging_full_numbers span.paginate_button:hover {
+ background-color: #ccc;
+}
+
+.paging_full_numbers span.paginate_active {
+ background-color: #99B3FF;
+}
+
+table.display tr.even.row_selected td {
+ background-color: #B0BED9;
+}
+
+table.display tr.odd.row_selected td {
+ background-color: #9FAFD1;
+}
+
+
+/*
+ * Sorting classes for columns
+ */
+/* For the standard odd/even */
+tr.odd td.sorting_1 {
+ background-color: #D3D6FF;
+}
+
+tr.odd td.sorting_2 {
+ background-color: #DADCFF;
+}
+
+tr.odd td.sorting_3 {
+ background-color: #E0E2FF;
+}
+
+tr.even td.sorting_1 {
+ background-color: #EAEBFF;
+}
+
+tr.even td.sorting_2 {
+ background-color: #F2F3FF;
+}
+
+tr.even td.sorting_3 {
+ background-color: #F9F9FF;
+}
+
+
+/* For the Conditional-CSS grading rows */
+/*
+ Colour calculations (based off the main row colours)
+ Level 1:
+ dd > c4
+ ee > d5
+ Level 2:
+ dd > d1
+ ee > e2
+ */
+tr.odd.gradeA td.sorting_1 {
+ background-color: #c4ffc4;
+}
+
+tr.odd.gradeA td.sorting_2 {
+ background-color: #d1ffd1;
+}
+
+tr.odd.gradeA td.sorting_3 {
+ background-color: #d1ffd1;
+}
+
+tr.even.gradeA td.sorting_1 {
+ background-color: #d5ffd5;
+}
+
+tr.even.gradeA td.sorting_2 {
+ background-color: #e2ffe2;
+}
+
+tr.even.gradeA td.sorting_3 {
+ background-color: #e2ffe2;
+}
+
+tr.odd.gradeC td.sorting_1 {
+ background-color: #c4c4ff;
+}
+
+tr.odd.gradeC td.sorting_2 {
+ background-color: #d1d1ff;
+}
+
+tr.odd.gradeC td.sorting_3 {
+ background-color: #d1d1ff;
+}
+
+tr.even.gradeC td.sorting_1 {
+ background-color: #d5d5ff;
+}
+
+tr.even.gradeC td.sorting_2 {
+ background-color: #e2e2ff;
+}
+
+tr.even.gradeC td.sorting_3 {
+ background-color: #e2e2ff;
+}
+
+tr.odd.gradeX td.sorting_1 {
+ background-color: #ffc4c4;
+}
+
+tr.odd.gradeX td.sorting_2 {
+ background-color: #ffd1d1;
+}
+
+tr.odd.gradeX td.sorting_3 {
+ background-color: #ffd1d1;
+}
+
+tr.even.gradeX td.sorting_1 {
+ background-color: #ffd5d5;
+}
+
+tr.even.gradeX td.sorting_2 {
+ background-color: #ffe2e2;
+}
+
+tr.even.gradeX td.sorting_3 {
+ background-color: #ffe2e2;
+}
+
+tr.odd.gradeU td.sorting_1 {
+ background-color: #c4c4c4;
+}
+
+tr.odd.gradeU td.sorting_2 {
+ background-color: #d1d1d1;
+}
+
+tr.odd.gradeU td.sorting_3 {
+ background-color: #d1d1d1;
+}
+
+tr.even.gradeU td.sorting_1 {
+ background-color: #d5d5d5;
+}
+
+tr.even.gradeU td.sorting_2 {
+ background-color: #e2e2e2;
+}
+
+tr.even.gradeU td.sorting_3 {
+ background-color: #e2e2e2;
+}
+
+/*
+ * Row highlighting example
+ */
+.ex_highlight #example tbody tr.even:hover, #example tbody tr.even td.highlighted {
+ background-color: #ECFFB3;
+}
+
+.ex_highlight #example tbody tr.odd:hover, #example tbody tr.odd td.highlighted {
+ background-color: #E6FF99;
+}
=== added file 'dashboard_app/static/images/details_close.png'
Binary files dashboard_app/static/images/details_close.png 1970-01-01 00:00:00 +0000 and dashboard_app/static/images/details_close.png 2011-07-08 04:20:37 +0000 differ
=== added file 'dashboard_app/static/images/details_open.png'
Binary files dashboard_app/static/images/details_open.png 1970-01-01 00:00:00 +0000 and dashboard_app/static/images/details_open.png 2011-07-08 04:20:37 +0000 differ
=== added file 'dashboard_app/static/js/FixedHeader.min.js'
@@ -0,0 +1,115 @@
+/*
+ * File: FixedHeader.min.js
+ * Version: 2.0.4
+ * Author: Allan Jardine (www.sprymedia.co.uk)
+ *
+ * Copyright 2009-2011 Allan Jardine, all rights reserved.
+ *
+ * This source file is free software, under either the GPL v2 license or a
+ * BSD (3 point) style license, as supplied with this software.
+ *
+ * This source file 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 license files for details.
+ */
+var FixedHeader=function(b,a){if(typeof this.fnInit!="function"){alert("FixedHeader warning: FixedHeader must be initialised with the 'new' keyword.");
+return}var c=this;var d={aoCache:[],oSides:{top:true,bottom:false,left:false,right:false},oZIndexes:{top:104,bottom:103,left:102,right:101},oMes:{iTableWidth:0,iTableHeight:0,iTableLeft:0,iTableRight:0,iTableTop:0,iTableBottom:0},nTable:null,bUseAbsPos:false,bFooter:false};
+this.fnGetSettings=function(){return d};this.fnUpdate=function(){this._fnUpdateClones();
+this._fnUpdatePositions()};this.fnInit(b,a)};FixedHeader.prototype={fnInit:function(b,a){var c=this.fnGetSettings();
+var d=this;this.fnInitSettings(c,a);if(typeof b.fnSettings=="function"){if(typeof b.fnVersionCheck=="functon"&&b.fnVersionCheck("1.6.0")!==true){alert("FixedHeader 2 required DataTables 1.6.0 or later. Please upgrade your DataTables installation");
+return}var e=b.fnSettings();if(e.oScroll.sX!=""||e.oScroll.sY!=""){alert("FixedHeader 2 is not supported with DataTables' scrolling mode at this time");
+return}c.nTable=e.nTable;e.aoDrawCallback.push({fn:function(){FixedHeader.fnMeasure();
+d._fnUpdateClones.call(d);d._fnUpdatePositions.call(d)},sName:"FixedHeader"})}else{c.nTable=b
+}c.bFooter=($(">tfoot",c.nTable).length>0)?true:false;c.bUseAbsPos=(jQuery.browser.msie&&(jQuery.browser.version=="6.0"||jQuery.browser.version=="7.0"));
+if(c.oSides.top){c.aoCache.push(d._fnCloneTable("fixedHeader","FixedHeader_Header",d._fnCloneThead))
+}if(c.oSides.bottom){c.aoCache.push(d._fnCloneTable("fixedFooter","FixedHeader_Footer",d._fnCloneTfoot))
+}if(c.oSides.left){c.aoCache.push(d._fnCloneTable("fixedLeft","FixedHeader_Left",d._fnCloneTLeft))
+}if(c.oSides.right){c.aoCache.push(d._fnCloneTable("fixedRight","FixedHeader_Right",d._fnCloneTRight))
+}FixedHeader.afnScroll.push(function(){d._fnUpdatePositions.call(d)});jQuery(window).resize(function(){FixedHeader.fnMeasure();
+d._fnUpdateClones.call(d);d._fnUpdatePositions.call(d)});FixedHeader.fnMeasure();
+d._fnUpdateClones();d._fnUpdatePositions()},fnInitSettings:function(b,a){if(typeof a!="undefined"){if(typeof a.top!="undefined"){b.oSides.top=a.top
+}if(typeof a.bottom!="undefined"){b.oSides.bottom=a.bottom}if(typeof a.left!="undefined"){b.oSides.left=a.left
+}if(typeof a.right!="undefined"){b.oSides.right=a.right}if(typeof a.zTop!="undefined"){b.oZIndexes.top=a.zTop
+}if(typeof a.zBottom!="undefined"){b.oZIndexes.bottom=a.zBottom}if(typeof a.zLeft!="undefined"){b.oZIndexes.left=a.zLeft
+}if(typeof a.zRight!="undefined"){b.oZIndexes.right=a.zRight}}b.bUseAbsPos=(jQuery.browser.msie&&(jQuery.browser.version=="6.0"||jQuery.browser.version=="7.0"))
+},_fnCloneTable:function(f,e,d){var b=this.fnGetSettings();var a;if(jQuery(b.nTable.parentNode).css("position")!="absolute"){b.nTable.parentNode.style.position="relative"
+}a=b.nTable.cloneNode(false);var c=document.createElement("div");c.style.position="absolute";
+c.className+=" FixedHeader_Cloned "+f+" "+e;if(f=="fixedHeader"){c.style.zIndex=b.oZIndexes.top
+}if(f=="fixedFooter"){c.style.zIndex=b.oZIndexes.bottom}if(f=="fixedLeft"){c.style.zIndex=b.oZIndexes.left
+}else{if(f=="fixedRight"){c.style.zIndex=b.oZIndexes.right}}c.appendChild(a);document.body.appendChild(c);
+return{nNode:a,nWrapper:c,sType:f,sPosition:"",sTop:"",sLeft:"",fnClone:d}},_fnMeasure:function(){var d=this.fnGetSettings(),a=d.oMes,c=jQuery(d.nTable),b=c.offset(),f=this._fnSumScroll(d.nTable.parentNode,"scrollTop"),e=this._fnSumScroll(d.nTable.parentNode,"scrollLeft");
+a.iTableWidth=c.outerWidth();a.iTableHeight=c.outerHeight();a.iTableLeft=b.left+d.nTable.parentNode.scrollLeft;
+a.iTableTop=b.top+f;a.iTableRight=a.iTableLeft+a.iTableWidth;a.iTableRight=FixedHeader.oDoc.iWidth-a.iTableLeft-a.iTableWidth;
+a.iTableBottom=FixedHeader.oDoc.iHeight-a.iTableTop-a.iTableHeight},_fnSumScroll:function(c,b){var a=c[b];
+while(c=c.parentNode){if(c.nodeName!="HTML"&&c.nodeName!="BODY"){break}a=c[b]}return a
+},_fnUpdatePositions:function(){var c=this.fnGetSettings();this._fnMeasure();for(var b=0,a=c.aoCache.length;
+b<a;b++){if(c.aoCache[b].sType=="fixedHeader"){this._fnScrollFixedHeader(c.aoCache[b])
+}else{if(c.aoCache[b].sType=="fixedFooter"){this._fnScrollFixedFooter(c.aoCache[b])
+}else{if(c.aoCache[b].sType=="fixedLeft"){this._fnScrollHorizontalLeft(c.aoCache[b])
+}else{this._fnScrollHorizontalRight(c.aoCache[b])}}}}},_fnUpdateClones:function(){var c=this.fnGetSettings();
+for(var b=0,a=c.aoCache.length;b<a;b++){c.aoCache[b].fnClone.call(this,c.aoCache[b])
+}},_fnScrollHorizontalRight:function(g){var e=this.fnGetSettings(),f=e.oMes,b=FixedHeader.oWin,a=FixedHeader.oDoc,d=g.nWrapper,c=jQuery(d).outerWidth();
+if(b.iScrollRight<f.iTableRight){this._fnUpdateCache(g,"sPosition","absolute","position",d.style);
+this._fnUpdateCache(g,"sTop",f.iTableTop+"px","top",d.style);this._fnUpdateCache(g,"sLeft",(f.iTableLeft+f.iTableWidth-c)+"px","left",d.style)
+}else{if(f.iTableLeft<a.iWidth-b.iScrollRight-c){if(e.bUseAbsPos){this._fnUpdateCache(g,"sPosition","absolute","position",d.style);
+this._fnUpdateCache(g,"sTop",f.iTableTop+"px","top",d.style);this._fnUpdateCache(g,"sLeft",(a.iWidth-b.iScrollRight-c)+"px","left",d.style)
+}else{this._fnUpdateCache(g,"sPosition","fixed","position",d.style);this._fnUpdateCache(g,"sTop",(f.iTableTop-b.iScrollTop)+"px","top",d.style);
+this._fnUpdateCache(g,"sLeft",(b.iWidth-c)+"px","left",d.style)}}else{this._fnUpdateCache(g,"sPosition","absolute","position",d.style);
+this._fnUpdateCache(g,"sTop",f.iTableTop+"px","top",d.style);this._fnUpdateCache(g,"sLeft",f.iTableLeft+"px","left",d.style)
+}}},_fnScrollHorizontalLeft:function(g){var e=this.fnGetSettings(),f=e.oMes,b=FixedHeader.oWin,a=FixedHeader.oDoc,c=g.nWrapper,d=jQuery(c).outerWidth();
+if(b.iScrollLeft<f.iTableLeft){this._fnUpdateCache(g,"sPosition","absolute","position",c.style);
+this._fnUpdateCache(g,"sTop",f.iTableTop+"px","top",c.style);this._fnUpdateCache(g,"sLeft",f.iTableLeft+"px","left",c.style)
+}else{if(b.iScrollLeft<f.iTableLeft+f.iTableWidth-d){if(e.bUseAbsPos){this._fnUpdateCache(g,"sPosition","absolute","position",c.style);
+this._fnUpdateCache(g,"sTop",f.iTableTop+"px","top",c.style);this._fnUpdateCache(g,"sLeft",b.iScrollLeft+"px","left",c.style)
+}else{this._fnUpdateCache(g,"sPosition","fixed","position",c.style);this._fnUpdateCache(g,"sTop",(f.iTableTop-b.iScrollTop)+"px","top",c.style);
+this._fnUpdateCache(g,"sLeft","0px","left",c.style)}}else{this._fnUpdateCache(g,"sPosition","absolute","position",c.style);
+this._fnUpdateCache(g,"sTop",f.iTableTop+"px","top",c.style);this._fnUpdateCache(g,"sLeft",(f.iTableLeft+f.iTableWidth-d)+"px","left",c.style)
+}}},_fnScrollFixedFooter:function(h){var f=this.fnGetSettings(),g=f.oMes,b=FixedHeader.oWin,a=FixedHeader.oDoc,c=h.nWrapper,e=jQuery("thead",f.nTable).outerHeight(),d=jQuery(c).outerHeight();
+if(b.iScrollBottom<g.iTableBottom){this._fnUpdateCache(h,"sPosition","absolute","position",c.style);
+this._fnUpdateCache(h,"sTop",(g.iTableTop+g.iTableHeight-d)+"px","top",c.style);this._fnUpdateCache(h,"sLeft",g.iTableLeft+"px","left",c.style)
+}else{if(b.iScrollBottom<g.iTableBottom+g.iTableHeight-d-e){if(f.bUseAbsPos){this._fnUpdateCache(h,"sPosition","absolute","position",c.style);
+this._fnUpdateCache(h,"sTop",(a.iHeight-b.iScrollBottom-d)+"px","top",c.style);this._fnUpdateCache(h,"sLeft",g.iTableLeft+"px","left",c.style)
+}else{this._fnUpdateCache(h,"sPosition","fixed","position",c.style);this._fnUpdateCache(h,"sTop",(b.iHeight-d)+"px","top",c.style);
+this._fnUpdateCache(h,"sLeft",(g.iTableLeft-b.iScrollLeft)+"px","left",c.style)}}else{this._fnUpdateCache(h,"sPosition","absolute","position",c.style);
+this._fnUpdateCache(h,"sTop",(g.iTableTop+d)+"px","top",c.style);this._fnUpdateCache(h,"sLeft",g.iTableLeft+"px","left",c.style)
+}}},_fnScrollFixedHeader:function(g){var d=this.fnGetSettings(),f=d.oMes,b=FixedHeader.oWin,a=FixedHeader.oDoc,c=g.nWrapper,e=d.nTable.getElementsByTagName("tbody")[0].offsetHeight;
+if(f.iTableTop>b.iScrollTop){this._fnUpdateCache(g,"sPosition","absolute","position",c.style);
+this._fnUpdateCache(g,"sTop",f.iTableTop+"px","top",c.style);this._fnUpdateCache(g,"sLeft",f.iTableLeft+"px","left",c.style)
+}else{if(b.iScrollTop>f.iTableTop+e){this._fnUpdateCache(g,"sPosition","absolute","position",c.style);
+this._fnUpdateCache(g,"sTop",(f.iTableTop+e)+"px","top",c.style);this._fnUpdateCache(g,"sLeft",f.iTableLeft+"px","left",c.style)
+}else{if(d.bUseAbsPos){this._fnUpdateCache(g,"sPosition","absolute","position",c.style);
+this._fnUpdateCache(g,"sTop",b.iScrollTop+"px","top",c.style);this._fnUpdateCache(g,"sLeft",f.iTableLeft+"px","left",c.style)
+}else{this._fnUpdateCache(g,"sPosition","fixed","position",c.style);this._fnUpdateCache(g,"sTop","0px","top",c.style);
+this._fnUpdateCache(g,"sLeft",(f.iTableLeft-b.iScrollLeft)+"px","left",c.style)}}}},_fnUpdateCache:function(e,c,b,d,a){if(e[c]!=b){a[d]=b;
+e[c]=b}},_fnCloneThead:function(d){var c=this.fnGetSettings();var a=d.nNode;d.nWrapper.style.width=jQuery(c.nTable).outerWidth()+"px";
+while(a.childNodes.length>0){jQuery("thead th",a).unbind("click");a.removeChild(a.childNodes[0])
+}var b=jQuery("thead",c.nTable).clone(true)[0];a.appendChild(b);jQuery("thead:eq(0)>tr th",c.nTable).each(function(e){jQuery("thead:eq(0)>tr th:eq("+e+")",a).width(jQuery(this).width())
+});jQuery("thead:eq(0)>tr td",c.nTable).each(function(e){jQuery("thead:eq(0)>tr th:eq("+e+")",a)[0].style.width(jQuery(this).width())
+})},_fnCloneTfoot:function(d){var c=this.fnGetSettings();var a=d.nNode;d.nWrapper.style.width=jQuery(c.nTable).outerWidth()+"px";
+while(a.childNodes.length>0){a.removeChild(a.childNodes[0])}var b=jQuery("tfoot",c.nTable).clone(true)[0];
+a.appendChild(b);jQuery("tfoot:eq(0)>tr th",c.nTable).each(function(e){jQuery("tfoot:eq(0)>tr th:eq("+e+")",a).width(jQuery(this).width())
+});jQuery("tfoot:eq(0)>tr td",c.nTable).each(function(e){jQuery("tfoot:eq(0)>tr th:eq("+e+")",a)[0].style.width(jQuery(this).width())
+})},_fnCloneTLeft:function(f){var c=this.fnGetSettings();var b=f.nNode;var e=jQuery("tbody tr:eq(0) td",c.nTable).length;
+var a=($.browser.msie&&($.browser.version=="6.0"||$.browser.version=="7.0"));while(b.childNodes.length>0){b.removeChild(b.childNodes[0])
+}b.appendChild(jQuery("thead",c.nTable).clone(true)[0]);b.appendChild(jQuery("tbody",c.nTable).clone(true)[0]);
+if(c.bFooter){b.appendChild(jQuery("tfoot",c.nTable).clone(true)[0])}jQuery("thead tr th:gt(0)",b).remove();
+jQuery("tfoot tr th:gt(0)",b).remove();$("tbody tr",b).each(function(g){$("td:gt(0)",this).remove();
+if($.browser.mozilla||$.browser.opera){$("td",this).height($("tbody tr:eq("+g+")",that.dom.body).outerHeight())
+}else{$("td",this).height($("tbody tr:eq("+g+")",that.dom.body).outerHeight()-iBoxHack)
+}if(!a){$("tbody tr:eq("+g+")",that.dom.body).height($("tbody tr:eq("+g+")",that.dom.body).outerHeight())
+}});var d=jQuery("thead tr th:eq(0)",c.nTable).outerWidth();b.style.width=d+"px";
+f.nWrapper.style.width=d+"px"},_fnCloneTRight:function(f){var c=this.fnGetSettings();
+var b=f.nNode;var e=jQuery("tbody tr:eq(0) td",c.nTable).length;var a=($.browser.msie&&($.browser.version=="6.0"||$.browser.version=="7.0"));
+while(b.childNodes.length>0){b.removeChild(b.childNodes[0])}b.appendChild(jQuery("thead",c.nTable).clone(true)[0]);
+b.appendChild(jQuery("tbody",c.nTable).clone(true)[0]);if(c.bFooter){b.appendChild(jQuery("tfoot",c.nTable).clone(true)[0])
+}jQuery("thead tr th:not(:nth-child("+e+"n))",b).remove();jQuery("tfoot tr th:not(:nth-child("+e+"n))",b).remove();
+$("tbody tr",b).each(function(g){$("td:lt("+e-1+")",this).remove();if($.browser.mozilla||$.browser.opera){$("td",this).height($("tbody tr:eq("+g+")",that.dom.body).outerHeight())
+}else{$("td",this).height($("tbody tr:eq("+g+")",that.dom.body).outerHeight()-iBoxHack)
+}if(!a){$("tbody tr:eq("+g+")",that.dom.body).height($("tbody tr:eq("+g+")",that.dom.body).outerHeight())
+}});var d=jQuery("thead tr th:eq("+(e-1)+")",c.nTable).outerWidth();b.style.width=d+"px";
+f.nWrapper.style.width=d+"px"}};FixedHeader.oWin={iScrollTop:0,iScrollRight:0,iScrollBottom:0,iScrollLeft:0,iHeight:0,iWidth:0};
+FixedHeader.oDoc={iHeight:0,iWidth:0};FixedHeader.afnScroll=[];FixedHeader.fnMeasure=function(){var d=jQuery(window),c=jQuery(document),b=FixedHeader.oWin,a=FixedHeader.oDoc;
+a.iHeight=c.height();a.iWidth=c.width();b.iHeight=d.height();b.iWidth=d.width();b.iScrollTop=d.scrollTop();
+b.iScrollLeft=d.scrollLeft();b.iScrollRight=a.iWidth-b.iScrollLeft-b.iWidth;b.iScrollBottom=a.iHeight-b.iScrollTop-b.iHeight
+};jQuery(window).scroll(function(){FixedHeader.fnMeasure();for(var b=0,a=FixedHeader.afnScroll.length;
+b<a;b++){FixedHeader.afnScroll[b]()}});
\ No newline at end of file
=== modified file 'dashboard_app/static/js/jquery.dashboard.js'
@@ -2,6 +2,7 @@
(function($) {
var _server = null;
var _url = null;
+ var _global_table_id = 0;
function query_data_view(data_view_name, data_view_arguments, callback) {
_server.query_data_view(callback, data_view_name, data_view_arguments);
@@ -40,25 +41,30 @@
$this.data('dashboard', plot_data);
}
query_data_view(query.data_view.name, query.data_view.args, function(response) {
- plot_data.series.push({
- data: response.result.rows,
- label: query.label
- });
- $.plot($this, plot_data.series, plot_data.options);
+ if (response.result) {
+ plot_data.series.push({
+ data: response.result.rows,
+ label: query.label
+ });
+ $.plot($this, plot_data.series, plot_data.options);
+ } else {
+ alert("Query failed: "+ response.error.faultString + " (code: " + response.error.faultCode + ")");
+ }
});
});
},
render_table: function(dataset, options) {
- var html = "<table class='data'>";
+ var table_id = _global_table_id++;
+ var html = "<table class='demo_jui display' id='dashboard_table_" + table_id + "'>";
if (options != undefined && options.caption != undefined) {
html += "<caption>" + options.caption + "</caption>";
}
- html += "<tr>";
+ html += "<thead><tr>";
$.each(dataset.columns, function (index, column) {
html += "<th>" + column.name + "</th>";
});
- html += "</tr>";
+ html += "</tr></thead><tbody>";
$.each(dataset.rows, function (index, row) {
html += "<tr>";
$.each(row, function (index, cell) {
@@ -84,8 +90,12 @@
});
html += "</tr>";
});
- html += "</table>";
+ html += "</tbody></table>";
this.html(html);
+ $("#dashboard_table_" + table_id).dataTable({
+ "bJQueryUI": true,
+ "sPaginationType": "full_numbers",
+ });
},
render_to_table: function(data_view_name, data_view_arguments, options) {
=== added file 'dashboard_app/templates/dashboard_app/_ajax_attachment_viewer.html'
@@ -0,0 +1,12 @@
+{% load i18n %}
+{% if lines %}
+<ol class="file_listing">
+ {% for line in lines %}
+ <li id="L{{forloop.counter}}">
+ <a href="#L{{forloop.counter}}">{{line}}</a>
+ </li>
+ {% endfor %}
+</ol>
+{% else %}
+<h1>{% trans "Viewer not available" %}</h1>
+{% endif %}
=== added file 'dashboard_app/templates/dashboard_app/_ajax_bundle_viewer.html'
@@ -0,0 +1,17 @@
+{% load i18n %}
+{% load stylize %}
+
+
+{% with bundle.get_sanitized_bundle as sanitized_bundle %}
+{% if sanitized_bundle.did_remove_attachments %}
+<div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all" style="margin-top: 20px; padding: 0.7em">
+ <span class="ui-icon ui-icon-info" style="float: left; margin-right: 0.3em;"></span>
+ <strong>Note:</strong> Inline attachments were removed to make this page more readable.
+ </div>
+</div>
+{% endif %}
+<div style="overflow-x: scroll">
+ {% stylize "js" %}{{ sanitized_bundle.get_human_readable_json|safe }}{% endstylize %}
+</div>
+{% endwith %}
=== added file 'dashboard_app/templates/dashboard_app/_breadcrumbs.html'
@@ -0,0 +1,5 @@
+{% for bread_crumb in bread_crumb_trail %}
+<li><a
+ href="{{ bread_crumb.get_absolute_url }}"
+ >{{ bread_crumb.get_name }}</a></li>
+{% endfor %}
=== added file 'dashboard_app/templates/dashboard_app/_bundle_stream_sidebar.html'
@@ -0,0 +1,74 @@
+{% load i18n %}
+<h3>{% trans "About" %}</h3>
+<dl>
+ <dt>{% trans "Pathname:" %}</dt>
+ <dd>{{ bundle_stream.pathname }}</dd>
+ <dt>{% trans "Name:" %}</dt>
+ <dd>{{ bundle_stream.name|default:"<i>not set</i>" }}</dd>
+</dl>
+<h3>{% trans "Ownership" %}</h3>
+{% if bundle_stream.user %}
+<p>{% trans "This stream is owned by" %} <q>{{ bundle_stream.user }}</q></p>
+{% else %}
+<p>{% trans "This stream is owned by group called" %} <q>{{ bundle_stream.group }}</q></p>
+{% endif %}
+<h3>{% trans "Access rights" %}</h3>
+<dl>
+ <dt>{% trans "Stream type:" %}</dt>
+ {% if bundle_stream.is_anonymous %}
+ <dd>
+ Anonymous stream <a href="#" id="what-are-anonymous-streams">(what is this?)</a>
+ <div id="dialog-message" title="{% trans "About anonymous streams" %}">
+ <p>The dashboard has several types of containers for test results. One of the
+ most common and the oldest one is an <em>anonymous stream</em>. Anonymous
+ streams act like public FTP servers. Anyone can download or upload files at
+ will. There are some restrictions, nobody can change or remove existing
+ files</p>
+
+ <p>When a stream is anonymous anonyone can upload new test results and the
+ identity of the uploading user is not recorded in the system. Anonymous
+ streams have to be public (granting read access to everyone)</p>
+
+ <div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all" style="margin-top: 20px; padding: 0.7em">
+ <span class="ui-icon ui-icon-info" style="float: left; margin-right: 0.3em;"></span>
+ <strong>Note:</strong> A stream can be marked as anonymous in the administration panel
+ </div>
+ </div>
+ </div>
+ <script type="text/javascript">
+ $(function() {
+ $( "#dialog-message" ).dialog({
+ autoOpen: false,
+ modal: true,
+ minWidth: 500,
+ buttons: {
+ Ok: function() {
+ $( this ).dialog( "close" );
+ }
+ }
+ });
+ $( "#what-are-anonymous-streams" ).click(function(e) {
+ $( "#dialog-message").dialog('open');
+ });
+ });
+ </script>
+ </dd>
+ <dt>{% trans "Read access:" %}</dt>
+ <dd>{% trans "Anyone can download or read test results uploaded here" %}</dd>
+ <dt>{% trans "Write access:" %}</dt>
+ <dd>{% trans "Anyone can upload test results here" %}</dt>
+ {% else %}
+ {% if bundle_stream.is_public %}
+ <dd>{% trans "Public stream" %}</dd>
+ <dt>{% trans "Read access:" %}</dt>
+ <dd>{% trans "Anyone can download or read test results uploaded here" %}</dd>
+ {% else %}
+ <dd>{% trans "Private stream" %}</dd>
+ <dt>{% trans "Read access:" %}</dt>
+ <dd>{% trans "Only the owner can download or read test results uploaded here" %}</dd>
+ {% endif %}
+ <dt>{% trans "Write access:" %}</dt>
+ <dd>{% trans "Only the owner can upload test results here" %}</dd>
+ {% endif %}
+</dl>
=== added file 'dashboard_app/templates/dashboard_app/_content.html'
@@ -0,0 +1,22 @@
+{% extends "layouts/content.html" %}
+
+
+{% block extrahead %}
+{{ block.super }}
+{% include "dashboard_app/_extrahead.html" %}
+{% endblock %}
+
+
+{% block title %}
+{{ block.super }}{% include "dashboard_app/_title.html" %}
+{% endblock %}
+
+
+{% block breadcrumbs %}
+{% include "dashboard_app/_breadcrumbs.html" %}
+{% endblock %}
+
+
+{% block extension_navigation %}
+{% include "dashboard_app/_extension_navigation.html" %}
+{% endblock %}
=== added file 'dashboard_app/templates/dashboard_app/_content_with_sidebar.html'
@@ -0,0 +1,22 @@
+{% extends "layouts/content_with_sidebar.html" %}
+
+
+{% block extrahead %}
+{{ block.super }}
+{% include "dashboard_app/_extrahead.html" %}
+{% endblock %}
+
+
+{% block title %}
+{{ block.super }}{% include "dashboard_app/_title.html" %}
+{% endblock %}
+
+
+{% block breadcrumbs %}
+{% include "dashboard_app/_breadcrumbs.html" %}
+{% endblock %}
+
+
+{% block extension_navigation %}
+{% include "dashboard_app/_extension_navigation.html" %}
+{% endblock %}
=== added file 'dashboard_app/templates/dashboard_app/_extension_navigation.html'
@@ -0,0 +1,13 @@
+{% load i18n %}
+<div id="lava-server-extension-navigation" class="lava-server-sub-toolbar">
+ <ul>
+ <li><a href="{% url dashboard_app.views.bundle_stream_list %}"
+ >{% trans "Bundle Streams" %}</a></li>
+ <li><a href="{% url dashboard_app.views.test_list %}"
+ >{% trans "Tests" %}</a></li>
+ <li><a href="{% url dashboard_app.views.data_view_list %}"
+ >{% trans "Data Views" %}</a></li>
+ <li><a href="{% url dashboard_app.views.report_list %}"
+ >{% trans "Reports" %}</a></li>
+ </ul>
+</div>
=== added file 'dashboard_app/templates/dashboard_app/_extrahead.html'
@@ -0,0 +1,3 @@
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava/css/demo_table_jui.css"/>
+<script type="text/javascript" src="{{ STATIC_URL }}js/FixedHeader.min.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}lava/js/jquery.dataTables.min.js"></script>
=== added file 'dashboard_app/templates/dashboard_app/_test_run_list_table.html'
@@ -0,0 +1,31 @@
+{% load i18n %}
+<table class="demo_jui display" id="test_runs">
+ <thead>
+ <tr>
+ <th>{% trans "Test Run" %}</th>
+ <th>{% trans "Test" %}</th>
+ <th>{% trans "Uploaded On" %} </th>
+ <th>{% trans "Analyzed" %}</th>
+ <th>{% trans "Pass" %}</th>
+ <th>{% trans "Fail" %}</th>
+ <th>{% trans "Skip" %}</th>
+ <th>{% trans "Unknown" %}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for test_run in test_run_list %}
+ <tr>
+ <td><a href="{{ test_run.get_absolute_url }}"><code>{{ test_run.analyzer_assigned_uuid }}<code/></a></td>
+ <td><a href="{{ test_run.test.get_absolute_url }}">{{ test_run.test }}</a></td>
+ <td>{{ test_run.bundle.uploaded_on }}</td>
+ <td>{{ test_run.analyzer_assigned_date }}</td>
+ {% with test_run.get_summary_results as summary %}
+ <td>{{ summary.pass|default:0 }}</td>
+ <td>{{ summary.fail|default:0 }}</td>
+ <td>{{ summary.skip|default:0 }}</td>
+ <td>{{ summary.unknown|default:0 }}</td>
+ {% endwith %}
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
=== added file 'dashboard_app/templates/dashboard_app/_title.html'
@@ -0,0 +1,1 @@
+{% for bread_crumb in bread_crumb_trail %} | {{ bread_crumb.get_name }}{% endfor %}
=== modified file 'dashboard_app/templates/dashboard_app/api.html'
@@ -1,4 +1,4 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content.html" %}
{% load markup %}
{% load i18n %}
@@ -7,6 +7,7 @@
{{ block.super }} | {% trans "XML-RPC API" %}
{% endblock %}
+
{% block extrahead %}
{{ block.super }}
<script type="text/javascript" src="{{ STATIC_URL }}lava/js/jquery-1.5.1.min.js"></script>
=== modified file 'dashboard_app/templates/dashboard_app/attachment_detail.html'
@@ -1,51 +1,46 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content.html" %}
{% load i18n %}
{% load humanize %}
-{% block title %}
-{{ block.super }} | {% trans "Attachment" %} | {{ attachment.pk }}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-{{ block.super }}
-<li><a href="{{ attachment.get_absolute_url }}">{{ attachment }}</a></li>
-{% endblock %}
-
-
-{% block sidebar %}
-<h3>{% trans "Attachment Information" %}</h3>
-<dl>
- <dt>{% trans "Pathname" %}</dt>
- <dd>{{ attachment.content_filename }}</dd>
- <dt>{% trans "MIME type"%}</dt>
- <dd>{{ attachment.mime_type }}</dd>
- <dt>{% trans "Stored in dashboard" %}</dt>
- <dd>{{ attachment.content|yesno }}</dd>
- {% if attachment.content %}
- <dt>{% trans "File size" %}</dt>
- <dd>{{ attachment.content.size|filesizeformat }}</dd>
- {% endif %}
- <dt>{% trans "Stored on 3rd party server" %}</dt>
- <dd>{{ attachment.public_url|yesno }}</dd>
- <dt>{% trans "Public URL" %}</dt>
- <dd><a href="{{ attachment.public_url }}">{{ attachment.public_url }}</a></dd>
-</dl>
-{% endblock %}
-
-
{% block content %}
-{% if lines %}
-<h3>Inline viewer</h3>
-<ol class="file_listing">
- {% for line in lines %}
- <li id="L{{forloop.counter}}">
- <a href="#L{{forloop.counter}}">{{line}}</a>
- </li>
- {% endfor %}
-</ol>
-{% else %}
-<h1>Viewer not available</h1>
-{% endif %}
+<div id="tabs">
+ <ul>
+ <li><a href="#tab-attachment-information">{% trans "Attachment Information" %}</a></li>
+ <li><a href="{% url dashboard_app.views.ajax_attachment_viewer attachment.pk %}">{% trans "Inline Viewer" %}</a></li>
+ </ul>
+ <div id="tab-attachment-information">
+ <dl>
+ <dt>{% trans "Pathname" %}</dt>
+ <dd>{{ attachment.content_filename }}</dd>
+ <dt>{% trans "MIME type"%}</dt>
+ <dd>{{ attachment.mime_type }}</dd>
+ <dt>{% trans "Stored in dashboard" %}</dt>
+ <dd>{{ attachment.content|yesno }}</dd>
+ {% if attachment.content %}
+ <dt>{% trans "File size" %}</dt>
+ <dd>{{ attachment.content.size|filesizeformat }}</dd>
+ {% endif %}
+ <dt>{% trans "Stored on 3rd party server" %}</dt>
+ <dd>{{ attachment.public_url|yesno }}</dd>
+ {% if attachment.public_url %}
+ <dt>{% trans "Public URL" %}</dt>
+ <dd><a href="{{ attachment.public_url }}">{{ attachment.public_url }}</a></dd>
+ {% endif %}
+ </dl>
+ </div>
+</div>
+<script type="text/javascript">
+ $(document).ready(function() {
+ $("#tabs").tabs({
+ ajaxOptions: {
+ dataType: "html",
+ error: function( xhr, status, index, anchor ) {
+ $( anchor.hash ).html(
+ "Couldn't load this tab. We'll try to fix this as soon as possible.");
+ }
+ }
+ });
+ });
+</script>
{% endblock %}
=== added file 'dashboard_app/templates/dashboard_app/attachment_list.html'
@@ -0,0 +1,40 @@
+{% extends "dashboard_app/_content.html" %}
+{% load i18n %}
+
+
+{% block content %}
+<table id="attachments" class="demo_jui display">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Size</th>
+ <th>MIME type</th>
+ <th>Public URL</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for attachment in attachment_list %}
+ <tr>
+ <td><a href="{{ attachment.get_absolute_url }}"
+ ><code>{{ attachment.content_filename }}</code></a></td>
+ <td>{{ attachment.content.size|filesizeformat }}</td>
+ <td><code>{{ attachment.mime_type }}</code></td>
+ <td>
+ {% if attachment.public_url %}
+ <a href="{{ attachment.public_url }}">public url</a>
+ {% endif %}
+ </td>
+ </tr>
+ {% endfor %}
+ </body>
+</table>
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ $('#attachments').dataTable({
+ bJQueryUI: true,
+ bPaginate: false,
+ aaSorting: [[1, "desc"]],
+ });
+ });
+</script>
+{% endblock %}
=== removed file 'dashboard_app/templates/dashboard_app/base.html'
@@ -1,12 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-
-
-{% block extension_navigation %}
-<ul>
- <li><a href="{% url dashboard_app.views.bundle_stream_list %}">{% trans "Bundle Streams" %}</a></li>
- <li><a href="{% url dashboard_app.views.data_view_list %}">{% trans "Data Views" %}</a></li>
- <li><a href="{% url dashboard_app.views.report_list %}">{% trans "Reports" %}</a></li>
- <li><a href="{% url dashboard_app.views.dashboard_xml_rpc_handler %}">{% trans "XML-RPC (dashboard only)" %}</a></li>
-</ul>
-{% endblock %}
=== added file 'dashboard_app/templates/dashboard_app/bundle_detail.html'
@@ -0,0 +1,68 @@
+{% extends "dashboard_app/_content.html" %}
+{% load humanize %}
+{% load i18n %}
+{% load stylize %}
+
+
+{% block extrahead %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/pygments.css"/>
+{% endblock %}
+
+
+{% block content %}
+<script type="text/javascript">
+ $(document).ready(function() {
+ $("#tabs").tabs({
+ cache: true,
+ show: function(event, ui) {
+ var oTable = $('div.dataTables_scrollBody>table.display', ui.panel).dataTable();
+ if ( oTable.length > 0 ) {
+ oTable.fnAdjustColumnSizing();
+ }
+ },
+ ajaxOptions: {
+ dataType: "html",
+ error: function( xhr, status, index, anchor ) {
+ $( anchor.hash ).html(
+ "Couldn't load this tab. We'll try to fix this as soon as possible.");
+ }
+ }
+ });
+ $('#test_runs').dataTable({
+ bJQueryUI: true,
+ sPaginationType: "full_numbers",
+ aaSorting: [[0, "desc"]],
+ });
+ });
+</script>
+<div id="tabs">
+ <ul>
+ {% if bundle.is_deserialized %}
+ <li><a href="#tab-test-runs">{% trans "Test Runs" %}</a></li>
+ {% endif %}
+ {% if bundle.deserialization_error.get %}
+ <li><a href="#tab-deserialization-error">{% trans "Deserialization Error" %}</a></li>
+ {% endif %}
+ <li><a href="{% url dashboard_app.views.ajax_bundle_viewer bundle.pk %}">{% trans "Bundle Viewer" %}</a></li>
+ </ul>
+ {% if bundle.is_deserialized %}
+ <div id="tab-test-runs">
+ {% with bundle.test_runs.all as test_run_list %}
+ {% include "dashboard_app/_test_run_list_table.html" %}
+ {% endwith %}
+ </div>
+ {% endif %}
+
+ {% if bundle.deserialization_error.get %}
+ <div id="tab-deserialization-error">
+ <h3>Cause</h3>
+ <p>{{ bundle.deserialization_error.get.error_message }}</p>
+ <h3>Deserialization failure traceback</h3>
+ <div style="overflow-x: scroll">
+ {% stylize "pytb" %}{{ bundle.deserialization_error.get.traceback|safe }}{% endstylize %}
+ </div>
+ </div>
+ {% endif %}
+</div>
+{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/bundle_list.html'
@@ -1,51 +1,78 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content_with_sidebar.html" %}
{% load i18n %}
{% load humanize %}
-{% load pagination_tags %}
-
-{% block title %}
-{{ block.super }} | {% trans "Streams" %} | {% trans "Bundle Stream" %} {{ bundle_stream.pathname }} | {% trans "Bundles" %}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.bundle_stream_list %}">{% trans "Bundle Streams" %}</a></li>
-<li><a href="{{ bundle_stream.get_absolute_url }}">{{ bundle_stream }}</a></li>
-<li><a href="{% url dashboard_app.views.bundle_list bundle_stream.pathname %}">{% trans "Bundles" %}</a></li>
-{% endblock %}
-
-
-{% block sidebar %}
-{% endblock %}
{% block content %}
-
-{% autopaginate bundle_list %}
-{% if invalid_page %}
- <h3>{% trans "There are no bundles on this page." %}</h3>
- <p>{% trans "Try the" %} <a href="{% url dashboard_app.views.bundle_list bundle_stream.pathname %}">{% trans "first page" %}</a> {% trans "instead." %}</p>
-{% else %}
- {% if bundle_list.count %}
- <table class="data">
+<div id="master-toolbar-splice" >
+ <span
+ class="length"
+ style="float:left; display:inline-block; text-align:left;"></span>
+ <span
+ class="view-as"
+ style="text-align:center; display: inline-block; width:auto">
+ <input name="radio" checked type="radio" id="as_bundles"/>
+ <label for="as_bundles">
+ <a
+ id="as_bundles_link"
+ href="{% url dashboard_app.views.bundle_list bundle_stream.pathname %}"
+ >{% trans "Bundles" %}</a>
+ </label>
+ <input name="radio" type="radio" id="as_test_runs"/>
+ <label for="as_test_runs">
+ <a
+ id="as_test_runs_link"
+ href="{% url dashboard_app.views.test_run_list bundle_stream.pathname %}"
+ >{% trans "Test Runs" %}</a>
+ </label>
+ </span>
+ <span
+ class="search"
+ style="float:right; display:inline-block; text-align:right"></span>
+</div>
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ oTable = $('#bundles').dataTable({
+ bJQueryUI: true,
+ sPaginationType: "full_numbers",
+ aaSorting: [[1, "desc"]],
+ iDisplayLength: 25,
+ aLengthMenu: [[10, 25, 50, -1], [10, 25, 50, "All"]],
+ sDom: 'lfr<"#master-toolbar">t<"F"ip>'
+ });
+ // Try hard to make our radio boxes behave as links
+ $("#master-toolbar-splice span.view-as").buttonset();
+ $("#master-toolbar-splice span.view-as input").change(function(event) {
+ var link = $("#" + event.target.id + "_link");
+ location.href=link.attr("href");
+ });
+ // Insane splicing below
+ $("div.dataTables_length").children().appendTo("#master-toolbar-splice span.length");
+ $("div.dataTables_filter").children().appendTo("#master-toolbar-splice span.search");
+ $("#master-toolbar-splice").children().appendTo($("#master-toolbar"));
+ $("div.dataTables_length").remove();
+ $("div.dataTables_filter").remove();
+ $("#master-toolbar-splice").remove();
+ $("#master-toolbar").addClass("ui-widget-header ui-corner-tl ui-corner-tr").css(
+ "padding", "5pt").css("text-align", "center");
+ new FixedHeader(oTable);
+ });
+</script>
+<table class="demo_jui display" id="bundles">
+ <thead>
<tr>
- <th>
- {% trans "Uploaded On" %}
- <div style="font-size:smaller">
- {% trans "most recent first" %}
- </div>
- </th>
- <th>{% trans "Uploaded by" %}</th>
- <th>{% trans "Content filename" %}</th>
- <th>{% trans "Content SHA1" %}</th>
- <th>{% trans "Deserialized" %}</th>
+ <th>{% trans "Bundle SHA1" %}</th>
+ <th>{% trans "Uploaded On" %}</th>
+ <th>{% trans "Uploaded By" %}</th>
+ <th>{% trans "Deserialized?" %}</th>
+ <th>{% trans "Problems?" %}</th>
</tr>
+ </thead>
+ <tbody>
{% for bundle in bundle_list %}
<tr>
- <td>
- {{ bundle.uploaded_on|naturalday }}
- {{ bundle.uploaded_on|time }}
- </td>
+ <td><a href="{{ bundle.get_absolute_url }}"><code>{{ bundle.content_sha1 }}</code></a></td>
+ <td>{{ bundle.uploaded_on }}</td>
<td>
{% if bundle.uploaded_by %}
{{ bundle.uploaded_by }}
@@ -53,16 +80,15 @@
<em>{% trans "anonymous user" %}</em>
{% endif %}
</td>
- <td>{{ bundle.content_filename }}</td>
- <td>{{ bundle.content_sha1 }}</td>
<td>{{ bundle.is_deserialized|yesno }}</td>
+ <td>{% if bundle.deserialization_error.get %}yes{% endif %}</td>
</tr>
{% endfor %}
- </table>
- {% else %}
- <p>{% trans "There are no bundles in this stream yet." %}</p>
- {% endif %}
-{% paginate %}
-{% endif %}
-
+ </tbody>
+</table>
+{% endblock %}
+
+
+{% block sidebar %}
+{% include "dashboard_app/_bundle_stream_sidebar.html" %}
{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/bundle_stream_list.html'
@@ -1,15 +1,38 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content_with_sidebar.html" %}
{% load i18n %}
{% load pagination_tags %}
-{% block title %}
-{{ block.super }} | {% trans "Streams" %}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.bundle_stream_list %}">{% trans "Bundle Streams" %}</a></li>
+{% block content %}
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ oTable = $('#bundle_streams').dataTable({
+ bJQueryUI: true,
+ sPaginationType: "full_numbers",
+ iDisplayLength: 25,
+ });
+ });
+</script>
+<table class="demo_jui display" id="bundle_streams">
+ <thead>
+ <tr>
+ <th width="50%">Pathname</th>
+ <th>Name</th>
+ <th>Number of test runs</th>
+ <th>Number of bundles</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for bundle_stream in bundle_stream_list %}
+ <tr>
+ <td style="vertical-align: top;"><a href="{% url dashboard_app.views.bundle_list bundle_stream.pathname %}"><code>{{ bundle_stream.pathname }}</code></a></td>
+ <td>{{ bundle_stream.name|default:"<i>not set</i>" }}</td>
+ <td style="vertical-align: top;"><a href="{% url dashboard_app.views.test_run_list bundle_stream.pathname %}">{{ bundle_stream.get_test_run_count }}</a></td>
+ <td style="vertical-align: top;"><a href="{% url dashboard_app.views.bundle_list bundle_stream.pathname %}">{{ bundle_stream.bundles.count}}</a></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
{% endblock %}
@@ -34,34 +57,3 @@
<p>{% trans "You must" %} <a href="{% url django.contrib.auth.views.login %}">{% trans "sign in" %}</a> {% trans "to get more access" %}</p>
{% endif %}
{% endblock %}
-
-
-{% block content %}
-{% autopaginate bundle_stream_list %}
-{% if bundle_stream_list.count %}
-<table class="data">
- <tr>
- <th>Pathname</th>
- <th>Name</th>
- <th>Number of test runs</th>
- <th>Number of bundles</th>
- </tr>
- {% for bundle_stream in bundle_stream_list %}
- <tr>
- <td>{{ bundle_stream.pathname }}</td>
- <td>{{ bundle_stream.name|default:"<i>not set</i>" }}</td>
- <td><a href="{% url dashboard_app.views.test_run_list bundle_stream.pathname %}">{{ bundle_stream.get_test_run_count }}</a></td>
- <td><a href="{% url dashboard_app.views.bundle_list bundle_stream.pathname %}">{{ bundle_stream.bundles.count}}</a></td>
- </tr>
- {% endfor %}
-</table>
-{% else %}
-{% if user.is_staff %}
-<p>There are no streams yet, you can create one in the <a
- href="{% url admin:dashboard_app_bundlestream_add %}">admin</a> panel.</p>
-{% else %}
-<p>There are no streams yet.</p>
-{% endif %}
-{% endif %}
-{% paginate %}
-{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/data_view_detail.html'
@@ -1,16 +1,6 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content.html" %}
{% load i18n %}
-{% block title %}
-{{ block.super }} | {% trans "Data Views" %} | {{ data_view.name }}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.data_view_list %}">{% trans "Data Views" %}</a></li>
-<li><a href="{% url dashboard_app.views.data_view_detail data_view.name %}">{{ data_view.name }}</a></li>
-{% endblock %}
-
{% block content %}
<dl>
@@ -21,6 +11,7 @@
<dt>Documentation:</dt>
<dd>{{ data_view.documentation }}</dd>
</dl>
+{% if data_view.arguments %}
<table class="data">
<caption>Aruments</caption>
<tr>
@@ -38,4 +29,5 @@
</tr>
{% endfor %}
</table>
+{% endif %}
{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/data_view_list.html'
@@ -1,34 +1,43 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content.html" %}
{% load i18n %}
-{% block title %}
-{{ block.super }} | {% trans "Data Views" %}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.data_view_list %}">{% trans "Data Views" %}</a></li>
-{% endblock %}
-
-
-{% block sidebar %}
-<h3>Hint:</h3>
-<p>To call a data view use the <code>lava-dashboard-tool</code> command. See
-<code>lava-dashboard-tool query-data-view --help</code> to get started.</p>
-{% endblock %}
-
{% block content %}
-<table class="data">
- <tr>
- <th>{% trans "Name" %}</th>
- <th>{% trans "Summary" %}</th>
- </tr>
- {% for data_view in data_view_list %}
- <tr>
- <td><a href="{% url dashboard_app.views.data_view_detail data_view.name %}">{{ data_view.name }}</a></td>
- <td>{{ data_view.summary }}</td>
- </tr>
-{% endfor %}
+<div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all" style="margin-top: 20px; padding: 0.7em">
+ <span
+ class="ui-icon ui-icon-info"
+ style="float: left; margin-right: 0.3em;"></span>
+ <strong>Hint:</strong> To call a data view use the
+ <code>lava-dashboard-tool</code> command. See
+ <code>lava-dashboard-tool query-data-view --help</code>
+ to get started.
+ </div>
+</div>
+<br/>
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ oTable = $('#data_views').dataTable({
+ "bJQueryUI": true,
+ "sPaginationType": "full_numbers",
+ "aaSorting": [[0, "desc"]],
+ });
+ });
+</script>
+<table class="demo_jui display" id="data_views">
+ <thead>
+ <tr>
+ <th>{% trans "Name" %}</th>
+ <th>{% trans "Summary" %}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for data_view in data_view_list %}
+ <tr>
+ <td><a href="{% url dashboard_app.views.data_view_detail data_view.name %}">{{ data_view.name }}</a></td>
+ <td>{{ data_view.summary }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
</table>
{% endblock %}
=== added file 'dashboard_app/templates/dashboard_app/index.html'
@@ -0,0 +1,10 @@
+{% extends "dashboard_app/_content.html" %}
+
+{% block content %}
+<h1>TODO</h1>
+<ul>
+ <li>Briefly mention key dashboard features</li>
+ <li>Link to readthedocs dashboard manual</li>
+ <li>Add sensible dashboard index page (recent/interesting stuff)</li>
+</ul>
+{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/report_detail.html'
@@ -1,37 +1,31 @@
-{% extends "dashboard_app/base.html" %}
-
+{% extends "dashboard_app/_content_with_sidebar.html" %}
{% load i18n %}
{% load stylize %}
-{% block title %}
-{{ block.super }} | {% trans "Reports" %} | {{ report.title }}
-{% endblock %}
+
{% block extrahead %}
{{ block.super }}
<!--[if IE]><script type="text/javascript" src="{{ STATIC_URL }}js/excanvas.min.js"></script><![endif]-->
-<script type="text/javascript" src="{{ STATIC_URL }}lava/js/jquery-1.5.1.min.js"></script>
-<script type="text/javascript" src="{{ STATIC_URL }}lava/js/jquery-ui-1.8.12.custom.min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.rpc.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.flot.min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.dashboard.js"></script>
-<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava/css/Aristo/jquery-ui-1.8.7.custom.css"/>
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/pygments.css"/>
{% endblock %}
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.report_list %}">{% trans "Reports" %}</a></li>
-<li><a href="{{ report.get_absolute_url }}">{{ report.title }}</a></li>
+{% block content %}
+{{ report.get_html|safe}}
{% endblock %}
+
{% block sidebar %}
<h3>Basic information</h3>
<dl>
<dt>Title</dt>
<dd>{{report.title}}</dd>
<dt>Author</dt>
- <dd>{{report.author|default:"Unspecified"}}</dd>
+ <dd>{{report.author|default_if_none:"Unspecified"}}</dd>
<dt>Bug report URL</dt>
<dd>{{report.bug_report_url|default:"Unspecified"}}</dd>
</dl>
@@ -42,6 +36,9 @@
maintainer to debug the problem.</p>
<h3>Source Code</h3>
<button id="show-source">Show source</button>
+<div id="report-source" style="display: none">
+ {% stylize "html" %}{{ report.get_html }}{% endstylize %}
+</div>
<script type="text/javascript">
$(function() {
$("#report-source").dialog({ autoOpen: false, modal: true, width: "auto", title: "Source code"});
@@ -51,11 +48,3 @@
});
</script>
{% endblock %}
-
-
-{% block content %}
-{{ report.get_html|safe}}
-<div id="report-source" style="display: none">
- {% stylize "html" %}{{ report.get_html }}{% endstylize %}
-</div>
-{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/report_list.html'
@@ -1,27 +1,31 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content.html" %}
{% load i18n %}
-{% block title %}
-{{ block.super }} | {% trans "Reports" %}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.report_list %}">{% trans "Reports" %}</a></li>
-{% endblock %}
-
{% block content %}
-<table class="data">
- <tr>
- <th>{% trans "Report title" %}</th>
- <th>{% trans "Path of HTML file" %}</th>
- </tr>
- {% for report in report_list %}
- <tr>
- <td><a href="{{ report.get_absolute_url }}">{{ report.title }}</a></td>
- <td><pre>{{ report.path }}</pre></td>
- </tr>
-{% endfor %}
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ oTable = $('#reports').dataTable({
+ "bJQueryUI": true,
+ "sPaginationType": "full_numbers",
+ "aaSorting": [[0, "desc"]],
+ });
+ });
+</script>
+<table class="demo_jui display" id="reports">
+ <thead>
+ <tr>
+ <th>{% trans "Report title" %}</th>
+ <th>{% trans "Author" %}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for report in report_list %}
+ <tr>
+ <td><a href="{{ report.get_absolute_url }}">{{ report.title }}</a></td>
+ <td>{{ report.author|default_if_none:"<em>unspecified</em>" }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
</table>
{% endblock %}
=== added file 'dashboard_app/templates/dashboard_app/test_detail.html'
@@ -0,0 +1,46 @@
+{% extends "dashboard_app/_content.html" %}
+{% load i18n %}
+
+
+{% block content %}
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ oTable = $('#test_cases').dataTable({
+ bJQueryUI: true,
+ sPaginationType: "full_numbers",
+ aLengthMenu: [[10, 25, 50, -1], [10, 25, 50, "All"]],
+ });
+ });
+</script>
+<table class="demo_jui display" id="test_cases">
+ <thead>
+ <tr>
+ <th>ID</th>
+ <th>Name</th>
+ <th>Units</th>
+ <th>Total Results</th>
+ <th>Total Failures</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for test_case in test.test_cases.all %}
+ <tr>
+ <td>{{ test_case.test_case_id }}</td>
+ <td>{{ test_case.name|default:"<i>not set</i>" }}</td>
+ <td>{{ test_case.units|default:"<i>not set</i>" }}</td>
+ <td>{{ test_case.test_results.all.count }}</td>
+ <td>{{ test_case.count_failures }}</td>
+ </tr>
+ {% endfor %}
+ {% if test.count_results_without_test_case %}
+ <tr>
+ <td><em>Results without test case</em></td>
+ <td><em>N/A</em></td>
+ <td><em>N/A</em></td>
+ <td>{{ test.count_results_without_test_case }}</td>
+ <td>{{ test.count_failures_without_test_case }}</td>
+ </tr>
+ {% endif %}
+ </tbody>
+</table>
+{% endblock %}
=== added file 'dashboard_app/templates/dashboard_app/test_list.html'
@@ -0,0 +1,34 @@
+{% extends "dashboard_app/_content.html" %}
+{% load i18n %}
+
+
+{% block content %}
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ oTable = $('#tests').dataTable({
+ "bJQueryUI": true,
+ "bPaginate": false,
+ });
+ });
+</script>
+<table class="demo_jui display" id="tests">
+ <thead>
+ <tr>
+ <th>ID</th>
+ <th>Name</th>
+ <th>Test Cases</th>
+ <th>Test Runs</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for test in test_list %}
+ <tr>
+ <td><a href="{{ test.get_absolute_url }}">{{ test.test_id }}</a></td>
+ <td>{{ test.name|default:"<i>not set</i>" }}</td>
+ <td>{{ test.test_cases.all.count }}</td>
+ <td>{{ test.test_runs.all.count }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/test_result_detail.html'
@@ -1,20 +1,6 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content_with_sidebar.html" %}
{% load i18n %}
{% load humanize %}
-{% load pagination_tags %}
-
-
-{% block title %}
-{{ block.super }} | {% trans "Test Results" %} | {{ test_result }}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.bundle_stream_list %}">{% trans "Bundle Streams" %}</a></li>
-<li><a href="{{ test_result.test_run.bundle.bundle_stream.get_absolute_url }}">{{ test_result.test_run.bundle.bundle_stream }}</a></li>
-<li><a href="{{ test_result.test_run.get_absolute_url }}">{{ test_result.test_run }}</a></li>
-<li><a href="{{ test_result.get_absolute_url }}">{{ test_result }}</a></li>
-{% endblock %}
{% block sidebar %}
@@ -30,26 +16,27 @@
{% if test_result.test_run.test_results.count > 1 %}
<h3>Other results</h3>
<p class="hint">Results from the same test run are available here</p>
-<ul>
-{% for another_test_result in test_result.test_run.test_results.all %}
-{% if another_test_result.relative_index != test_result.relative_index %}
-<li><a
- href="{{ another_test_result.get_absolute_url }}"
- >Result {{ another_test_result.relative_index }}
- {% if another_test_result.test_case %}
- ({{another_test_result.test_case}})
- {% endif %}
- </a></li>
-{% else %}
-<li><b>
- Result {{ another_test_result.relative_index }}
- {% if another_test_result.test_case %}
- ({{another_test_result.test_case}})
- {% endif %}
-</b></li>
-{% endif %}
-{% endfor %}
-</ul>
+<select id="other_results">
+ {% regroup test_result.test_run.test_results.all by test_case as test_result_group_list %}
+ {% for test_result_group in test_result_group_list %}
+ {% if test_result_group.list|length > 1 %}<optgroup label="Results for test case {{ test_result_group.grouper }}">{% endif %}
+ {% for other_test_result in test_result_group.list %}
+ <option value="{{ other_test_result.get_absolute_url }}"
+ {% if other_test_result.pk == test_result.pk %}disabled selected{% endif %}
+ >
+ Result #{{ other_test_result.relative_index }}: {{ other_test_result.get_result_display }}
+ {% if test_result_group.list|length == 1 %} from test case {{ test_result_group.grouper }}{% endif %}
+ {% if other_test_result.measurement != None %} ({{ other_test_result.measurement }}){% endif %}
+ </option>
+ {% endfor %}
+ {% if test_result_group.list|length > 1 %}</optgroup>{% endif %}
+ {% endfor %}
+</select>
+<script type="text/javascript">
+ $("#other_results").change(function (event) {
+ location.href=$(this).val();
+ });
+</script>
{% endif %}
{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/test_run_detail.html'
@@ -1,19 +1,43 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content_with_sidebar.html" %}
{% load i18n %}
{% load humanize %}
-{% load pagination_tags %}
-
-
-{% block title %}
-{{ block.super }} | {% trans "Test Runs" %} | {{ test_run }}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.bundle_stream_list %}">{% trans "Bundle Streams" %}</a></li>
-<li><a href="{{ test_run.bundle.bundle_stream.get_absolute_url }}">{{ test_run.bundle.bundle_stream }}</a></li>
-<li><a href="{{ test_run.get_absolute_url }}">{{ test_run }}</a></li>
-{% endblock %}
+
+
+{% block content %}
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ oTable = $('#test_results').dataTable({
+ "bJQueryUI": true,
+ "sPaginationType": "full_numbers",
+ "aaSorting": [[0, "asc"]],
+ });
+ });
+</script>
+<table class="demo_jui display" id="test_results">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>{% trans "Test case" %}</th>
+ <th>{% trans "Result" %}</th>
+ <th>{% trans "Measurement" %}</th>
+ </tr>
+ <tbody>
+ {% for test_result in test_run.test_results.all %}
+ <tr>
+ <td width="1%">{{ test_result.relative_index }}</td>
+ <td>{{ test_result.test_case|default_if_none:"<em>Not specified</em>" }}</td>
+ <td>
+ <a href ="{{test_result.get_absolute_url}}">
+ <img src="{{ STATIC_URL }}images/icon-{{ test_result.result_code }}.png"
+ alt="{{ test_result.get_result_display }}" width="16" height="16" border="0"/></a>
+ <a href ="{{test_result.get_absolute_url}}">{{ test_result.get_result_display }}</a>
+ </td>
+ <td>{{ test_result.measurement|default_if_none:"Not specified" }} {{ test_result.units }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% endblock %}
{% block sidebar %}
@@ -24,8 +48,8 @@
<dd>{{ test_run.test }}</dd>
<dt>{% trans "OS Distribution" %}</dt>
<dd>{{ test_run.sw_image_desc|default:"<i>Unspecified</i>" }}</dd>
- <dt>{% trans "Bundle information" %}</dt>
- <dd>{{ test_run.bundle.content_sha1 }}</dd>
+ <dt>{% trans "Bundle SHA1" %}</dt>
+ <dd><a href="{{ test_run.bundle.get_absolute_url }}">{{ test_run.bundle.content_sha1 }}</a></dd>
<dt>{% trans "Time check performed" %}</dt>
<dd>{{ test_run.time_check_performed|yesno }}</dd>
<dt>{% trans "Log analyzed on:" %}</dt>
@@ -38,51 +62,16 @@
{{ test_run.import_assigned_date|naturalday }}
{{ test_run.import_assigned_date|time }}
</dd>
- <dt>{% trans "Software Context" %}</dt>
- <dd>
- <a
- href="{% url dashboard_app.views.test_run_software_context test_run.analyzer_assigned_uuid %}"
- >{% trans "more" %}</a>
- </dd>
- <dt>{% trans "Hardware Context" %}</dt>
- <dd>
- <a
- href="{% url dashboard_app.views.test_run_hardware_context test_run.analyzer_assigned_uuid %}"
- >{% trans "more" %}</a>
+ <dt>{% trans "Other information:" %}</dt>
+ <dd><a
+ href="{% url dashboard_app.views.test_run_hardware_context test_run.bundle.bundle_stream.pathname test_run.bundle.content_sha1 test_run.analyzer_assigned_uuid %}"
+ >{% trans "Hardware context" %} ({{ test_run.devices.all.count }} devices)</a></dd>
+ <dd><a
+ href="{% url dashboard_app.views.test_run_software_context test_run.bundle.bundle_stream.pathname test_run.bundle.content_sha1 test_run.analyzer_assigned_uuid %}"
+ >{% trans "Software context" %} ({{ test_run.packages.all.count }} packages, {{ test_run.sources.all.count }} sources)</a></dd>
+ <dd><a
+ href="{% url dashboard_app.views.attachment_list test_run.bundle.bundle_stream.pathname test_run.bundle.content_sha1 test_run.analyzer_assigned_uuid %}"
+ >{% trans "Attachments" %} ({{ test_run.attachments.count }})</a></dd>
</dd>
</dl>
{% endblock %}
-
-
-{% block content %}
-{% autopaginate test_run.test_results.all 50 as test_results %}
-{% if invalid_page %}
-<h3>{% trans "There is no content on this page." %}</h3>
-<p>{% trans "Try the" %} <a href="{{ test_run.get_absolute_url }}">{% trans "first page" %}</a> {% trans "instead." %}</p>
-{% else %}
-<table class="data">
- <tr>
- <th>#</th>
- <th>{% trans "Test case" %}</th>
- <th>{% trans "Result" %}</th>
- <th>{% trans "Measurement" %}</th>
- </tr>
- {% for test_result in test_results %}
- <tr>
- <td width="1%">{{ test_result.relative_index }}</td>
- <td>{{ test_result.test_case }}</td>
- <td>
- <a href ="{{test_result.get_absolute_url}}">
- <img src="{{ STATIC_URL }}images/icon-{{ test_result.result_code }}.png"
- alt="{{ test_result.get_result_display }}" width="16" height="16" border="0"/></a>
- <a href ="{{test_result.get_absolute_url}}">{{ test_result.get_result_display }}</a>
- </td>
- {% if test_result.measurement %}
- <td>{{ test_result.measurement|default_if_none:"" }} {{ test_result.units }}</td>
- {% endif %}
- </tr>
- {% endfor %}
-</table>
-{% paginate %}
-{% endif %}
-{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/test_run_hardware_context.html'
@@ -1,45 +1,79 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content.html" %}
{% load i18n %}
-{% load humanize %}
-{% load pagination_tags %}
-
-
-{% block title %}
-{{ block.super }} | {% trans "Test Runs" %} | {{ test_run }} | {% trans "Hardware Context" %}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.bundle_stream_list %}">{% trans "Bundle Streams" %}</a></li>
-<li><a href="{{ test_run.bundle.bundle_stream.get_absolute_url }}">{{ test_run.bundle.bundle_stream }}</a></li>
-<li><a href="{{ test_run.get_absolute_url }}">{{ test_run }}</a></li>
-<li><a href="{% url dashboard_app.views.test_run_hardware_context test_run.analyzer_assigned_uuid %}">{% trans "Hardware Context" %}</a></li>
-{% endblock %}
-
-
-{% block sidebar %}
-{% endblock %}
{% block content %}
-<h2>Hardware Devices</h2>
-<dl>
-{% autopaginate test_run.devices.all as hardware_devices %}
-{% for hardware_device in hardware_devices %}
-<dt>{{ hardware_device.description }}</dt>
-<dd>
-<table>
- {% for attribute in hardware_device.attributes.all %}
- <tr>
- <th>{{ attribute.name }}</th>
- <td>{{ attribute.value }}</td>
- </tr>
- {% endfor %}
+<table class="demo_jui display" id="hardware_devices">
+ <thead>
+ <tr>
+ <th>Description</th>
+ <th>Device Type</th>
+ <th>Attributes</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for hardware_device in test_run.devices.all %}
+ <tr>
+ <td>{{ hardware_device.description }}</td>
+ <td>{{ hardware_device.get_device_type_display }}</td>
+ <td>
+ <dl>
+ {% for attribute in hardware_device.attributes.all %}
+ <dt>{{ attribute.name }}</dt>
+ <dd>{{ attribute.value }}</dd>
+ {% endfor %}
+ </dl>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
</table>
-</dd>
-{% empty %}
-<em>There are no hardware devices associated with this test run</em>
-{% endfor %}
-</dl>
-{% paginate %}
+<script type="text/javascript" charset="utf-8">
+ function fnFormatDetails(oTable, nTr) {
+ var aData = oTable.fnGetData(nTr);
+ return aData[3];
+ }
+
+ $(document).ready(function() {
+ /* Insert a 'details' column to the table */
+ var nCloneTh = document.createElement('th');
+ var nCloneTd = document.createElement('td');
+ nCloneTd.innerHTML = '<img src="{{ STATIC_URL }}images/details_open.png">';
+ nCloneTd.className = "center";
+
+ $('#hardware_devices thead tr').each( function () {
+ this.insertBefore(nCloneTh, this.childNodes[0]);
+ });
+
+ $('#hardware_devices tbody tr').each( function () {
+ this.insertBefore(nCloneTd.cloneNode(true), this.childNodes[0]);
+ });
+
+ /* Initialse DataTables, with no sorting on the 'details' column */
+ var oTable = $('#hardware_devices').dataTable({
+ aoColumnDefs: [
+ { bSortable: false, aTargets: [0] },
+ { bVisible: false, aTargets: [3] }
+ ],
+ aaSorting: [[2, 'asc']],
+ bPaginate: false,
+ bJQueryUI: true
+ });
+ /* Add event listener for opening and closing details. Note that the
+ indicator for showing which row is open is not controlled by DataTables,
+ rather it is done here */
+ $('#hardware_devices tbody td img').live('click', function () {
+ var nTr = this.parentNode.parentNode;
+ if (this.src.match('details_close')) {
+ /* This row is already open - close it */
+ this.src = "{{ STATIC_URL }}images/details_open.png";
+ oTable.fnClose(nTr);
+ } else {
+ /* Open this row */
+ this.src = "{{ STATIC_URL }}images/details_close.png";
+ oTable.fnOpen(nTr, fnFormatDetails(oTable, nTr), 'details');
+ }
+ });
+ });
+</script>
{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/test_run_list.html'
@@ -1,152 +1,65 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content_with_sidebar.html" %}
{% load i18n %}
{% load humanize %}
-{% load pagination_tags %}
-
-{% block extrahead %}
-{{ block.super }}
-<script type="text/javascript" src="{{ STATIC_URL }}lava/js/jquery-1.5.1.min.js"></script>
-<script type="text/javascript" src="{{ STATIC_URL }}lava/js/jquery-ui-1.8.12.custom.min.js"></script>
-<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava/css/Aristo/jquery-ui-1.8.7.custom.css"/>
-{% endblock %}
-
-{% block title %}
-{{ block.super }} | {% trans "Streams" %} | {% trans "Bundle Stream" %} {{ bundle_stream.pathname }}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.bundle_stream_list %}">{% trans "Bundle Streams" %}</a></li>
-<li><a href="{{ bundle_stream.get_absolute_url }}">{{ bundle_stream }}</a></li>
+
+
+{% block content %}
+<div id="master-toolbar-splice" >
+ <span
+ class="length"
+ style="float:left; display:inline-block; width:30%; text-align:left;"></span>
+ <span class="view-as">
+ <input name="radio" type="radio" id="as_bundles"/>
+ <label for="as_bundles">
+ <a
+ id="as_bundles_link"
+ href="{% url dashboard_app.views.bundle_list bundle_stream.pathname %}"
+ >{% trans "Bundles" %}</a>
+ </label>
+ <input name="radio" checked type="radio" id="as_test_runs"/>
+ <label for="as_test_runs">
+ <a
+ id="as_test_runs_link"
+ href="{% url dashboard_app.views.test_run_list bundle_stream.pathname %}"
+ >{% trans "Test Runs" %}</a>
+ </label>
+ </span>
+ <span
+ class="search"
+ style="float:right; display:inline-block; width: 30%; text-align:right"></span>
+</div>
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ oTable = $('#test_runs').dataTable({
+ bJQueryUI: true,
+ sPaginationType: "full_numbers",
+ aaSorting: [[1, "desc"]],
+ iDisplayLength: 25,
+ aLengthMenu: [[10, 25, 50, -1], [10, 25, 50, "All"]],
+ sDom: 'lfr<"#master-toolbar">t<"F"ip>'
+ });
+ // Try hard to make our radio boxes behave as links
+ $("#master-toolbar-splice span.view-as").buttonset();
+ $("#master-toolbar-splice span.view-as input").change(function(event) {
+ var link = $("#" + event.target.id + "_link");
+ location.href=link.attr("href");
+ });
+ // Insane splicing below
+ $("div.dataTables_length").children().appendTo("#master-toolbar-splice span.length");
+ $("div.dataTables_filter").children().appendTo("#master-toolbar-splice span.search");
+ $("#master-toolbar-splice").children().appendTo($("#master-toolbar"));
+ $("div.dataTables_length").remove();
+ $("div.dataTables_filter").remove();
+ $("#master-toolbar-splice").remove();
+ $("#master-toolbar").addClass("ui-widget-header ui-corner-tl ui-corner-tr").css(
+ "padding", "5pt").css("text-align", "center");
+ new FixedHeader(oTable);
+ });
+</script>
+{% include "dashboard_app/_test_run_list_table.html" %}
{% endblock %}
{% block sidebar %}
-<h3>{% trans "About" %}</h3>
-<dl>
- <dt>{% trans "Pathname:" %}</dt>
- <dd>{{ bundle_stream.pathname }}</dd>
- <dt>{% trans "Name:" %}</dt>
- <dd>{{ bundle_stream.name|default:"<i>not set</i>" }}</dd>
-</dl>
-<h3>{% trans "Ownership" %}</h3>
-{% if bundle_stream.user %}
-<p>{% trans "This stream is owned by" %} <q>{{ bundle_stream.user }}</q></p>
-{% else %}
-<p>{% trans "This stream is owned by group called" %} <q>{{ bundle_stream.group }}</q></p>
-{% endif %}
-<h3>{% trans "Access rights" %}</h3>
-<dl>
- <dt>{% trans "Stream type:" %}</dt>
- {% if bundle_stream.is_anonymous %}
- <dd>
- Anonymous stream <a href="#" id="what-are-anonymous-streams">(what is this?)</a>
- <div id="dialog-message" title="{% trans "About anonymous streams" %}">
- <p>The dashboard has several types of containers for test results. One of the
- most common and the oldest one is an <em>anonymous stream</em>. Anonymous
- streams act like public FTP servers. Anyone can download or upload files at
- will. There are some restrictions, nobody can change or remove existing
- files</p>
-
- <p>When a stream is anonymous anonyone can upload new test results and the
- identity of the uploading user is not recorded in the system. Anonymous
- streams have to be public (granting read access to everyone)</p>
-
- <div class="ui-widget">
- <div class="ui-state-highlight ui-corner-all" style="margin-top: 20px; padding: 0.7em">
- <span class="ui-icon ui-icon-info" style="float: left; margin-right: 0.3em;"></span>
- <strong>Note:</strong> A stream can be marked as anonymous in the administration panel
- </div>
- </div>
- </div>
- <script type="text/javascript">
- $(function() {
- $( "#dialog-message" ).dialog({
- autoOpen: false,
- modal: true,
- minWidth: 500,
- buttons: {
- Ok: function() {
- $( this ).dialog( "close" );
- }
- }
- });
- $( "#what-are-anonymous-streams" ).click(function(e) {
- $( "#dialog-message").dialog('open');
- });
- });
- </script>
- </dd>
- <dt>{% trans "Read access:" %}</dt>
- <dd>{% trans "Anyone can download or read test results uploaded here" %}</dd>
- <dt>{% trans "Write access:" %}</dt>
- <dd>{% trans "Anyone can upload test results here" %}</dt>
- {% else %}
- {% if bundle_stream.is_public %}
- <dd>{% trans "Public stream" %}</dd>
- <dt>{% trans "Read access:" %}</dt>
- <dd>{% trans "Anyone can download or read test results uploaded here" %}</dd>
- {% else %}
- <dd>{% trans "Private stream" %}</dd>
- <dt>{% trans "Read access:" %}</dt>
- <dd>{% trans "Only the owner can download or read test results uploaded here" %}</dd>
- {% endif %}
- <dt>{% trans "Write access:" %}</dt>
- <dd>{% trans "Only the owner can upload test results here" %}</dd>
- {% endif %}
-</dl>
-{% endblock %}
-
-
-{% block content %}
-
-{% autopaginate test_run_list %}
-{% if invalid_page %}
- <h3>{% trans "There are no test runs on this page." %}</h3>
- <p>{% trans "Try the" %} <a href="{{ bundle_stream.get_absolute_url }}">{% trans "first page" %}</a> {% trans "instead." %}</p>
-{% else %}
- {% if test_run_list.count %}
- <table class="data">
- <tr>
- <th>
- {% trans "Uploaded On" %}
- <div style="font-size:smaller">
- {% trans "most recent first" %}
- </div>
- </th>
- <th>{% trans "Analyzed" %}</th>
- <th>{% trans "Test" %}</th>
- <th>{% trans "Run" %}</th>
- <th>{% trans "Pass" %}</th>
- <th>{% trans "Fail" %}</th>
- <th>{% trans "Skip" %}</th>
- <th>{% trans "Unknown" %}</th>
- </tr>
- {% for test_run in test_run_list %}
- <tr>
- <td>
- {{ test_run.bundle.uploaded_on|naturalday }}
- {{ test_run.bundle.uploaded_on|time }}
- </td>
- <td>
- {{ test_run.analyzer_assigned_date|timesince }}
- {% trans "ago" %}
- </td>
- <td>{{ test_run.test }}</td>
- <td><a href="{{ test_run.get_absolute_url }}">{{ test_run }}</a></td>
- {% with test_run.get_summary_results as summary %}
- <td>{{ summary.pass|default:0 }}</td>
- <td>{{ summary.fail|default:0 }}</td>
- <td>{{ summary.skip|default:0 }}</td>
- <td>{{ summary.unknown|default:0 }}</td>
- {% endwith %}
- </tr>
- {% endfor %}
- </table>
- {% else %}
- <p>{% trans "There are no test runs in this stream yet." %}</p>
- {% endif %}
-{% paginate %}
-{% endif %}
-
+{% include "dashboard_app/_bundle_stream_sidebar.html" %}
{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/test_run_software_context.html'
@@ -1,57 +1,85 @@
-{% extends "dashboard_app/base.html" %}
+{% extends "dashboard_app/_content.html" %}
{% load i18n %}
-{% load humanize %}
-{% load pagination_tags %}
-
-
-{% block title %}
-{{ block.super }} | {% trans "Test Runs" %} | {{ test_run }} | {% trans "Software Context" %}
-{% endblock %}
-
-
-{% block breadcrumbs %}
-<li><a href="{% url dashboard_app.views.bundle_stream_list %}">{% trans "Bundle Streams" %}</a></li>
-<li><a href="{{ test_run.bundle.bundle_stream.get_absolute_url }}">{{ test_run.bundle.bundle_stream }}</a></li>
-<li><a href="{{ test_run.get_absolute_url }}">{{ test_run }}</a></li>
-<li><a href="{% url dashboard_app.views.test_run_software_context test_run.analyzer_assigned_uuid %}">{% trans "Software Context" %}</a></li>
-{% endblock %}
-
-
-{% block sidebar %}
-{% endblock %}
{% block content %}
-<h2>Software Packages</h2>
-<ul>
-{% autopaginate test_run.packages.all as software_packages %}
-{% for software_package in software_packages %}
- <li>Package <a href="{{software_package.link_to_packages_ubuntu_com}}">{{software_package.name}}</a> version {{software_package.version}}</li>
-{% empty %}
- <em>There are no software packages associated with this test run</em>
-{% endfor %}
-</ul>
-{% paginate %}
-
-<h2>Source Sources</h2>
-<ul>
-{% for software_source in test_run.sources.all %}
- <li>
- {% if software_source.is_hosted_on_launchpad %}
- Launchpad project <a href="{{ software_source.link_to_project }}">{{ software_source.project_name }}</a>
- from bazaar branch <a href="{{ software_source.link_to_branch }}">{{ software_source.branch_url }}</a>
- {% if software_source.is_tag_revision %}
- at tag {{ software_source.branch_tag }}
- {% else %}
- at revision {{ software_source.branch_revision }}
- {% endif %}
- {% else %}
- Project {{software_source.project_name}} from {{software_source.branch_vcs}}
- branch {{software_source.branch_url}} at revision {{software_source.branch_revision}}
- {% endif %}
- </li>
-{% empty %}
- <em>There are no source references associated with this test run</em>
-{% endfor %}
-</ul>
+<div id="tabs">
+ <ul>
+ <li><a href="#tab-software-packages">Software Packages</a></li>
+ <li><a href="#tab-software-sources">Software Sources</a></li>
+ </ul>
+
+ <div id="tab-software-packages">
+ <table class="demo_jui display" id="software_packages">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Version</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for software_package in test_run.packages.all %}
+ <tr>
+ <td><a href="{{software_package.link_to_packages_ubuntu_com}}">{{software_package.name}}</a></td>
+ <td>{{software_package.version}}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+
+ <div id="tab-software-sources">
+ <table class="demo_jui display" id="software_sources">
+ <thead>
+ <tr>
+ <th>Project</th>
+ <th><abbr title="Version Control System">VCS</abbr></th>
+ <th>Branch</th>
+ <th>Tag or revision</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for software_source in test_run.sources.all %}
+ <tr>
+ {% if software_source.is_hosted_on_launchpad %}
+ <td><a href="{{ software_source.link_to_project }}">{{ software_source.project_name }}</a></td>
+ <td>{{software_source.branch_vcs}}</td>
+ <td><a href="{{ software_source.link_to_branch }}">{{ software_source.branch_url }}</a></td>
+ {% if software_source.is_tag_revision %}
+ <td>{{ software_source.branch_tag }}</td>
+ {% else %}
+ <td>{{ software_source.branch_revision }}</td>
+ {% endif %}
+ {% else %}
+ <td>{{software_source.project_name}}</td>
+ <td>{{software_source.branch_vcs}}</td>
+ <td>{{software_source.branch_url}}</td>
+ <td>{{software_source.branch_revision}}</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+</div>
+
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ $('#software_packages').dataTable({
+ bJQueryUI: true,
+ });
+ $('#software_sources').dataTable({
+ bJQueryUI: true
+ });
+ $("#tabs").tabs({
+ cache: true,
+ show: function(event, ui) {
+ var oTable = $('div.dataTables_scrollBody>table.display', ui.panel).dataTable();
+ if ( oTable.length > 0 ) {
+ oTable.fnAdjustColumnSizing();
+ }
+ }
+ });
+ });
+</script>
{% endblock %}
=== modified file 'dashboard_app/tests/views/test_run_detail_view.py'
@@ -42,12 +42,12 @@
self.assertTemplateUsed(response,
"dashboard_app/test_run_detail.html")
- def testrun_invalid_page_view(self):
- invalid_uuid = "0000000-0000-0000-0000-000000000000"
- invalid_test_run_url = reverse("dashboard_app.views.test_run_detail",
- args=[invalid_uuid])
- response = self.client.get(invalid_test_run_url)
- self.assertEqual(response.status_code, 404)
+ #def testrun_invalid_page_view(self):
+ # invalid_uuid = "0000000-0000-0000-0000-000000000000"
+ # invalid_test_run_url = reverse("dashboard_app.views.test_run_detail",
+ # args=[invalid_uuid])
+ # response = self.client.get(invalid_test_run_url)
+ # self.assertEqual(response.status_code, 404)
class TestRunViewAuth(TestCaseWithScenarios):
=== modified file 'dashboard_app/urls.py'
@@ -24,17 +24,24 @@
urlpatterns = patterns(
'dashboard_app.views',
- url(r'^streams/$', 'bundle_stream_list'),
- url(r'^test-results/(?P<pk>[0-9]+)/$', 'test_result_detail'),
- url(r'^test-runs/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/$', 'test_run_detail'),
- url(r'^test-runs/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/software-context$', 'test_run_software_context'),
- url(r'^test-runs/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/hardware-context$', 'test_run_hardware_context'),
- url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)$', 'test_run_list'),
- url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+?)\+bundles$', 'bundle_list'),
- url(r'^attachments/(?P<pk>[0-9]+)/$', 'attachment_detail'),
- url(r'^xml-rpc/', 'dashboard_xml_rpc_handler'),
+ url(r'^$', 'index'),
+ url(r'^ajax/bundle-viewer/(?P<pk>[0-9]+)/$', 'ajax_bundle_viewer'),
+ url(r'^ajax/attachment-viewer/(?P<pk>[0-9]+)/$', 'ajax_attachment_viewer'),
url(r'^data-views/$', 'data_view_list'),
url(r'^data-views/(?P<name>[a-zA-Z0-9-_]+)/$', 'data_view_detail'),
url(r'^reports/$', 'report_list'),
url(r'^reports/(?P<name>[a-zA-Z0-9-_]+)/$', 'report_detail'),
+ url(r'^tests/$', 'test_list'),
+ url(r'^tests/(?P<test_id>[^/]+)/$', 'test_detail'),
+ url(r'^xml-rpc/', 'dashboard_xml_rpc_handler'),
+ url(r'^streams/$', 'bundle_stream_list'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)bundles/$', 'bundle_list'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)bundles/(?P<content_sha1>[0-9a-z]+)/$', 'bundle_detail'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/$', 'test_run_detail'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/attachments$', 'attachment_list'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/attachments/(?P<pk>[0-9]+)/$', 'attachment_detail'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/result/(?P<relative_index>[0-9]+)/$', 'test_result_detail'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/hardware-context/$', 'test_run_hardware_context'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/software-context/$', 'test_run_software_context'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/_-]+)test-runs/$', 'test_run_list'),
)
=== modified file 'dashboard_app/views.py'
@@ -28,11 +28,22 @@
from django.http import (HttpResponse, Http404)
from django.shortcuts import render_to_response
from django.template import RequestContext
+from django.views.generic.list_detail import object_list, object_detail
from dashboard_app.dataview import DataView, DataViewRepository
from dashboard_app.dispatcher import DjangoXMLRPCDispatcher
-from dashboard_app.models import Attachment, BundleStream, TestRun, TestResult, DataReport
+from dashboard_app.models import (
+ Attachment,
+ Bundle,
+ BundleStream,
+ DataReport,
+ Test,
+ TestCase,
+ TestResult,
+ TestRun,
+)
from dashboard_app.xmlrpc import DashboardAPI
+from dashboard_app.bread_crumbs import BreadCrumb, BreadCrumbTrail
def _get_dashboard_dispatcher():
@@ -123,15 +134,23 @@
return xml_rpc_handler(request, DashboardDispatcher)
+@BreadCrumb("Dashboard")
+def index(request):
+ return render_to_response(
+ "dashboard_app/index.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(index)
+ }, RequestContext(request))
+
+
+@BreadCrumb("Bundle Streams", parent=index)
def bundle_stream_list(request):
"""
List of bundle streams.
-
- The list is paginated and dynamically depends on the currently
- logged in user.
"""
return render_to_response(
'dashboard_app/bundle_stream_list.html', {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ bundle_stream_list),
"bundle_stream_list": BundleStream.objects.accessible_by_principal(request.user).order_by('pathname'),
'has_personal_streams': (
request.user.is_authenticated() and
@@ -144,12 +163,84 @@
)
+@BreadCrumb(
+ "Bundles in {pathname}",
+ parent=bundle_stream_list,
+ needs=['pathname'])
+def bundle_list(request, pathname):
+ """
+ List of bundles in a specified bundle stream.
+ """
+ bundle_stream = get_restricted_object_or_404(
+ BundleStream,
+ lambda bundle_stream: bundle_stream,
+ request.user,
+ pathname=pathname
+ )
+ return object_list(
+ request,
+ queryset=bundle_stream.bundles.all().order_by('-uploaded_on'),
+ template_name="dashboard_app/bundle_list.html",
+ template_object_name="bundle",
+ extra_context={
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ bundle_list,
+ pathname=pathname),
+ "bundle_stream": bundle_stream
+ })
+
+
+@BreadCrumb(
+ "Bundle {content_sha1}",
+ parent=bundle_list,
+ needs=['pathname', 'content_sha1'])
+def bundle_detail(request, pathname, content_sha1):
+ """
+ Detail about a bundle from a particular stream
+ """
+ bundle_stream = get_restricted_object_or_404(
+ BundleStream,
+ lambda bundle_stream: bundle_stream,
+ request.user,
+ pathname=pathname
+ )
+ return object_detail(
+ request,
+ queryset=bundle_stream.bundles.all(),
+ slug=content_sha1,
+ slug_field="content_sha1",
+ template_name="dashboard_app/bundle_detail.html",
+ template_object_name="bundle",
+ extra_context={
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ bundle_detail,
+ pathname=pathname,
+ content_sha1=content_sha1),
+ "bundle_stream": bundle_stream
+ })
+
+
+def ajax_bundle_viewer(request, pk):
+ bundle = get_restricted_object_or_404(
+ Bundle,
+ lambda bundle: bundle.bundle_stream,
+ request.user,
+ pk=pk
+ )
+ return render_to_response(
+ "dashboard_app/_ajax_bundle_viewer.html", {
+ "bundle": bundle
+ },
+ RequestContext(request))
+
+
+@BreadCrumb(
+ "Test runs in {pathname}",
+ parent=bundle_stream_list,
+ needs=['pathname'])
def test_run_list(request, pathname):
"""
List of test runs in a specified bundle stream.
-
- The list is paginated and dynamically depends on the currently
- logged in user.
"""
bundle_stream = get_restricted_object_or_404(
BundleStream,
@@ -159,94 +250,183 @@
)
return render_to_response(
'dashboard_app/test_run_list.html', {
- "test_run_list": TestRun.objects.filter(bundle__bundle_stream=bundle_stream).order_by('-bundle__uploaded_on'),
- "bundle_stream": bundle_stream,
- }, RequestContext(request)
- )
-
-
-def bundle_list(request, pathname):
- """
- List of bundles in a specified bundle stream.
-
- The list is paginated and dynamically depends on the currently logged in
- user.
- """
- bundle_stream = get_restricted_object_or_404(
- BundleStream,
- lambda bundle_stream: bundle_stream,
- request.user,
- pathname=pathname
- )
- return render_to_response(
- 'dashboard_app/bundle_list.html', {
- "bundle_list": bundle_stream.bundles.all().order_by('-uploaded_on'),
- "bundle_stream": bundle_stream,
- }, RequestContext(request)
- )
-
-
-def _test_run_view(template_name):
- def view(request, analyzer_assigned_uuid):
- test_run = get_restricted_object_or_404(
- TestRun,
- lambda test_run: test_run.bundle.bundle_stream,
- request.user,
- analyzer_assigned_uuid = analyzer_assigned_uuid
- )
- return render_to_response(
- template_name, {
- "test_run": test_run
- }, RequestContext(request)
- )
- return view
-
-
-test_run_detail = _test_run_view("dashboard_app/test_run_detail.html")
-test_run_software_context = _test_run_view("dashboard_app/test_run_software_context.html")
-test_run_hardware_context = _test_run_view("dashboard_app/test_run_hardware_context.html")
-
-
-def test_result_detail(request, pk):
- test_result = get_restricted_object_or_404(
- TestResult,
- lambda test_result: test_result.test_run.bundle.bundle_stream,
- request.user,
- pk = pk
- )
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ test_run_list,
+ pathname=pathname),
+ "test_run_list": TestRun.objects.filter(
+ bundle__bundle_stream=bundle_stream),
+ "bundle_stream": bundle_stream,
+ }, RequestContext(request)
+ )
+
+
+@BreadCrumb(
+ "Run {analyzer_assigned_uuid}",
+ parent=bundle_detail,
+ needs=['pathname', 'content_sha1', 'analyzer_assigned_uuid'])
+def test_run_detail(request, pathname, content_sha1, analyzer_assigned_uuid):
+ test_run = get_restricted_object_or_404(
+ TestRun,
+ lambda test_run: test_run.bundle.bundle_stream,
+ request.user,
+ analyzer_assigned_uuid=analyzer_assigned_uuid
+ )
+ return render_to_response(
+ "dashboard_app/test_run_detail.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ test_run_detail,
+ pathname=pathname,
+ content_sha1=content_sha1,
+ analyzer_assigned_uuid=analyzer_assigned_uuid),
+ "test_run": test_run
+ }, RequestContext(request))
+
+
+@BreadCrumb(
+ "Software Context",
+ parent=test_run_detail,
+ needs=['pathname', 'content_sha1', 'analyzer_assigned_uuid'])
+def test_run_software_context(request, pathname, content_sha1, analyzer_assigned_uuid):
+ test_run = get_restricted_object_or_404(
+ TestRun,
+ lambda test_run: test_run.bundle.bundle_stream,
+ request.user,
+ analyzer_assigned_uuid=analyzer_assigned_uuid
+ )
+ return render_to_response(
+ "dashboard_app/test_run_software_context.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ test_run_software_context,
+ pathname=pathname,
+ content_sha1=content_sha1,
+ analyzer_assigned_uuid=analyzer_assigned_uuid),
+ "test_run": test_run
+ }, RequestContext(request))
+
+
+@BreadCrumb(
+ "Hardware Context",
+ parent=test_run_detail,
+ needs=['pathname', 'content_sha1', 'analyzer_assigned_uuid'])
+def test_run_hardware_context(request, pathname, content_sha1, analyzer_assigned_uuid):
+ test_run = get_restricted_object_or_404(
+ TestRun,
+ lambda test_run: test_run.bundle.bundle_stream,
+ request.user,
+ analyzer_assigned_uuid=analyzer_assigned_uuid
+ )
+ return render_to_response(
+ "dashboard_app/test_run_hardware_context.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ test_run_hardware_context,
+ pathname=pathname,
+ content_sha1=content_sha1,
+ analyzer_assigned_uuid=analyzer_assigned_uuid),
+ "test_run": test_run
+ }, RequestContext(request))
+
+
+@BreadCrumb(
+ "Result {relative_index}",
+ parent=test_run_detail,
+ needs=['pathname', 'content_sha1', 'analyzer_assigned_uuid', 'relative_index'])
+def test_result_detail(request, pathname, content_sha1, analyzer_assigned_uuid, relative_index):
+ test_run = get_restricted_object_or_404(
+ TestRun,
+ lambda test_run: test_run.bundle.bundle_stream,
+ request.user,
+ analyzer_assigned_uuid=analyzer_assigned_uuid
+ )
+ test_result = test_run.test_results.get(relative_index=relative_index)
return render_to_response(
"dashboard_app/test_result_detail.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ test_result_detail,
+ pathname=pathname,
+ content_sha1=content_sha1,
+ analyzer_assigned_uuid=analyzer_assigned_uuid,
+ relative_index=relative_index),
"test_result": test_result
- }, RequestContext(request)
+ }, RequestContext(request))
+
+
+@BreadCrumb(
+ "Attachments",
+ parent=test_run_detail,
+ needs=['pathname', 'content_sha1', 'analyzer_assigned_uuid'])
+def attachment_list(request, pathname, content_sha1, analyzer_assigned_uuid):
+ test_run = get_restricted_object_or_404(
+ TestRun,
+ lambda test_run: test_run.bundle.bundle_stream,
+ request.user,
+ analyzer_assigned_uuid=analyzer_assigned_uuid
)
-
-
-def attachment_detail(request, pk):
+ return object_list(
+ request,
+ queryset=test_run.attachments.all(),
+ template_name="dashboard_app/attachment_list.html",
+ template_object_name="attachment",
+ extra_context={
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ attachment_list,
+ pathname=pathname,
+ content_sha1=content_sha1,
+ analyzer_assigned_uuid=analyzer_assigned_uuid),
+ 'test_run': test_run})
+
+
+@BreadCrumb(
+ "{content_filename}",
+ parent=attachment_list,
+ needs=['pathname', 'content_sha1', 'analyzer_assigned_uuid', 'content_filename'])
+def attachment_detail(request, pathname, content_sha1, analyzer_assigned_uuid, pk):
attachment = get_restricted_object_or_404(
Attachment,
- lambda attachment: attachment.content_object.bundle.bundle_stream,
+ lambda attachment: attachment.test_run.bundle.bundle_stream,
request.user,
pk = pk
)
+ return render_to_response(
+ "dashboard_app/attachment_detail.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ attachment_detail,
+ pathname=pathname,
+ content_sha1=content_sha1,
+ analyzer_assigned_uuid=analyzer_assigned_uuid,
+ content_filename=attachment.content_filename),
+ "attachment": attachment,
+ }, RequestContext(request))
+
+
+def ajax_attachment_viewer(request, pk):
+ attachment = get_restricted_object_or_404(
+ Attachment,
+ lambda attachment: attachment.test_run.bundle.bundle_stream,
+ request.user,
+ pk=pk
+ )
if attachment.mime_type == "text/plain":
data = attachment.get_content_if_possible(mirror=request.user.is_authenticated())
else:
data = None
return render_to_response(
- "dashboard_app/attachment_detail.html", {
+ "dashboard_app/_ajax_attachment_viewer.html", {
+ "attachment": attachment,
"lines": data.splitlines() if data else None,
- "attachment": attachment,
- }, RequestContext(request)
- )
-
-
+ },
+ RequestContext(request))
+
+
+@BreadCrumb("Reports", parent=index)
def report_list(request):
return render_to_response(
"dashboard_app/report_list.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(report_list),
"report_list": DataReport.repository.all()
}, RequestContext(request))
+@BreadCrumb("{title}", parent=report_list, needs=['name'])
def report_detail(request, name):
try:
report = DataReport.repository.get(name=name)
@@ -254,18 +434,22 @@
raise Http404('No report matches given name.')
return render_to_response(
"dashboard_app/report_detail.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(report_detail, name=report.name, title=report.title),
"report": report,
}, RequestContext(request))
+@BreadCrumb("Data views", parent=index)
def data_view_list(request):
repo = DataViewRepository.get_instance()
return render_to_response(
"dashboard_app/data_view_list.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(data_view_list),
"data_view_list": repo.data_views
}, RequestContext(request))
+@BreadCrumb("Details of {name}", parent=data_view_list, needs=['name'])
def data_view_detail(request, name):
repo = DataViewRepository.get_instance()
try:
@@ -274,5 +458,32 @@
raise Http404('No data view matches the given query.')
return render_to_response(
"dashboard_app/data_view_detail.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(data_view_detail, name=data_view.name, summary=data_view.summary),
"data_view": data_view
}, RequestContext(request))
+
+
+@BreadCrumb("Tests", parent=index)
+def test_list(request):
+ return object_list(
+ request,
+ queryset=Test.objects.all(),
+ template_name="dashboard_app/test_list.html",
+ template_object_name="test",
+ extra_context={
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(test_list)
+ })
+
+
+@BreadCrumb("Details of {test_id}", parent=test_list, needs=['test_id'])
+def test_detail(request, test_id):
+ return object_detail(
+ request,
+ queryset=Test.objects.all(),
+ slug=test_id,
+ slug_field="test_id",
+ template_name="dashboard_app/test_detail.html",
+ template_object_name="test",
+ extra_context={
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(test_detail, test_id=test_id)
+ })
=== added directory 'production'
=== added directory 'production/reports'
=== added directory 'production/views'