diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj
index 51303d2de..33f3953e8 100644
--- a/src/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/src/NzbDrone.Api/NzbDrone.Api.csproj
@@ -207,6 +207,8 @@
+
+
diff --git a/src/NzbDrone.Api/System/Tasks/TaskModule.cs b/src/NzbDrone.Api/System/Tasks/TaskModule.cs
new file mode 100644
index 000000000..e5f454e5b
--- /dev/null
+++ b/src/NzbDrone.Api/System/Tasks/TaskModule.cs
@@ -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
+ {
+ private readonly ITaskManager _taskManager;
+
+ public TaskModule(ITaskManager taskManager)
+ : base("system/task")
+ {
+ _taskManager = taskManager;
+ GetResourceAll = GetAll;
+ }
+
+ private List 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)
+ };
+ }
+ }
+}
diff --git a/src/NzbDrone.Api/System/Tasks/TaskResource.cs b/src/NzbDrone.Api/System/Tasks/TaskResource.cs
new file mode 100644
index 000000000..900457ccc
--- /dev/null
+++ b/src/NzbDrone.Api/System/Tasks/TaskResource.cs
@@ -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; }
+ }
+}
diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs
index 46c6f79d5..99e600fa6 100644
--- a/src/NzbDrone.Core/Jobs/TaskManager.cs
+++ b/src/NzbDrone.Core/Jobs/TaskManager.cs
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Jobs
public interface ITaskManager
{
IList GetPending();
+ List GetAll();
}
public class TaskManager : ITaskManager, IHandle, IHandle, IHandleAsync
@@ -45,6 +46,11 @@ namespace NzbDrone.Core.Jobs
.ToList();
}
+ public List GetAll()
+ {
+ return _scheduledTaskRepository.All().ToList();
+ }
+
public void Handle(ApplicationStartedEvent message)
{
var defaultTasks = new[]
diff --git a/src/UI/Cells/RelativeTimeCell.js b/src/UI/Cells/RelativeTimeCell.js
new file mode 100644
index 000000000..d7a6363e1
--- /dev/null
+++ b/src/UI/Cells/RelativeTimeCell.js
@@ -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 = '{1}';
+
+ 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;
+ }
+ });
+ });
diff --git a/src/UI/Cells/cells.less b/src/UI/Cells/cells.less
index 85f255088..44437b487 100644
--- a/src/UI/Cells/cells.less
+++ b/src/UI/Cells/cells.less
@@ -50,6 +50,11 @@
.text-overflow();
}
+.relative-time-cell {
+ cursor: default;
+ .text-overflow();
+}
+
.history-event-type-cell {
width : 10px;
}
@@ -193,3 +198,11 @@ td.delete-episode-file-cell {
word-break: break-all;
word-wrap: break-word;
}
+
+.execute-task-cell {
+ width : 28px;
+
+ i {
+ .clickable();
+ }
+}
diff --git a/src/UI/System/SystemLayout.js b/src/UI/System/SystemLayout.js
index 29e8562f7..08fe635ca 100644
--- a/src/UI/System/SystemLayout.js
+++ b/src/UI/System/SystemLayout.js
@@ -8,6 +8,7 @@ define(
'System/Logs/LogsLayout',
'System/Update/UpdateLayout',
'System/Backup/BackupLayout',
+ 'System/Task/TaskLayout',
'Shared/Messenger'
], function ($,
Backbone,
@@ -16,6 +17,7 @@ define(
LogsLayout,
UpdateLayout,
BackupLayout,
+ TaskLayout,
Messenger) {
return Marionette.Layout.extend({
template: 'System/SystemLayoutTemplate',
@@ -24,23 +26,26 @@ define(
info : '#info',
logs : '#logs',
updates : '#updates',
- backup : '#backup'
+ backup : '#backup',
+ tasks : '#tasks'
},
ui: {
infoTab : '.x-info-tab',
logsTab : '.x-logs-tab',
updatesTab : '.x-updates-tab',
- backupTab : '.x-backup-tab'
+ backupTab : '.x-backup-tab',
+ tasksTab : '.x-tasks-tab'
},
events: {
- 'click .x-info-tab' : '_showInfo',
- 'click .x-logs-tab' : '_showLogs',
- 'click .x-updates-tab': '_showUpdates',
- 'click .x-backup-tab': '_showBackup',
- 'click .x-shutdown' : '_shutdown',
- 'click .x-restart' : '_restart'
+ 'click .x-info-tab' : '_showInfo',
+ 'click .x-logs-tab' : '_showLogs',
+ 'click .x-updates-tab' : '_showUpdates',
+ 'click .x-backup-tab' : '_showBackup',
+ 'click .x-tasks-tab' : '_showTasks',
+ 'click .x-shutdown' : '_shutdown',
+ 'click .x-restart' : '_restart'
},
initialize: function (options) {
@@ -60,6 +65,9 @@ define(
case 'backup':
this._showBackup();
break;
+ case 'tasks':
+ this._showTasks();
+ break;
default:
this._showInfo();
}
@@ -109,6 +117,16 @@ define(
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 () {
$.ajax({
url: window.NzbDrone.ApiRoot + '/system/shutdown',
diff --git a/src/UI/System/SystemLayoutTemplate.hbs b/src/UI/System/SystemLayoutTemplate.hbs
index aa6996850..f0bb81574 100644
--- a/src/UI/System/SystemLayoutTemplate.hbs
+++ b/src/UI/System/SystemLayoutTemplate.hbs
@@ -3,6 +3,7 @@
Logs
Updates
Backup
+ Tasks
\ No newline at end of file
diff --git a/src/UI/System/Task/ExecuteTaskCell.js b/src/UI/System/Task/ExecuteTaskCell.js
new file mode 100644
index 000000000..2969d0bd1
--- /dev/null
+++ b/src/UI/System/Task/ExecuteTaskCell.js
@@ -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(
+ ''.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')
+ });
+ }
+ });
+ });
diff --git a/src/UI/System/Task/TaskCollection.js b/src/UI/System/Task/TaskCollection.js
new file mode 100644
index 000000000..3f8e09e70
--- /dev/null
+++ b/src/UI/System/Task/TaskCollection.js
@@ -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'
+ });
+ });
diff --git a/src/UI/System/Task/TaskIntervalCell.js b/src/UI/System/Task/TaskIntervalCell.js
new file mode 100644
index 000000000..694fc042e
--- /dev/null
+++ b/src/UI/System/Task/TaskIntervalCell.js
@@ -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;
+ }
+ });
+ });
diff --git a/src/UI/System/Task/TaskLayout.js b/src/UI/System/Task/TaskLayout.js
new file mode 100644
index 000000000..c55337c92
--- /dev/null
+++ b/src/UI/System/Task/TaskLayout.js
@@ -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'
+ }));
+ }
+ });
+ });
diff --git a/src/UI/System/Task/TaskLayoutTemplate.hbs b/src/UI/System/Task/TaskLayoutTemplate.hbs
new file mode 100644
index 000000000..87b503704
--- /dev/null
+++ b/src/UI/System/Task/TaskLayoutTemplate.hbs
@@ -0,0 +1,5 @@
+
diff --git a/src/UI/System/Task/TaskModel.js b/src/UI/System/Task/TaskModel.js
new file mode 100644
index 000000000..530a080c6
--- /dev/null
+++ b/src/UI/System/Task/TaskModel.js
@@ -0,0 +1,9 @@
+'use strict';
+define(
+ [
+ 'backbone'
+ ], function (Backbone) {
+ return Backbone.Model.extend({
+
+ });
+ });