Record and display update logs

At the moment it's a bit difficult to get update logs out of the
environment in which the update script is being run. In order to make
the logs more accessible, create a LayerUpdate model to record the
output of update_layer.py separately for each layerbranch and tie the
created LayerUpdates together with a single Update model per session.

We provide two ways to look at this - a Tools->Updates page for
logged-in users, and there's also an "Updates" tab on each layer that is
accessible to anyone; which one is useful depends on whether you are
looking at the index as a whole or an individual layer.

Update records older than 30 days are deleted automatically by default.

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
This commit is contained in:
Paul Eggleton 2016-11-07 12:15:12 +13:00
parent cc1d82f893
commit 43203c578c
15 changed files with 487 additions and 120 deletions

2
TODO
View File

@ -35,10 +35,8 @@ Other
* Create simple script to check for unlisted layer subdirectories in all repos * Create simple script to check for unlisted layer subdirectories in all repos
* Auto-detect more values from github pages? * Auto-detect more values from github pages?
* Ability for submitters to get email notification about publication? * Ability for submitters to get email notification about publication?
* Update script still seems not to be always printing layer name on parsing warnings/errors
* Update script could send warnings when parsing layers to maintainers? (optional) * Update script could send warnings when parsing layers to maintainers? (optional)
* Click on OE-Classic graph element to go to query? * Click on OE-Classic graph element to go to query?
* Use bar instead of pie graphs for OE-Classic statistics * Use bar instead of pie graphs for OE-Classic statistics
* Ensure OE-Core appears before meta-oe in layer list * Ensure OE-Core appears before meta-oe in layer list
* Ability for reviewers to comment before publishing a layer? * Ability for reviewers to comment before publishing a layer?
* Record update & parse errors against recipe/layer

View File

@ -74,6 +74,12 @@ class LayerDependencyAdmin(CompareVersionAdmin):
class LayerNoteAdmin(CompareVersionAdmin): class LayerNoteAdmin(CompareVersionAdmin):
list_filter = ['layer__name'] list_filter = ['layer__name']
class UpdateAdmin(admin.ModelAdmin):
pass
class LayerUpdateAdmin(admin.ModelAdmin):
list_filter = ['update__started', 'layerbranch__layer__name', 'layerbranch__branch__name']
class RecipeAdmin(admin.ModelAdmin): class RecipeAdmin(admin.ModelAdmin):
search_fields = ['filename', 'pn'] search_fields = ['filename', 'pn']
list_filter = ['layerbranch__layer__name', 'layerbranch__branch__name'] list_filter = ['layerbranch__layer__name', 'layerbranch__branch__name']
@ -144,6 +150,8 @@ admin.site.register(LayerBranch, LayerBranchAdmin)
admin.site.register(LayerMaintainer, LayerMaintainerAdmin) admin.site.register(LayerMaintainer, LayerMaintainerAdmin)
admin.site.register(LayerDependency, LayerDependencyAdmin) admin.site.register(LayerDependency, LayerDependencyAdmin)
admin.site.register(LayerNote, LayerNoteAdmin) admin.site.register(LayerNote, LayerNoteAdmin)
admin.site.register(Update, UpdateAdmin)
admin.site.register(LayerUpdate, LayerUpdateAdmin)
admin.site.register(Recipe, RecipeAdmin) admin.site.register(Recipe, RecipeAdmin)
admin.site.register(RecipeFileDependency) admin.site.register(RecipeFileDependency)
admin.site.register(Machine, MachineAdmin) admin.site.register(Machine, MachineAdmin)

View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('layerindex', '0004_layerdependency_required'),
]
operations = [
migrations.CreateModel(
name='LayerUpdate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('started', models.DateTimeField()),
('finished', models.DateTimeField()),
('errors', models.IntegerField(default=0)),
('warnings', models.IntegerField(default=0)),
('log', models.TextField(blank=True)),
('layerbranch', models.ForeignKey(to='layerindex.LayerBranch')),
],
),
migrations.CreateModel(
name='Update',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('started', models.DateTimeField()),
('finished', models.DateTimeField(null=True, blank=True)),
('log', models.TextField(blank=True)),
('reload', models.BooleanField(help_text='Was this update a reload?', verbose_name='Reloaded', default=False)),
],
),
migrations.AlterField(
model_name='branch',
name='name',
field=models.CharField(max_length=50, verbose_name='Branch name'),
),
migrations.AddField(
model_name='layerupdate',
name='update',
field=models.ForeignKey(to='layerindex.Update'),
),
]

View File

@ -31,7 +31,7 @@ class PythonEnvironment(models.Model):
class Branch(models.Model): class Branch(models.Model):
name = models.CharField(max_length=50) name = models.CharField('Branch name', max_length=50)
bitbake_branch = models.CharField(max_length=50) bitbake_branch = models.CharField(max_length=50)
short_description = models.CharField(max_length=50, blank=True) short_description = models.CharField(max_length=50, blank=True)
sort_priority = models.IntegerField(blank=True, null=True) sort_priority = models.IntegerField(blank=True, null=True)
@ -47,6 +47,16 @@ class Branch(models.Model):
return self.name return self.name
class Update(models.Model):
started = models.DateTimeField()
finished = models.DateTimeField(blank=True, null=True)
log = models.TextField(blank=True)
reload = models.BooleanField('Reloaded', default=False, help_text='Was this update a reload?')
def __str__(self):
return '%s' % self.started
class LayerItem(models.Model): class LayerItem(models.Model):
LAYER_STATUS_CHOICES = ( LAYER_STATUS_CHOICES = (
('N', 'New'), ('N', 'New'),
@ -255,6 +265,31 @@ class LayerNote(models.Model):
return "%s: %s" % (self.layer.name, self.text) return "%s: %s" % (self.layer.name, self.text)
class LayerUpdate(models.Model):
layerbranch = models.ForeignKey(LayerBranch)
update = models.ForeignKey(Update)
started = models.DateTimeField()
finished = models.DateTimeField()
errors = models.IntegerField(default=0)
warnings = models.IntegerField(default=0)
log = models.TextField(blank=True)
def save(self):
warnings = 0
errors = 0
for line in self.log.splitlines():
if line.startswith('WARNING:'):
warnings += 1
elif line.startswith('ERROR:'):
errors += 1
self.warnings = warnings
self.errors = errors
super(LayerUpdate, self).save()
def __str__(self):
return "%s: %s: %s" % (self.layerbranch.layer.name, self.layerbranch.branch.name, self.started)
class Recipe(models.Model): class Recipe(models.Model):
layerbranch = models.ForeignKey(LayerBranch) layerbranch = models.ForeignKey(LayerBranch)
filename = models.CharField(max_length=255) filename = models.CharField(max_length=255)

View File

@ -14,6 +14,7 @@ import optparse
import logging import logging
import subprocess import subprocess
import signal import signal
from datetime import datetime, timedelta
from distutils.version import LooseVersion from distutils.version import LooseVersion
import utils import utils
from layerconfparse import LayerConfParse from layerconfparse import LayerConfParse
@ -41,10 +42,24 @@ def run_command_interruptible(cmd):
""" """
signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN)
try: try:
ret = subprocess.call(cmd, cwd=os.path.dirname(sys.argv[0]), shell=True, preexec_fn=reenable_sigint) process = subprocess.Popen(
cmd, cwd=os.path.dirname(sys.argv[0]), shell=True, preexec_fn=reenable_sigint, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
buf = ''
while True:
out = process.stdout.read(1)
out = out.decode('utf-8')
if out:
sys.stdout.write(out)
sys.stdout.flush()
buf += out
elif out == '' and process.poll() != None:
break
finally: finally:
signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL)
return ret return process.returncode, buf
def main(): def main():
@ -93,7 +108,7 @@ def main():
utils.setup_django() utils.setup_django()
import settings import settings
from layerindex.models import Branch, LayerItem, LayerDependency from layerindex.models import Branch, LayerItem, Update, LayerUpdate
logger.setLevel(options.loglevel) logger.setLevel(options.loglevel)
@ -126,8 +141,21 @@ def main():
if not os.path.exists(fetchdir): if not os.path.exists(fetchdir):
os.makedirs(fetchdir) os.makedirs(fetchdir)
fetchedrepos = [] fetchedrepos = []
failedrepos = [] failedrepos = {}
listhandler = utils.ListHandler()
listhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(listhandler)
update = Update()
update.started = datetime.now()
if options.fullreload or options.reload:
update.reload = True
else:
update.reload = False
if not options.dryrun:
update.save()
try:
lockfn = os.path.join(fetchdir, "layerindex.lock") lockfn = os.path.join(fetchdir, "layerindex.lock")
lockfile = utils.lock_file(lockfn) lockfile = utils.lock_file(lockfn)
if not lockfile: if not lockfile:
@ -147,12 +175,12 @@ def main():
out = None out = None
try: try:
if not os.path.exists(repodir): if not os.path.exists(repodir):
out = utils.runcmd("git clone %s %s" % (layer.vcs_url, urldir), fetchdir, logger=logger) out = utils.runcmd("git clone %s %s" % (layer.vcs_url, urldir), fetchdir, logger=logger, printerr=False)
else: else:
out = utils.runcmd("git fetch", repodir, logger=logger) out = utils.runcmd("git fetch", repodir, logger=logger, printerr=False)
except Exception as e: except subprocess.CalledProcessError as e:
logger.error("Fetch of layer %s failed: %s" % (layer.name, str(e))) logger.error("Fetch of layer %s failed: %s" % (layer.name, e.output))
failedrepos.append(layer.vcs_url) failedrepos[layer.vcs_url] = e.output
continue continue
fetchedrepos.append(layer.vcs_url) fetchedrepos.append(layer.vcs_url)
@ -173,8 +201,20 @@ def main():
last_rev = {} last_rev = {}
for branch in branches: for branch in branches:
for layer in layerquery: for layer in layerquery:
if layer.vcs_url in failedrepos: layerupdate = LayerUpdate()
logger.info("Skipping update of layer %s as fetch of repository %s failed" % (layer.name, layer.vcs_url)) layerupdate.update = update
errmsg = failedrepos.get(layer.vcs_url, '')
if errmsg:
logger.info("Skipping update of layer %s as fetch of repository %s failed:\n%s" % (layer.name, layer.vcs_url, errmsg))
layerbranch = layer.get_layerbranch(branch)
if layerbranch:
layerupdate.layerbranch = layerbranch
layerupdate.started = datetime.now()
layerupdate.finished = datetime.now()
layerupdate.log = 'ERROR: fetch failed: %s' % errmsg
if not options.dryrun:
layerupdate.save()
continue continue
urldir = layer.get_fetch_dir() urldir = layer.get_fetch_dir()
@ -199,8 +239,11 @@ def main():
cmd += ' -d' cmd += ' -d'
elif options.loglevel == logging.ERROR: elif options.loglevel == logging.ERROR:
cmd += ' -q' cmd += ' -q'
logger.debug('Running layer update command: %s' % cmd) logger.debug('Running layer update command: %s' % cmd)
ret = run_command_interruptible(cmd) layerupdate.started = datetime.now()
ret, output = run_command_interruptible(cmd)
layerupdate.finished = datetime.now()
# We need to get layerbranch here because it might not have existed until # We need to get layerbranch here because it might not have existed until
# layer_update.py created it, but it still may not create one (e.g. if subdir # layer_update.py created it, but it still may not create one (e.g. if subdir
@ -208,6 +251,10 @@ def main():
layerbranch = layer.get_layerbranch(branch) layerbranch = layer.get_layerbranch(branch)
if layerbranch: if layerbranch:
last_rev[layerbranch] = layerbranch.vcs_last_rev last_rev[layerbranch] = layerbranch.vcs_last_rev
layerupdate.layerbranch = layerbranch
layerupdate.log = output
if not options.dryrun:
layerupdate.save()
if ret == 254: if ret == 254:
# Interrupted by user, break out of loop # Interrupted by user, break out of loop
@ -246,11 +293,20 @@ def main():
finally: finally:
layerconfparser.shutdown() layerconfparser.shutdown()
finally: finally:
utils.unlock_file(lockfile) utils.unlock_file(lockfile)
finally:
update.log = ''.join(listhandler.read())
update.finished = datetime.now()
if not options.dryrun:
update.save()
if not options.dryrun:
# Purge old update records
update_purge_days = getattr(settings, 'UPDATE_PURGE_DAYS', 30)
Update.objects.filter(started__lte=datetime.now()-timedelta(days=update_purge_days)).delete()
sys.exit(0) sys.exit(0)

View File

@ -8,7 +8,7 @@ from django.conf.urls import *
from django.views.generic import TemplateView, DetailView, ListView, RedirectView from django.views.generic import TemplateView, DetailView, ListView, RedirectView
from django.views.defaults import page_not_found from django.views.defaults import page_not_found
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, PlainTextListView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, HistoryListView, EditProfileFormView, AdvancedRecipeSearchView, BulkChangeView, BulkChangeSearchView, bulk_change_edit_view, bulk_change_patch_view, BulkChangeDeleteView, RecipeDetailView, RedirectParamsView, ClassicRecipeSearchView, ClassicRecipeDetailView, ClassicRecipeStatsView from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, PlainTextListView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, HistoryListView, EditProfileFormView, AdvancedRecipeSearchView, BulkChangeView, BulkChangeSearchView, bulk_change_edit_view, bulk_change_patch_view, BulkChangeDeleteView, RecipeDetailView, RedirectParamsView, ClassicRecipeSearchView, ClassicRecipeDetailView, ClassicRecipeStatsView, LayerUpdateDetailView, UpdateListView, UpdateDetailView
from layerindex.models import LayerItem, Recipe, RecipeChangeset from layerindex.models import LayerItem, Recipe, RecipeChangeset
from rest_framework import routers from rest_framework import routers
from . import restviews from . import restviews
@ -67,6 +67,10 @@ urlpatterns = patterns('',
template_name='layerindex/recipedetail.html'), template_name='layerindex/recipedetail.html'),
name='recipe'), name='recipe'),
url(r'^layer/(?P<name>[-\w]+)/publish/$', 'layerindex.views.publish', name="publish"), url(r'^layer/(?P<name>[-\w]+)/publish/$', 'layerindex.views.publish', name="publish"),
url(r'^layerupdate/(?P<pk>[-\w]+)/$',
LayerUpdateDetailView.as_view(
template_name='layerindex/layerupdate.html'),
name='layerupdate'),
url(r'^bulkchange/$', url(r'^bulkchange/$',
BulkChangeView.as_view( BulkChangeView.as_view(
template_name='layerindex/bulkchange.html'), template_name='layerindex/bulkchange.html'),
@ -97,6 +101,14 @@ urlpatterns = patterns('',
# context_object_name='recipe_list', # context_object_name='recipe_list',
# template_name='layerindex/rawrecipes.txt'), # template_name='layerindex/rawrecipes.txt'),
# name='recipe_list_raw'), # name='recipe_list_raw'),
url(r'^updates/$',
UpdateListView.as_view(
template_name='layerindex/updatelist.html'),
name='update_list'),
url(r'^updates/(?P<pk>[-\w]+)/$',
UpdateDetailView.as_view(
template_name='layerindex/updatedetail.html'),
name='update'),
url(r'^history/$', url(r'^history/$',
HistoryListView.as_view( HistoryListView.as_view(
template_name='layerindex/history.html'), template_name='layerindex/history.html'),

View File

@ -1,13 +1,13 @@
# layerindex-web - Branch-based URL definitions # layerindex-web - Branch-based URL definitions
# #
# Copyright (C) 2013 Intel Corporation # Copyright (C) 2013-2016 Intel Corporation
# #
# Licensed under the MIT license, see COPYING.MIT for details # Licensed under the MIT license, see COPYING.MIT for details
from django.conf.urls import * from django.conf.urls import *
from django.views.defaults import page_not_found from django.views.defaults import page_not_found
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from layerindex.views import LayerListView, RecipeSearchView, MachineSearchView, DistroSearchView, PlainTextListView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, RedirectParamsView, DuplicatesView from layerindex.views import LayerListView, RecipeSearchView, MachineSearchView, DistroSearchView, PlainTextListView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, RedirectParamsView, DuplicatesView, LayerUpdateDetailView
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', url(r'^$',

View File

@ -223,6 +223,19 @@ def logger_create(name):
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
return logger return logger
class ListHandler(logging.Handler):
"""Logging handler which accumulates formatted log records in a list, returning the list on demand"""
def __init__(self):
self.log = []
logging.Handler.__init__(self, logging.WARNING)
def emit(self, record):
self.log.append('%s\n' % self.format(record))
def read(self):
log = self.log
self.log = []
return log
def lock_file(fn): def lock_file(fn):
starttime = time.time() starttime = time.time()
while True: while True:

View File

@ -1,6 +1,6 @@
# layerindex-web - view definitions # layerindex-web - view definitions
# #
# Copyright (C) 2013-2014 Intel Corporation # Copyright (C) 2013-2016 Intel Corporation
# #
# Licensed under the MIT license, see COPYING.MIT for details # Licensed under the MIT license, see COPYING.MIT for details
@ -10,7 +10,7 @@ from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidde
from django.core.urlresolvers import reverse, reverse_lazy, resolve from django.core.urlresolvers import reverse, reverse_lazy, resolve
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.template import RequestContext from django.template import RequestContext
from layerindex.models import Branch, LayerItem, LayerMaintainer, LayerBranch, LayerDependency, LayerNote, Recipe, Machine, Distro, BBClass, BBAppend, RecipeChange, RecipeChangeset, ClassicRecipe from layerindex.models import Branch, LayerItem, LayerMaintainer, LayerBranch, LayerDependency, LayerNote, Update, LayerUpdate, Recipe, Machine, Distro, BBClass, BBAppend, RecipeChange, RecipeChangeset, ClassicRecipe
from datetime import datetime from datetime import datetime
from django.views.generic import TemplateView, DetailView, ListView from django.views.generic import TemplateView, DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
@ -18,7 +18,7 @@ from django.views.generic.base import RedirectView
from layerindex.forms import EditLayerForm, LayerMaintainerFormSet, EditNoteForm, EditProfileForm, RecipeChangesetForm, AdvancedRecipeSearchForm, BulkChangeEditFormSet, ClassicRecipeForm, ClassicRecipeSearchForm from layerindex.forms import EditLayerForm, LayerMaintainerFormSet, EditNoteForm, EditProfileForm, RecipeChangesetForm, AdvancedRecipeSearchForm, BulkChangeEditFormSet, ClassicRecipeForm, ClassicRecipeSearchForm
from django.db import transaction from django.db import transaction
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from django.db.models import Q, Count from django.db.models import Q, Count, Sum
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.template.loader import get_template from django.template.loader import get_template
from django.template import Context from django.template import Context
@ -328,6 +328,7 @@ class LayerDetailView(DetailView):
context['distros'] = layerbranch.distro_set.order_by('name') context['distros'] = layerbranch.distro_set.order_by('name')
context['appends'] = layerbranch.bbappend_set.order_by('filename') context['appends'] = layerbranch.bbappend_set.order_by('filename')
context['classes'] = layerbranch.bbclass_set.order_by('name') context['classes'] = layerbranch.bbclass_set.order_by('name')
context['updates'] = layerbranch.layerupdate_set.order_by('-started')
context['url_branch'] = self.kwargs['branch'] context['url_branch'] = self.kwargs['branch']
context['this_url_name'] = resolve(self.request.path_info).url_name context['this_url_name'] = resolve(self.request.path_info).url_name
return context return context
@ -599,6 +600,29 @@ class MachineSearchView(ListView):
return context return context
class UpdateListView(ListView):
context_object_name = "updates"
paginate_by = 50
def get_queryset(self):
return Update.objects.all().order_by('-started').annotate(errors=Sum('layerupdate__errors'), warnings=Sum('layerupdate__warnings'))
class UpdateDetailView(DetailView):
model = Update
def get_context_data(self, **kwargs):
context = super(UpdateDetailView, self).get_context_data(**kwargs)
update = self.get_object()
if update:
context['layerupdates'] = update.layerupdate_set.exclude(log__isnull=True).exclude(log__exact='')
return context
class LayerUpdateDetailView(DetailView):
model = LayerUpdate
class DistroSearchView(ListView): class DistroSearchView(ListView):
context_object_name = 'distro_list' context_object_name = 'distro_list'
paginate_by = 50 paginate_by = 50

View File

@ -211,6 +211,9 @@ BITBAKE_REPO_URL = "git://git.openembedded.org/bitbake"
# Core layer to be used by the update script for basic BitBake configuration # Core layer to be used by the update script for basic BitBake configuration
CORE_LAYER_NAME = "openembedded-core" CORE_LAYER_NAME = "openembedded-core"
# Update records older than this number of days will be deleted every update
UPDATE_PURGE_DAYS = 30
# Settings for layer submission feature # Settings for layer submission feature
SUBMIT_EMAIL_FROM = 'noreply@example.com' SUBMIT_EMAIL_FROM = 'noreply@example.com'
SUBMIT_EMAIL_SUBJECT = 'OE Layerindex layer submission' SUBMIT_EMAIL_SUBJECT = 'OE Layerindex layer submission'

View File

@ -72,6 +72,7 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="{% url 'bulk_change' %}">Bulk Change</a></li> <li><a href="{% url 'bulk_change' %}">Bulk Change</a></li>
<li><a href="{% url 'duplicates' 'master' %}">Duplicates</a></li> <li><a href="{% url 'duplicates' 'master' %}">Duplicates</a></li>
<li><a href="{% url 'update_list' %}">Updates</a></li>
</ul> </ul>
</li> </li>
{% endif %} {% endif %}

View File

@ -181,6 +181,9 @@
{% if distros.count > 0 %} {% if distros.count > 0 %}
<li><a href="#distros" data-toggle="tab">Distros</a></li> <li><a href="#distros" data-toggle="tab">Distros</a></li>
{% endif %} {% endif %}
{% if updates.count > 0 %}
<li><a href="#updates" data-toggle="tab">Updates</a></li>
{% endif %}
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
@ -298,6 +301,40 @@
</table> </table>
</div> </div>
{% endif %} {% endif %}
{% if updates.count > 0 %}
<div class="tab-pane" id="updates">
<div class="navbar">
<div class="navbar-inner">
<a class="brand pull-left">{{ layeritem.name }} updates</a>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>Date/time</th>
<th>Errors</th>
<th>Warnings</th>
</tr>
</thead>
<tbody>
{% for update in updates %}
<tr>
<td>
{% if update.log %}
<a href="{% url 'layerupdate' update.id %}">{{ update.started }}{% if update.update.reload %} (reload){% endif%}</a>
{% else %}
<span class="muted">{{ update.started }}{% if update.update.reload %} (reload){% endif%}</span>
{% endif %}
</td>
<td>{% if update.errors %}<span class="badge badge-important">{{ update.errors }}</span>{% endif %}</td>
<td>{% if update.warnings %}<span class="badge badge-warning">{{ update.warnings }}</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div> </div>

View File

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% load i18n %}
{% comment %}
layerindex-web - layer update page
Copyright (C) 2016 Intel Corporation
Licensed under the MIT license, see COPYING.MIT for details
{% endcomment %}
<!--
{% block title_append %} - {{ layerupdate.layerbranch.layer.name }} {{ layerupdate.layerbranch.branch.name }} - {{ layerupdate.started }} {% endblock %}
-->
{% block content %}
{% autoescape on %}
<h2>{{ layerupdate.layerbranch.layer.name }} {{ layerupdate.layerbranch.branch.name }} - {{ layerupdate.started }}</h2>
<pre>{{ layerupdate.log }}</pre>
{% endautoescape %}
{% endblock %}
{% block scripts %}
{% endblock %}

View File

@ -0,0 +1,47 @@
{% extends "base.html" %}
{% load i18n %}
{% comment %}
layerindex-web - update page
Copyright (C) 2016 Intel Corporation
Licensed under the MIT license, see COPYING.MIT for details
{% endcomment %}
<!--
{% block title_append %} - {{ update.started }} {% endblock %}
-->
{% block content %}
{% autoescape on %}
<ul class="breadcrumb">
<li><a href="{% url 'update_list' %}">Updates</a> <span class="divider">&rarr;</span></li>
<li class="active">{{ update.started }}</li>
</ul>
<h2>{{ update.started }} {% if update.reload %}(reload){% endif %}</h2>
{% if update.log %}
<pre>{{ update.log }}</pre>
{% endif %}
{% for layerupdate in layerupdates %}
<a href="{% url 'layer_item' layerupdate.layerbranch.branch.name layerupdate.layerbranch.layer.name %}"><h3>{{ layerupdate.layerbranch.layer.name }} {{ layerupdate.layerbranch.branch.name }}</h3></a>
<pre>{{ layerupdate.log }}</pre>
{% endfor %}
{% if not update.log and not layerupdates %}
<p>No messages</p>
{% endif %}
{% endautoescape %}
{% endblock %}
{% block scripts %}
{% endblock %}

View File

@ -0,0 +1,57 @@
{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% comment %}
layerindex-web - updates list page template
Copyright (C) 2016 Intel Corporation
Licensed under the MIT license, see COPYING.MIT for details
{% endcomment %}
<!--
{% block title_append %} - updates{% endblock %}
-->
{% block content %}
{% autoescape on %}
<div class="row-fluid">
<div class="span9 offset1">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Update date</th>
<th>Time</th>
<th>Errors</th>
<th>Warnings</th>
</tr>
</thead>
<tbody>
{% for update in updates %}
<tr>
<td><a href="{% url 'update' update.id %}">{{ update.started }}{% if update.reload %} (reload){% endif %}</a></td>
<td>{% if update.finished %}{{ update.started|timesince:update.finished }}{% else %}(in progress){% endif %}</td>
<td>{% if update.errors %}<span class="badge badge-important">{{ update.errors }}</span>{% endif %}</td>
<td>{% if update.warnings %}<span class="badge badge-warning">{{ update.warnings }}</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if is_paginated %}
{% load pagination %}
{% pagination page_obj %}
{% endif %}
{% endautoescape %}
{% endblock %}