Log files are viewable in the UI

pull/6/head
Mark McDowall 11 years ago
parent a9ac2500d5
commit 4c536a077f

@ -0,0 +1,29 @@
using System.IO;
using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Api.Frontend
{
public class LogFileMapper : IMapHttpRequestsToDisk
{
private readonly IAppFolderInfo _appFolderInfo;
public LogFileMapper(IAppFolderInfo appFolderInfo)
{
_appFolderInfo = appFolderInfo;
}
public string Map(string resourceUrl)
{
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = Path.GetFileName(path);
return Path.Combine(_appFolderInfo.GetLogFolder(), path);
}
public bool CanHandle(string resourceUrl)
{
return resourceUrl.StartsWith("/log");
}
}
}

@ -0,0 +1,45 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Api.Logs
{
public class LogFileModule : NzbDroneRestModule<LogFileResource>
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
public LogFileModule(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider)
: base("log/files")
{
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;
GetResourceAll = GetLogFiles;
}
private List<LogFileResource> GetLogFiles()
{
var result = new List<LogFileResource>();
var files = _diskProvider.GetFiles(_appFolderInfo.GetLogFolder(), SearchOption.TopDirectoryOnly);
for (int i = 0; i < files.Length; i++)
{
var file = files[i];
result.Add(new LogFileResource
{
Id = i + 1,
Filename = Path.GetFileName(file),
LastWriteTime = _diskProvider.GetLastFileWrite(file)
});
}
return result.OrderByDescending(l => l.LastWriteTime).ToList();
}
}
}

@ -0,0 +1,11 @@
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Logs
{
public class LogFileResource : RestResource
{
public String Filename { get; set; }
public DateTime LastWriteTime { get; set; }
}
}

@ -1,5 +1,4 @@
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.History;
using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Instrumentation;
using NzbDrone.Api.Mapping; using NzbDrone.Api.Mapping;
@ -8,7 +7,6 @@ namespace NzbDrone.Api.Logs
public class LogModule : NzbDroneRestModule<LogResource> public class LogModule : NzbDroneRestModule<LogResource>
{ {
private readonly ILogService _logService; private readonly ILogService _logService;
private readonly IHistoryService _historyService;
public LogModule(ILogService logService) public LogModule(ILogService logService)
{ {

@ -92,6 +92,7 @@
<Compile Include="Extensions\CacheHeaderPipeline.cs" /> <Compile Include="Extensions\CacheHeaderPipeline.cs" />
<Compile Include="Extensions\GZipPipeline.cs" /> <Compile Include="Extensions\GZipPipeline.cs" />
<Compile Include="Extensions\NancyJsonSerializer.cs" /> <Compile Include="Extensions\NancyJsonSerializer.cs" />
<Compile Include="Frontend\LogFileMapper.cs" />
<Compile Include="Frontend\IAddCacheHeaders.cs" /> <Compile Include="Frontend\IAddCacheHeaders.cs" />
<Compile Include="Frontend\MediaCoverMapper.cs" /> <Compile Include="Frontend\MediaCoverMapper.cs" />
<Compile Include="Frontend\IMapHttpRequestsToDisk.cs" /> <Compile Include="Frontend\IMapHttpRequestsToDisk.cs" />
@ -106,6 +107,8 @@
<Compile Include="Indexers\ReleaseModule.cs" /> <Compile Include="Indexers\ReleaseModule.cs" />
<Compile Include="Extensions\LazyExtensions.cs" /> <Compile Include="Extensions\LazyExtensions.cs" />
<Compile Include="Indexers\ReleaseResource.cs" /> <Compile Include="Indexers\ReleaseResource.cs" />
<Compile Include="Logs\LogFileResource.cs" />
<Compile Include="Logs\LogFileModule.cs" />
<Compile Include="Logs\LogModule.cs" /> <Compile Include="Logs\LogModule.cs" />
<Compile Include="Logs\LogResource.cs" /> <Compile Include="Logs\LogResource.cs" />
<Compile Include="Mapping\CloneInjection.cs" /> <Compile Include="Mapping\CloneInjection.cs" />

@ -10,11 +10,7 @@ define(
className: 'episode-title-cell', className: 'episode-title-cell',
events: { events: {
'click': 'showDetails' 'click': '_showDetails'
},
showDetails: function () {
App.vent.trigger(App.Commands.ShowEpisodeDetails, {episode: this.cellValue});
}, },
render: function () { render: function () {
@ -26,6 +22,10 @@ define(
this.$el.html(title); this.$el.html(title);
return this; return this;
},
_showDetails: function () {
App.vent.trigger(App.Commands.ShowEpisodeDetails, {episode: this.cellValue});
} }
}); });
}); });

@ -12,12 +12,13 @@ define(
'Series/SeriesModel', 'Series/SeriesModel',
'Calendar/CalendarLayout', 'Calendar/CalendarLayout',
'Logs/Layout', 'Logs/Layout',
'Logs/Files/Layout',
'Release/Layout', 'Release/Layout',
'System/Layout', 'System/Layout',
'Shared/NotFoundView', 'Shared/NotFoundView',
'Shared/Modal/Region' 'Shared/Modal/Region'
], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, MissingLayout, SeriesModel, CalendarLayout, ], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, MissingLayout, SeriesModel, CalendarLayout,
LogsLayout, ReleaseLayout, SystemLayout, NotFoundView) { LogsLayout, LogFileLayout, ReleaseLayout, SystemLayout, NotFoundView) {
return Marionette.Controller.extend({ return Marionette.Controller.extend({
series : function () { series : function () {
@ -71,9 +72,16 @@ define(
App.mainRegion.show(new ReleaseLayout()); App.mainRegion.show(new ReleaseLayout());
}, },
logs: function () { logs: function (action) {
this._setTitle('logs'); if (action) {
App.mainRegion.show(new LogsLayout()); this._setTitle('log files');
App.mainRegion.show(new LogFileLayout());
}
else {
this._setTitle('logs');
App.mainRegion.show(new LogsLayout());
}
}, },
system: function () { system: function () {

@ -0,0 +1,13 @@
'use strict';
define(['Logs/Files/Model' ],
function (LogFileModel) {
return Backbone.Collection.extend({
url : window.ApiRoot + '/log/files',
model: LogFileModel,
state: {
sortKey : 'lastWriteTime',
order : 1
}
});
});

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

@ -0,0 +1,11 @@
'use strict';
define(
[
'app',
'marionette'
], function (App, Marionette) {
return Marionette.ItemView.extend({
template: 'Logs/Files/ContentsViewTemplate'
});
});

@ -0,0 +1,11 @@
<div class="row">
<div class="span12">
<h3>{{filename}}</h3>
</div>
</div>
<div class="row">
<div class="span12">
<pre>{{contents}}</pre>
</div>
</div>

@ -0,0 +1,18 @@
'use strict';
define(
[
'Cells/NzbDroneCell'
], function (NzbDroneCell) {
return NzbDroneCell.extend({
className: 'log-filename-cell',
render: function () {
var filename = this._getValue();
this.$el.html(filename);
return this;
}
});
});

@ -0,0 +1,79 @@
'use strict';
define(
[
'app',
'marionette',
'backgrid',
'Logs/Files/FilenameCell',
'Cells/RelativeDateCell',
'Logs/Files/Collection',
'Logs/Files/Row',
'Logs/Files/ContentsView',
'Logs/Files/ContentsModel'
], function (App, Marionette, Backgrid, FilenameCell, RelativeDateCell, LogFileCollection, LogFileRow, ContentsView, ContentsModel) {
return Marionette.Layout.extend({
template: 'Logs/Files/LayoutTemplate',
regions: {
grid : '#x-grid',
contents : '#x-contents'
},
columns:
[
{
name : 'filename',
label: 'Filename',
cell : FilenameCell
},
{
name : 'lastWriteTime',
label: 'Last Write Time',
cell : RelativeDateCell
}
],
initialize: function () {
this.collection = new LogFileCollection();
this.collectionPromise = this.collection.fetch();
App.vent.on(App.Commands.ShowLogFile, this._showLogFile, this);
},
onShow: function () {
var self = this;
this._showTable();
this.collectionPromise.done(function (){
self._showLogFile({ model: _.first(self.collection.models) });
});
},
_showTable: function () {
this.grid.show(new Backgrid.Grid({
row : LogFileRow,
columns : this.columns,
collection: this.collection,
className : 'table table-hover'
}));
},
_showLogFile: function (options) {
var self = this;
var filename = options.model.get('filename');
$.ajax({
url: '/log/' + filename,
success: function (data) {
var model = new ContentsModel({
filename: filename,
contents: data
});
self.contents.show(new ContentsView({ model: model }));
}
});
}
});
});

@ -0,0 +1,11 @@
<div class="row">
<div class="span12">
<div id="x-grid"/>
</div>
</div>
<div class="row">
<div class="span12">
<div id="x-contents"/>
</div>
</div>

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

@ -0,0 +1,19 @@
'use strict';
define(
[
'app',
'backgrid'
], function (App, Backgrid) {
return Backgrid.Row.extend({
className: 'log-file-row',
events: {
'click': '_showContents'
},
_showContents: function () {
App.vent.trigger(App.Commands.ShowLogFile, { model: this.model });
}
});
});

@ -6,8 +6,9 @@ define(
'Logs/LogTimeCell', 'Logs/LogTimeCell',
'Logs/LogLevelCell', 'Logs/LogLevelCell',
'Shared/Grid/Pager', 'Shared/Grid/Pager',
'Logs/Collection' 'Logs/Collection',
], function (Marionette, Backgrid, LogTimeCell, LogLevelCell, GridPager, LogCollection) { 'Shared/Toolbar/ToolbarLayout'
], function (Marionette, Backgrid, LogTimeCell, LogLevelCell, GridPager, LogCollection, ToolbarLayout) {
return Marionette.Layout.extend({ return Marionette.Layout.extend({
template: 'Logs/LayoutTemplate', template: 'Logs/LayoutTemplate',
@ -52,7 +53,30 @@ define(
} }
], ],
showTable: function () { leftSideButtons: {
type : 'default',
storeState: false,
items :
[
{
title: 'Files',
icon : 'icon-file',
route: 'logs/files'
}
]
},
initialize: function () {
this.collection = new LogCollection();
this.collection.fetch();
},
onShow: function () {
this._showToolbar();
this._showTable();
},
_showTable: function () {
this.grid.show(new Backgrid.Grid({ this.grid.show(new Backgrid.Grid({
row : Backgrid.Row, row : Backgrid.Row,
@ -67,14 +91,14 @@ define(
})); }));
}, },
initialize: function () { _showToolbar: function () {
this.collection = new LogCollection(); this.toolbar.show(new ToolbarLayout({
this.collection.fetch(); left :
}, [
this.leftSideButtons
onShow: function () { ],
this.showTable(); context: this
}));
} }
}); });
}); });

@ -1,4 +1,5 @@
@import "../Content/FontAwesome/font-awesome"; @import "../Content/FontAwesome/font-awesome";
@import "../Shared/Styles/clickable";
#logs-screen { #logs-screen {
@ -45,6 +46,8 @@
.icon(@remove-sign); .icon(@remove-sign);
color : purple; color : purple;
} }
} }
.log-file-row {
.clickable;
}

@ -1,16 +0,0 @@
'use strict';
define(
[
'backgrid'
], function (Backgrid) {
return Backgrid.Row.extend({
events: {
'click .x-search': 'search'
},
search: function () {
window.alert('Episode Search');
}
});
});

@ -22,6 +22,7 @@ require(
'missing' : 'missing', 'missing' : 'missing',
'history' : 'history', 'history' : 'history',
'logs' : 'logs', 'logs' : 'logs',
'logs/:action' : 'logs',
'rss' : 'rss', 'rss' : 'rss',
'system' : 'system', 'system' : 'system',
':whatever' : 'notFound' ':whatever' : 'notFound'

@ -179,7 +179,8 @@ define(
DeleteSeriesCommand: 'DeleteSeriesCommand', DeleteSeriesCommand: 'DeleteSeriesCommand',
CloseModalCommand : 'CloseModalCommand', CloseModalCommand : 'CloseModalCommand',
ShowEpisodeDetails : 'ShowEpisodeDetails', ShowEpisodeDetails : 'ShowEpisodeDetails',
SaveSettings : 'saveSettings' SaveSettings : 'saveSettings',
ShowLogFile : 'showLogFile'
}; };
app.addInitializer(function () { app.addInitializer(function () {

Loading…
Cancel
Save