New: Scheduled tasks shwon in UI under System

This commit is contained in:
Mark McDowall 2014-09-11 23:06:11 -07:00
parent 879035b28a
commit 2b0ddb6131
14 changed files with 310 additions and 8 deletions

View File

@ -207,6 +207,8 @@
<Compile Include="Series\SeriesResource.cs" /> <Compile Include="Series\SeriesResource.cs" />
<Compile Include="System\Backup\BackupModule.cs" /> <Compile Include="System\Backup\BackupModule.cs" />
<Compile Include="System\Backup\BackupResource.cs" /> <Compile Include="System\Backup\BackupResource.cs" />
<Compile Include="System\Tasks\TaskModule.cs" />
<Compile Include="System\Tasks\TaskResource.cs" />
<Compile Include="System\SystemModule.cs" /> <Compile Include="System\SystemModule.cs" />
<Compile Include="TinyIoCNancyBootstrapper.cs" /> <Compile Include="TinyIoCNancyBootstrapper.cs" />
<Compile Include="Update\UpdateModule.cs" /> <Compile Include="Update\UpdateModule.cs" />

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Jobs;
namespace NzbDrone.Api.System.Tasks
{
public class TaskModule : NzbDroneRestModule<TaskResource>
{
private readonly ITaskManager _taskManager;
public TaskModule(ITaskManager taskManager)
: base("system/task")
{
_taskManager = taskManager;
GetResourceAll = GetAll;
}
private List<TaskResource> GetAll()
{
return _taskManager.GetAll().Select(ConvertToResource).ToList();
}
private static TaskResource ConvertToResource(ScheduledTask scheduledTask)
{
return new TaskResource
{
Id = scheduledTask.Id,
Name = scheduledTask.TypeName.Split('.').Last().Replace("Command", ""),
CommandName = scheduledTask.TypeName.Split('.').Last(),
Interval = scheduledTask.Interval,
LastExecution = scheduledTask.LastExecution,
NextExecution = scheduledTask.LastExecution.AddMinutes(scheduledTask.Interval)
};
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.System.Tasks
{
public class TaskResource : RestResource
{
public String Name { get; set; }
public String CommandName { get; set; }
public Int32 Interval { get; set; }
public DateTime LastExecution { get; set; }
public DateTime NextExecution { get; set; }
}
}

View File

@ -23,6 +23,7 @@ namespace NzbDrone.Core.Jobs
public interface ITaskManager public interface ITaskManager
{ {
IList<ScheduledTask> GetPending(); IList<ScheduledTask> GetPending();
List<ScheduledTask> GetAll();
} }
public class TaskManager : ITaskManager, IHandle<ApplicationStartedEvent>, IHandle<CommandExecutedEvent>, IHandleAsync<ConfigSavedEvent> public class TaskManager : ITaskManager, IHandle<ApplicationStartedEvent>, IHandle<CommandExecutedEvent>, IHandleAsync<ConfigSavedEvent>
@ -45,6 +46,11 @@ namespace NzbDrone.Core.Jobs
.ToList(); .ToList();
} }
public List<ScheduledTask> GetAll()
{
return _scheduledTaskRepository.All().ToList();
}
public void Handle(ApplicationStartedEvent message) public void Handle(ApplicationStartedEvent message)
{ {
var defaultTasks = new[] var defaultTasks = new[]

View File

@ -0,0 +1,38 @@
'use strict';
define(
[
'Cells/NzbDroneCell',
'moment',
'Shared/FormatHelpers',
'Shared/UiSettingsModel'
], function (NzbDroneCell, moment, FormatHelpers, UiSettings) {
return NzbDroneCell.extend({
className: 'relative-time-cell',
render: function () {
var dateStr = this.model.get(this.column.get('name'));
if (dateStr) {
var date = moment(dateStr);
var result = '<span title="{0}">{1}</span>';
if (UiSettings.get('showRelativeDates')) {
var tooltip = date.format(UiSettings.longDateTime());
var text = date.fromNow();
this.$el.html(result.format(tooltip, text));
}
else {
this.$el.html(date.format(UiSettings.longDateTime()));
}
}
return this;
}
});
});

View File

@ -50,6 +50,11 @@
.text-overflow(); .text-overflow();
} }
.relative-time-cell {
cursor: default;
.text-overflow();
}
.history-event-type-cell { .history-event-type-cell {
width : 10px; width : 10px;
} }
@ -193,3 +198,11 @@ td.delete-episode-file-cell {
word-break: break-all; word-break: break-all;
word-wrap: break-word; word-wrap: break-word;
} }
.execute-task-cell {
width : 28px;
i {
.clickable();
}
}

View File

@ -8,6 +8,7 @@ define(
'System/Logs/LogsLayout', 'System/Logs/LogsLayout',
'System/Update/UpdateLayout', 'System/Update/UpdateLayout',
'System/Backup/BackupLayout', 'System/Backup/BackupLayout',
'System/Task/TaskLayout',
'Shared/Messenger' 'Shared/Messenger'
], function ($, ], function ($,
Backbone, Backbone,
@ -16,6 +17,7 @@ define(
LogsLayout, LogsLayout,
UpdateLayout, UpdateLayout,
BackupLayout, BackupLayout,
TaskLayout,
Messenger) { Messenger) {
return Marionette.Layout.extend({ return Marionette.Layout.extend({
template: 'System/SystemLayoutTemplate', template: 'System/SystemLayoutTemplate',
@ -24,21 +26,24 @@ define(
info : '#info', info : '#info',
logs : '#logs', logs : '#logs',
updates : '#updates', updates : '#updates',
backup : '#backup' backup : '#backup',
tasks : '#tasks'
}, },
ui: { ui: {
infoTab : '.x-info-tab', infoTab : '.x-info-tab',
logsTab : '.x-logs-tab', logsTab : '.x-logs-tab',
updatesTab : '.x-updates-tab', updatesTab : '.x-updates-tab',
backupTab : '.x-backup-tab' backupTab : '.x-backup-tab',
tasksTab : '.x-tasks-tab'
}, },
events: { events: {
'click .x-info-tab' : '_showInfo', 'click .x-info-tab' : '_showInfo',
'click .x-logs-tab' : '_showLogs', 'click .x-logs-tab' : '_showLogs',
'click .x-updates-tab': '_showUpdates', 'click .x-updates-tab' : '_showUpdates',
'click .x-backup-tab': '_showBackup', 'click .x-backup-tab' : '_showBackup',
'click .x-tasks-tab' : '_showTasks',
'click .x-shutdown' : '_shutdown', 'click .x-shutdown' : '_shutdown',
'click .x-restart' : '_restart' 'click .x-restart' : '_restart'
}, },
@ -60,6 +65,9 @@ define(
case 'backup': case 'backup':
this._showBackup(); this._showBackup();
break; break;
case 'tasks':
this._showTasks();
break;
default: default:
this._showInfo(); this._showInfo();
} }
@ -109,6 +117,16 @@ define(
this._navigate('system/backup'); this._navigate('system/backup');
}, },
_showTasks: function (e) {
if (e) {
e.preventDefault();
}
this.tasks.show(new TaskLayout());
this.ui.tasksTab.tab('show');
this._navigate('system/tasks');
},
_shutdown: function () { _shutdown: function () {
$.ajax({ $.ajax({
url: window.NzbDrone.ApiRoot + '/system/shutdown', url: window.NzbDrone.ApiRoot + '/system/shutdown',

View File

@ -3,6 +3,7 @@
<li><a href="#logs" class="x-logs-tab no-router">Logs</a></li> <li><a href="#logs" class="x-logs-tab no-router">Logs</a></li>
<li><a href="#updates" class="x-updates-tab no-router">Updates</a></li> <li><a href="#updates" class="x-updates-tab no-router">Updates</a></li>
<li><a href="#backup" class="x-backup-tab no-router">Backup</a></li> <li><a href="#backup" class="x-backup-tab no-router">Backup</a></li>
<li><a href="#tasks" class="x-tasks-tab no-router">Tasks</a></li>
<li class="lifecycle-controls pull-right"> <li class="lifecycle-controls pull-right">
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-default btn-icon-only x-shutdown" title="Shutdown" data-container="body"> <button class="btn btn-default btn-icon-only x-shutdown" title="Shutdown" data-container="body">
@ -20,4 +21,5 @@
<div class="tab-pane" id="logs"></div> <div class="tab-pane" id="logs"></div>
<div class="tab-pane" id="updates"></div> <div class="tab-pane" id="updates"></div>
<div class="tab-pane" id="backup"></div> <div class="tab-pane" id="backup"></div>
<div class="tab-pane" id="tasks"></div>
</div> </div>

View File

@ -0,0 +1,41 @@
'use strict';
define(
[
'Cells/NzbDroneCell',
'Commands/CommandController'
], function (NzbDroneCell, CommandController) {
return NzbDroneCell.extend({
className: 'execute-task-cell',
events: {
'click .x-execute' : '_executeTask'
},
render: function () {
this.$el.empty();
var task = this.model.get('name');
this.$el.html(
'<i class="icon-cogs x-execute" title="Execute {0}"></i>'.format(task)
);
CommandController.bindToCommand({
element: this.$el.find('.x-execute'),
command: {
name : task
}
});
return this;
},
_executeTask: function () {
CommandController.Execute(this.model.get('name'), {
name : this.model.get('name')
});
}
});
});

View File

@ -0,0 +1,19 @@
'use strict';
define(
[
'backbone.pageable',
'System/Task/TaskModel'
], function (PageableCollection, TaskModel) {
return PageableCollection.extend({
url : window.NzbDrone.ApiRoot + '/system/task',
model: TaskModel,
state: {
sortKey : 'name',
order : -1,
pageSize : 100000
},
mode: 'client'
});
});

View File

@ -0,0 +1,25 @@
'use strict';
define(
[
'Cells/NzbDroneCell',
'moment'
], function (NzbDroneCell, moment) {
return NzbDroneCell.extend({
className: 'task-interval-cell',
render: function () {
this.$el.empty();
var interval = this.model.get('interval');
var duration = moment.duration(interval, 'minutes').humanize();
this.$el.html(
duration.replace(/an?(?=\s)/, '1')
);
return this;
}
});
});

View File

@ -0,0 +1,73 @@
'use strict';
define(
[
'marionette',
'backgrid',
'System/Task/TaskCollection',
'Cells/RelativeTimeCell',
'System/Task/TaskIntervalCell',
'System/Task/ExecuteTaskCell',
'Shared/LoadingView'
], function (Marionette, Backgrid, BackupCollection, RelativeTimeCell, TaskIntervalCell, ExecuteTaskCell, LoadingView) {
return Marionette.Layout.extend({
template: 'System/Task/TaskLayoutTemplate',
regions: {
tasks : '#x-tasks'
},
columns: [
{
name : 'name',
label : 'Name',
sortable : true,
cell : 'string'
},
{
name : 'interval',
label : 'Interval',
sortable : true,
cell : TaskIntervalCell
},
{
name : 'lastExecution',
label : 'Last Execution',
sortable : true,
cell : RelativeTimeCell
},
{
name : 'nextExecution',
label : 'Next Execution',
sortable : true,
cell : RelativeTimeCell
},
{
name : 'this',
label : '',
sortable : false,
cell : ExecuteTaskCell
}
],
initialize: function () {
this.taskCollection = new BackupCollection();
this.listenTo(this.taskCollection, 'sync', this._showTasks);
},
onRender: function () {
this.tasks.show(new LoadingView());
this.taskCollection.fetch();
},
_showTasks: function () {
this.tasks.show(new Backgrid.Grid({
columns : this.columns,
collection: this.taskCollection,
className : 'table table-hover'
}));
}
});
});

View File

@ -0,0 +1,5 @@
<div class="row">
<div class="col-md-12">
<div id="x-tasks" class="table-responsive"/>
</div>
</div>

View File

@ -0,0 +1,9 @@
'use strict';
define(
[
'backbone'
], function (Backbone) {
return Backbone.Model.extend({
});
});