Commit d1e68c29 authored by BAIRE Anthony's avatar BAIRE Anthony
Browse files

Merge branch 'redis-log-tpl' into 'django'

Update of job process [was: Reading of Redis logs for a specific job]

Closes #179

See merge request !46
parents 5dc4e46f a2f0bcac
Pipeline #29869 failed with stage
in 48 seconds
......@@ -10,6 +10,7 @@ from .models import (
JobQueue,
Runner,
Webapp,
WebappParameter,
WebappVersion,
)
......@@ -29,6 +30,16 @@ class WebappAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'docker_name', 'user')
class WebappParameterAdmin(admin.ModelAdmin):
list_display = ('id', 'get_webapp', 'name')
def get_webapp(self, obj):
return obj.webapp.name
get_webapp.short_description = 'Webapp'
get_webapp.admin_order_field = 'webapp__name'
class WebappVersionAdmin(admin.ModelAdmin):
list_display = ('id', 'get_webapp', 'number')
......@@ -51,6 +62,7 @@ admin.site.unregister(User)
admin.site.register(User, UserAdmin)
admin.site.register(Runner, RunnerAdmin)
admin.site.register(Webapp, WebappAdmin)
admin.site.register(WebappParameter, WebappParameterAdmin)
admin.site.register(WebappVersion, WebappVersionAdmin)
admin.site.register(Job, JobAdmin)
admin.site.register(AllgoUser)
......
......@@ -10,6 +10,7 @@ from .models import (
JobQueue,
Runner,
Webapp,
WebappParameter,
WebappVersion,
)
......@@ -90,9 +91,16 @@ class JobForm(forms.ModelForm):
label_suffix='',
)
param = forms.CharField(label='Parameters', label_suffix='', required=False)
webapp_parameters = forms.ModelChoiceField(
queryset=WebappParameter.objects.all(),
label="presets",
required=False)
def __init__(self, *args, **kwargs):
super(JobForm, self).__init__(*args, **kwargs)
webapp = kwargs.pop('webapp')
super().__init__(*args, **kwargs)
self.fields['webapp_parameters'].queryset = WebappParameter.objects.filter(webapp=webapp)
for field in self.fields.values():
field.error_messages = {'required':'The field "{fieldname}" is required'.format(
......@@ -100,7 +108,7 @@ class JobForm(forms.ModelForm):
class Meta:
model = Job
fields = ('param', 'queue_id', 'files')
fields = ('param', 'queue_id', 'files', 'webapp_parameters')
class RunnerForm(forms.ModelForm):
......
import base64
import hashlib
import os
import redis
from django.conf import settings
import config
DEFAULT_ENTROPY = 32 # number of bytes to return by default
......@@ -54,3 +57,15 @@ def upload_data(file_obj, job_id):
with open(endpoint, 'wb+') as destination:
for chunk in file_data.chunks():
destination.write(chunk)
def read_runner_logs(jobid):
redis_host = config.env.ALLGO_REDIS_HOST
r = redis.StrictRedis(host=redis_host, port=6379, db=0)
linenumber = 0
k = "job:log:%s:%s" % (jobid, linenumber)
log.debug("Runner read logs k %s - %s", k, r.exists(k))
while r.exists(k):
yield r.get(k)
linenumber += 1
k = "job:log:%s:%s" % (jobid, linenumber)
......@@ -64,3 +64,8 @@ def append_attr(field, attr):
@silence_without_field
def add_class(field, css_class):
return append_attr(field, 'class:' + css_class)
@register.filter(is_safe=True)
def label_with_classes(value, arg):
return value.label_tag(attrs={'class': arg})
# -*- coding: utf-8 -*-
"""Main url module
This module handles all URLs related to the `main` django application.
Below a short description of the different type of URLs available for
front-end.
/apps list all the public apps
/apps/author/<username> list all public apps of a particular owner
/apps/tag/<tag> list all apps according to a particular tag
/app[s]/<docker_name> app details
/runners/ list all runners of a given user
/runners/create create a runner
/runners/<runner_id>/ details of a runner
/runners/<runner_id>/update update a given runner
/runners/<runner_id>/delete delete a given runner
/profile profile of user (name, first name, email)
/profile/password update password
/profile/security SSH key and token
/jobs/ list all jobs of the user
/jobs/<job_id>/ details of a given job
/jobs/<job_id>/delete delete the job
/jobs/<job_id>/issue report an issue
/jobs/<job_id>/relaunch re-run a given job
Todo:
- make the app details available both at /app/<docker_name> (historic
version) and at /apps/<docker_name>/ (more coherent with the overall
system.
See also:
This url file depends on the main `urls.py` file located in the `config`
folder at the root of the Django application.
"""
# Third party import
from django.conf.urls import url, include
# Local import
from . import views
app_name = 'main'
......@@ -13,11 +54,11 @@ urlpatterns = [
url(r'^app/(?P<docker_name>[\w-]+)/$', views.WebappDetail.as_view(), name='webapp_detail'),
url(r'^jobs/$', views.JobList.as_view(), name='job_list'),
url(r'^jobs/(?P<pk>\d+)/$', views.JobDetailView.as_view(), name='job_detail'),
url(r'^jobs/(?P<pk>\d+)/$', views.JobDetail.as_view(), name='job_detail'),
url(r'^jobs/(?P<docker_name>[\w-]+)/create/$', views.JobCreate.as_view(), name='job_create'),
url(r'^jobs/delete/(?P<pk>\d+)/$', views.JobDeleteView.as_view(), name='job_delete'),
url(r'^jobs/(?P<pk>\d+)/download/(?P<filename>(.*))$', views.JobFileDownloadView.as_view(), name='job_download_file'),
url(r'^jobs/(?P<pk>\d+)/download/all/$', views.JobFileDownloadAllView.as_view(), name='job_download_all'),
url(r'^jobs/delete/(?P<pk>\d+)/$', views.JobDelete.as_view(), name='job_delete'),
url(r'^jobs/(?P<pk>\d+)/download/(?P<filename>(.*))$', views.JobFileDownload.as_view(), name='job_download_file'),
url(r'^jobs/(?P<pk>\d+)/download/all/$', views.JobFileDownloadAll.as_view(), name='job_download_all'),
url(r'^profile/$', views.UserUpdate.as_view(), name='user_detail'),
url(r'^profile/token/update$', views.UserToken.as_view(), name='user_token'),
......@@ -30,27 +71,3 @@ urlpatterns = [
url(r'^runners/(?P<pk>\d+)/update/$', views.RunnerUpdate.as_view(), name='runner_update'),
url(r'^runners/(?P<pk>\d+)/delete/$', views.RunnerDelete.as_view(), name='runner_delete'),
]
"""
# Apps
/apps list all the public apps
/apps/author/<username> list all public apps of a particular owner
/apps/tag/<tag> list all apps according to a particular tag
/app[s]/<docker_name> app details
/runners/ list all runners of a given user
/runners/create create a runner
/runners/<runner_id>/ details of a runner
/runners/<runner_id>/update update a given runner
/runners/<runner_id>/delete delete a given runner
/profile profile of user (name, first name, email)
/profile/password update password
/profile/security SSH key and token
/jobs/ list all jobs of the user
/jobs/<job_id>/ details of a given job
/jobs/<job_id>/delete delete the job
/jobs/<job_id>/issue report an issue
/jobs/<job_id>/relaunch re-run a given job
"""
This diff is collapsed.
......@@ -224,3 +224,25 @@ a {
.app-unfinished:hover {
color: #6A2828;
}
/* JOB DETAIL */
.page-title {
margin-bottom: 3em;
}
/* pictogram color scheme */
.picto-blue {
color: rgba(83, 147, 178, 1);
}
.picto-orange {
color: rgba(249, 163, 38, 1);
}
.picto-green {
color: rgba(53, 136, 72, 1);
}
.picto-red {
color: rgba(170, 23, 45, 1);
}
.picto-yellow {
color: rgba(232, 200, 40, 1);
}
......@@ -36,15 +36,26 @@
<div class="form-group">
{{ form.files.label_tag }}
<input type="file" class="form-control-file filestyle" id="inputGroupFile01" data-buttonBefore="true" data-badge="true" multiple>
<input type="file" class="form-control-file filestyle" name="{{ form.files.name }}" id="inputGroupFile01" data-buttonBefore="true" data-badge="true" multiple>
</div>
{{ form.param | label_with_classes:"d-block" }}
<div class="form-group">
{{ form.param.label_tag }}
{{ form.param | attr:"placeholder:Type your parameters" | add_class:"form-control" }}
<div class="input-group">
<div class="input-group-prepend">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Presets</button>
<div class="dropdown-menu">
{% for choice in form.webapp_parameters.field.queryset %}
<a class="dropdown-item" href="#" data-param="{{ choice.value }}">{{ choice.name }}</a>
{% endfor %}
</div>
</div>
{{ form.param | attr:"placeholder:Type your parameters" | add_class:"form-control dropdown-value" }}
</div>
</div>
</div>
<div class="tab-pane" id="advanced">
<div class="form-group">
{{ form.queue_id.label_tag }}
......@@ -84,4 +95,12 @@
<script type="text/javascript">
$('#{{ form.files.id_for_label }}').filestyle();
</script>
<script type="text/javascript">
$(".dropdown-item").on('click', function() {
console.log('hello');
var d = $(this).data('param');
$('input[type=text].dropdown-value').val(d);
});
</script>
{% endblock %}
......@@ -11,32 +11,75 @@
{% block content %}
<div class="container">
<div class="allgo-page">
<div class="row">
<div class="col">
<h5 class="page-title">Job #{{ job.id }}</h5>
</div>
<div class="card">
<div class="card-header">
<h4 class="card-title">Job #{{ job.id }}
<span class="text-right">
<a href="{% url 'main:job_delete' job.id %}" class="badge badge-danger"
data-toggle="tooltip"
data-placement="top"
title="Delete this job">
<i class="fas fa-trash-alt"></i><span class="text-hide">Delete</span>
</a>
<a href="mailto:{{ job.webapp.contact }}?subject=[Allgo] issue on job for {{ job.webapp.name }}&body={% if request.is_secure %}https://{% else %}http://{% endif %}{{ request.get_host }}{% url 'main:job_detail' job.id %}"
class="badge badge-warning"
data-toggle="tooltip"
data-placement="top"
title="Report an issue">
<i class="fas fa-exclamation-circle"></i><span class="text-hide">Report a problem</span>
</a>
</span>
</h4>
<div class="col text-right">
<a href="{% url 'main:job_delete' job.id %}"
data-toggle="tooltip"
data-placement="top"
title="Delete this job"
class="fa-layers fa-2x">
<i class="fas fa-square"></i>
<i class="fa-inverse fas fa-trash-alt" data-fa-transform="shrink-7 down-.25 left-.25"></i></span>
<span class="text-hide">Delete this job</span>
</a>
<a href="#"
data-toggle="tooltip"
data-placement="top"
class="fa-layers fa-2x" title="Report an issue to the maintainer">
<i class="fas fa-square"></i>
<i class="fa-inverse fas fa-exclamation" data-fa-transform="shrink-7 down-.25 lef-.25"></i>
<span class="text-hide">Report an issue</span>
</a>
</div>
</div>
<div class="row">
<div class="col-sm-7">
<h5 class="card-title">Files</h5>
<table class="table card-table">
{% for file in files %}
<tr>
<td><i class="fas fa-file"></i></td>
<td> {{ file }}</td>
<td class="text-right">
<a
href="{% url 'main:job_download_file' job.id file %}"
data-toggle="tooltip"
data-placement="top"
title="Download {{ file }}">
<i class="fas fa-download"></i><span class="text-hide">Download {{ file }}</span>
</a>
</td>
</tr>
{% endfor %}
<tr>
<td><i class="fas fa-copy"></i></td>
<td>All files</td>
<td>
<a
href="{% url 'main:job_download_all' job.id %}"
data-toggle="tooltip"
data-placement="bottom"
title="Download all files (zip archive)"
class="float-right"><i class="fas fa-download"></i></a>
</td>
</tr>
</table>
</div>
<div class="col-sm-4 offset-sm-1">
<h5 class="card-title">Metadata</h5>
<table class="table card-table">
<tr>
<td class="text-muted" width="1">Name</td>
<td><a href="{% url 'main:webapp_detail' job.webapp.docker_name %}">{{ job.webapp.name }}</a></td>
<td><a href="{% url 'main:webapp_detail' job.webapp.docker_name %}">{{ job.webapp.name | fancy_webapp_name | title }}</a></td>
</tr>
<tr>
<td class="text-muted">Version</td>
......@@ -54,63 +97,90 @@
<td class="text-muted">Status</td>
<td>
{% if job.state == 0 %}
New
<i class="fas fa-plus picto-green"
data-toggle="tooltip"
data-placement="top"
title="New job"
></i><span class="text-hide">New job</span>
{% elif job.state == 1 %}
Waiting
<i class="fas fa-clock picto-orange"
data-toggle="tooltip"
data-placement="top"
title="Job waiting"
></i><span class="text-hide">Waiting job</span>
{% elif job.state == 2 %}
Running
<i class="fas fa-play picto-yellow"
data-toggle="tooltip"
data-placement="top"
title="Job running"
></i><span class="text-hide">Job running</span>
{% elif job.state == 3 %}
Done
<i class="fas fa-check picto-green"
data-toggle="tooltip"
data-placement="top"
title="Job Done"
></i><span class="text-hide">Job done</span>
{% elif job.state == 4 %}
Archived
<i class="fas fa-archive"
data-toggle="tooltip"
data-placement="top"
title="Job archived"
></i><span class="text-hide">Job archived</span>
{% elif job.state == 5 %}
<i class="fas fa-eraser picto-orange"
data-toggle="tooltip"
data-placement="top"
title="Job deleted"
></i><span class="text-hide">Job deleted</span>
{% else %}
Uknown
{% endif %}
</td>
</tr>
<tr>
<td class="text-muted">Data</td>
<td>TODO: zip to download</td>
</tr>
</table>
</div>
{% if files %}
<div class="card">
<div class="card-header">
<h4 class="card-title">Files</h4>
</div>
<table class="table card-table">
{% for file in files %}
<tr>
<td>{{ file }}</td>
<td class="text-right">
<a
href="{% url 'main:job_download_file' job.id file %}"
<i class="fas fa-ban picto-red"
data-toggle="tooltip"
data-placement="top"
title="Download {{ file }}">
<i class="fas fa-download"></i><span class="text-hide">Download {{ file }}</span>
</a>
title="Job aborted"
></i><span class="text-hide">Job aborted</span>
{% endif %}
</td>
</tr>
{% endfor %}
<tr>
<td class="text-muted">Result</td>
<td>
<a
href="{% url 'main:job_download_all' job.id %}"
{% if job.result == 0 %}
in progress
{% elif job.result == 1 %}
<i class="fas fa-check"
data-toggle="tooltip"
data-placement="bottom"
title="Download all files (zip archive)"
class="btn btn-primary"><i class="fas fa-download"></i> Download all files (zip archive)</a>
data-placement="top"
title="Result successful"
style="color: green;"></i><span class="text-hide">Result successful</span>
{% elif job.result == 2 %}
<i class="fas fa-times"
data-toggle="tooltip"
data-placement="top"
title="Result error"
style="color: red;"></i><span class="text-hide">Result error</span>
{% elif job.result == 3 %}
<i class="fas fa-ban"
data-toggle="tooltip"
data-placement="top"
title="Result aborted"
style="color: orange;"></i><span class="text-hide">Result aborted</span>
{% elif job.result == 4 %}
<i class="fas fa-stopwatch"
data-toggle="tooltip"
data-placement="top"
title="Timeout"
style="color: orange;"></i><span class="text-hide">Timeout</span>
{% endif %}
</td>
<td></td>
</tr>
</table>
</div>
{% endif %}
</div>
<div class="col-sm">
<h5 class="card-title">Logs</h5>
<pre><code class="lang-json">{{ logs }}</code></pre>
</div>
</div>
</div>
</div>
{% endblock %}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment