Merge pull request #1026 from tcely/patch-6
Add a start link to scheduled tasks
This commit is contained in:
@@ -44,10 +44,19 @@ class ResetTasksForm(forms.Form):
|
||||
pass
|
||||
|
||||
|
||||
class ScheduleTaskForm(forms.Form):
|
||||
|
||||
when = forms.DateTimeField(
|
||||
label=_('When the task should run'),
|
||||
required=True,
|
||||
)
|
||||
|
||||
|
||||
class ConfirmDeleteMediaServerForm(forms.Form):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
_media_server_type_label = 'Jellyfin'
|
||||
class JellyfinMediaServerForm(forms.Form):
|
||||
|
||||
|
||||
34
tubesync/sync/templates/sync/task-schedule.html
Normal file
34
tubesync/sync/templates/sync/task-schedule.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block headtitle %}Schedule task{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row no-margin-bottom">
|
||||
<div class="col s12">
|
||||
<h1>Schedule task</h1>
|
||||
<p>
|
||||
If you don't want to wait for the existing schedule to be triggered,
|
||||
you can use this to change when the task will be scheduled to run.
|
||||
It is not guaranteed to run at any exact time, because when a task
|
||||
requests to run and when a slot to execute it, in the appropriate
|
||||
queue and with the priority level assigned, is dependent on how long
|
||||
other tasks are taking to complete the assigned work.
|
||||
</p>
|
||||
<p>
|
||||
This will change the time that the task is requesting to be run
|
||||
to the current time, plus some number of seconds.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form method="post" action="{% url 'sync:run-task' pk=task.pk %}" class="col s12 simpleform">
|
||||
{% csrf_token %}
|
||||
{% include 'simpleform.html' with form=form %}
|
||||
<div class="row no-margin-bottom padding-top">
|
||||
<div class="col s12">
|
||||
<button class="btn" type="submit" name="action">Really schedule task <i class="far fa-clock"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -43,11 +43,16 @@
|
||||
</p>
|
||||
<div class="collection">
|
||||
{% for task in errors %}
|
||||
<a href="{% url task.url pk=task.instance.pk %}" class="collection-item error-text">
|
||||
<i class="fas fa-exclamation-triangle"></i> <strong>{{ task }}</strong>, attempted {{ task.attempts }} time{{ task.attempts|pluralize }}<br>
|
||||
Error: "{{ task.error_message }}"<br>
|
||||
<div class="collection-item error-text">
|
||||
<a href="{% url task.url pk=task.instance.pk %}" class="error-text">
|
||||
<i class="fas fa-exclamation-triangle"></i> <strong>{{ task }}</strong>, attempted {{ task.attempts }} time{{ task.attempts|pluralize }}<br>
|
||||
Error: "{{ task.error_message }}"<br>
|
||||
</a>
|
||||
<i class="fas fa-history"></i> Task will be retried at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>
|
||||
</a>
|
||||
<a href="{% url 'sync:run-task' pk=task.pk %}" class="error-text">
|
||||
<i class="fas fa-undo"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% empty %}
|
||||
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no tasks with errors on this page.</span>
|
||||
{% endfor %}
|
||||
@@ -66,11 +71,17 @@
|
||||
</p>
|
||||
<div class="collection">
|
||||
{% for task in scheduled %}
|
||||
<a href="{% url task.url pk=task.instance.pk %}" class="collection-item">
|
||||
<i class="fas fa-stopwatch"></i> <strong>{{ task }}</strong><br>
|
||||
{% if task.instance.index_schedule and task.repeat > 0 %}Scheduled to run {{ task.instance.get_index_schedule_display|lower }}.<br>{% endif %}
|
||||
<i class="fas fa-redo"></i> Task will run {% if task.run_now %}<strong>immediately</strong>{% else %}at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>{% endif %}
|
||||
</a>
|
||||
<div class="collection-item">
|
||||
<a href="{% url task.url pk=task.instance.pk %}">
|
||||
<i class="fas fa-hourglass-start"></i> <strong>{{ task }}</strong><br>
|
||||
{% if task.instance.index_schedule and task.repeat > 0 %}Scheduled to run {{ task.instance.get_index_schedule_display|lower }}.<br>{% endif %}
|
||||
<i class="far fa-clock"></i> Task will run {% if task.run_now %}<strong>immediately</strong>{% else %}at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>
|
||||
</a>
|
||||
<a href="{% url 'sync:run-task' pk=task.pk %}">
|
||||
<i class="far fa-play-circle"></i>
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
{% empty %}
|
||||
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no scheduled tasks on this page.</span>
|
||||
{% endfor %}
|
||||
|
||||
@@ -3,7 +3,7 @@ from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceVie
|
||||
SourceView, UpdateSourceView, DeleteSourceView, MediaView,
|
||||
MediaThumbView, MediaItemView, MediaRedownloadView, MediaSkipView,
|
||||
MediaEnableView, MediaContent, TasksView, CompletedTasksView, ResetTasks,
|
||||
MediaServersView, AddMediaServerView, MediaServerView,
|
||||
TaskScheduleView, MediaServersView, AddMediaServerView, MediaServerView,
|
||||
DeleteMediaServerView, UpdateMediaServerView)
|
||||
|
||||
|
||||
@@ -122,6 +122,18 @@ urlpatterns = [
|
||||
name='tasks',
|
||||
),
|
||||
|
||||
path(
|
||||
'task/<int:pk>/schedule/now',
|
||||
TaskScheduleView.as_view(),
|
||||
name='run-task',
|
||||
),
|
||||
|
||||
path(
|
||||
'task/<int:pk>/schedule/<int:timestamp>',
|
||||
TaskScheduleView.as_view(),
|
||||
name='schedule-task',
|
||||
),
|
||||
|
||||
path(
|
||||
'tasks-completed',
|
||||
CompletedTasksView.as_view(),
|
||||
|
||||
@@ -20,11 +20,12 @@ from django.utils.text import slugify
|
||||
from django.utils._os import safe_join
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from common.timestamp import timestamp_to_datetime
|
||||
from common.utils import append_uri_params
|
||||
from background_task.models import Task, CompletedTask
|
||||
from .models import Source, Media, MediaServer
|
||||
from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm,
|
||||
SkipMediaForm, EnableMediaForm, ResetTasksForm,
|
||||
SkipMediaForm, EnableMediaForm, ResetTasksForm, ScheduleTaskForm,
|
||||
ConfirmDeleteMediaServerForm)
|
||||
from .utils import validate_url, delete_file, multi_key_sort, mkdir_p
|
||||
from .tasks import (map_task_to_instance, get_error_message,
|
||||
@@ -1005,6 +1006,84 @@ class ResetTasks(FormView):
|
||||
return append_uri_params(url, {'message': 'reset'})
|
||||
|
||||
|
||||
class TaskScheduleView(FormView, SingleObjectMixin):
|
||||
'''
|
||||
Confirm that the task should be re-scheduled.
|
||||
'''
|
||||
|
||||
template_name = 'sync/task-schedule.html'
|
||||
form_class = ScheduleTaskForm
|
||||
model = Task
|
||||
errors = dict(
|
||||
invalid_when=_('The type ({}) was incorrect.'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.object = None
|
||||
self.timestamp = None
|
||||
self.when = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
self.timestamp = kwargs.get('timestamp', '')
|
||||
try:
|
||||
self.timestamp = int(self.timestamp, 10)
|
||||
except (TypeError, ValueError):
|
||||
self.timestamp = None
|
||||
else:
|
||||
try:
|
||||
self.when = timestamp_to_datetime(self.timestamp)
|
||||
except AssertionError:
|
||||
self.when = None
|
||||
if self.when is None:
|
||||
self.when = timezone.now()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
initial['when'] = self.when
|
||||
return initial
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super().get_context_data(*args, **kwargs)
|
||||
data['when'] = self.when
|
||||
return data
|
||||
|
||||
def get_success_url(self):
|
||||
return append_uri_params(
|
||||
reverse_lazy('sync:tasks'),
|
||||
dict(
|
||||
message='scheduled',
|
||||
pk=str(self.object.pk),
|
||||
),
|
||||
)
|
||||
|
||||
def form_valid(self, form):
|
||||
max_attempts = getattr(settings, 'MAX_ATTEMPTS', 15)
|
||||
now = timezone.now()
|
||||
when = form.cleaned_data.get('when')
|
||||
|
||||
if not isinstance(when, now.__class__):
|
||||
form.add_error(
|
||||
'when',
|
||||
ValidationError(
|
||||
errors['invalid_when'].format(
|
||||
type(when),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
if form.errors:
|
||||
return super().form_invalid(form)
|
||||
|
||||
self.object.attempts = max_attempts // 2
|
||||
self.object.run_at = when
|
||||
self.object.save()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class MediaServersView(ListView):
|
||||
'''
|
||||
List of media servers which have been added.
|
||||
|
||||
Reference in New Issue
Block a user