mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-12-25 02:30:20 +02:00
Backups
New: Backup drone's database and configuration from the UI New: Download Backup files from the UI Fixed: Run a database backup before upgrade
This commit is contained in:
parent
d74e461aea
commit
c5bd8b27fb
1
.gitignore
vendored
1
.gitignore
vendored
@ -84,7 +84,6 @@ Generated_Code #added for RIA/Silverlight projects
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
|
||||
# SQL Server files
|
||||
|
31
src/NzbDrone.Api/Frontend/Mappers/BackupFileMapper.cs
Normal file
31
src/NzbDrone.Api/Frontend/Mappers/BackupFileMapper.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Api.Frontend.Mappers
|
||||
{
|
||||
public class BackupFileMapper : StaticResourceMapperBase
|
||||
{
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
|
||||
public BackupFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
|
||||
: base(diskProvider, logger)
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
}
|
||||
|
||||
protected override string Map(string resourceUrl)
|
||||
{
|
||||
var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);
|
||||
|
||||
return Path.Combine(_appFolderInfo.GetBackupFolder(), path);
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
{
|
||||
return resourceUrl.StartsWith("/backup/") && resourceUrl.ContainsIgnoreCase("nzbdrone_backup_") && resourceUrl.EndsWith(".zip");
|
||||
}
|
||||
}
|
||||
}
|
@ -131,6 +131,7 @@
|
||||
<Compile Include="Extensions\RequestExtensions.cs" />
|
||||
<Compile Include="Frontend\IsCacheableSpecification.cs" />
|
||||
<Compile Include="Frontend\Mappers\UpdateLogFileMapper.cs" />
|
||||
<Compile Include="Frontend\Mappers\BackupFileMapper.cs" />
|
||||
<Compile Include="Frontend\Mappers\FaviconMapper.cs" />
|
||||
<Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" />
|
||||
<Compile Include="Frontend\Mappers\LogFileMapper.cs" />
|
||||
@ -165,6 +166,8 @@
|
||||
<Compile Include="Mapping\ResourceMappingException.cs" />
|
||||
<Compile Include="Mapping\ValueInjectorExtensions.cs" />
|
||||
<Compile Include="Series\AlternateTitleResource.cs" />
|
||||
<Compile Include="System\Backup\BackupModule.cs" />
|
||||
<Compile Include="System\Backup\BackupResource.cs" />
|
||||
<Compile Include="Update\UpdateResource.cs" />
|
||||
<Compile Include="Wanted\CutoffModule.cs" />
|
||||
<Compile Include="Wanted\LegacyMissingModule.cs" />
|
||||
|
32
src/NzbDrone.Api/System/Backup/BackupModule.cs
Normal file
32
src/NzbDrone.Api/System/Backup/BackupModule.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Backup;
|
||||
|
||||
namespace NzbDrone.Api.System.Backup
|
||||
{
|
||||
public class BackupModule : NzbDroneRestModule<BackupResource>
|
||||
{
|
||||
private readonly IBackupService _backupService;
|
||||
|
||||
public BackupModule(IBackupService backupService) : base("system/backup")
|
||||
{
|
||||
_backupService = backupService;
|
||||
GetResourceAll = GetBackupFiles;
|
||||
}
|
||||
|
||||
public List<BackupResource> GetBackupFiles()
|
||||
{
|
||||
var backups = _backupService.GetBackups();
|
||||
|
||||
return backups.Select(b => new BackupResource
|
||||
{
|
||||
Id = b.Path.GetHashCode(),
|
||||
Name = Path.GetFileName(b.Path),
|
||||
Path = b.Path,
|
||||
Type = b.Type,
|
||||
Time = b.Time
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
14
src/NzbDrone.Api/System/Backup/BackupResource.cs
Normal file
14
src/NzbDrone.Api/System/Backup/BackupResource.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Backup;
|
||||
|
||||
namespace NzbDrone.Api.System.Backup
|
||||
{
|
||||
public class BackupResource : RestResource
|
||||
{
|
||||
public String Name { get; set; }
|
||||
public String Path { get; set; }
|
||||
public BackupType Type { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
}
|
||||
}
|
@ -172,7 +172,7 @@ public void should_be_able_to_delete_directory_with_read_only_file()
|
||||
public void empty_folder_should_return_folder_modified_date()
|
||||
{
|
||||
var tempfolder = new DirectoryInfo(TempFolder);
|
||||
Subject.FolderGetLastWrite(TempFolder).Should().Be(tempfolder.LastWriteTimeUtc);
|
||||
Subject.FolderGetLastWriteUtc(TempFolder).Should().Be(tempfolder.LastWriteTimeUtc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -189,8 +189,8 @@ public void folder_should_return_correct_value_for_last_write()
|
||||
|
||||
Subject.WriteAllText(testFile, "Test");
|
||||
|
||||
Subject.FolderGetLastWrite(TempFolder).Should().BeOnOrAfter(DateTime.UtcNow.AddMinutes(-1));
|
||||
Subject.FolderGetLastWrite(TempFolder).Should().BeBefore(DateTime.UtcNow.AddMinutes(1));
|
||||
Subject.FolderGetLastWriteUtc(TempFolder).Should().BeOnOrAfter(DateTime.UtcNow.AddMinutes(-1));
|
||||
Subject.FolderGetLastWriteUtc(TempFolder).Should().BeBefore(DateTime.UtcNow.AddMinutes(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -238,7 +238,7 @@ public void should_be_able_to_set_permission_from_parrent()
|
||||
[Explicit]
|
||||
public void check_last_write()
|
||||
{
|
||||
Console.WriteLine(Subject.FolderGetLastWrite(GetFilledTempFolder().FullName));
|
||||
Console.WriteLine(Subject.FolderGetLastWriteUtc(GetFilledTempFolder().FullName));
|
||||
Console.WriteLine(GetFilledTempFolder().LastWriteTimeUtc);
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ namespace NzbDrone.Common
|
||||
public interface IArchiveService
|
||||
{
|
||||
void Extract(string compressedFile, string destination);
|
||||
void ExtractZip(string compressedFile, string destination);
|
||||
void CreateZip(string path, params string[] files);
|
||||
}
|
||||
|
||||
public class ArchiveService : IArchiveService
|
||||
@ -40,7 +42,22 @@ public void Extract(string compressedFile, string destination)
|
||||
_logger.Debug("Extraction complete.");
|
||||
}
|
||||
|
||||
private void ExtractZip(string compressedFile, string destination)
|
||||
public void CreateZip(string path, params string[] files)
|
||||
{
|
||||
using (var zipFile = ZipFile.Create(path))
|
||||
{
|
||||
zipFile.BeginUpdate();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
zipFile.Add(file, Path.GetFileName(file));
|
||||
}
|
||||
|
||||
zipFile.CommitUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void ExtractZip(string compressedFile, string destination)
|
||||
{
|
||||
using (var fileStream = File.OpenRead(compressedFile))
|
||||
{
|
@ -33,7 +33,7 @@ public DateTime FolderGetCreationTimeUtc(string path)
|
||||
return new DirectoryInfo(path).CreationTimeUtc;
|
||||
}
|
||||
|
||||
public DateTime FolderGetLastWrite(string path)
|
||||
public DateTime FolderGetLastWriteUtc(string path)
|
||||
{
|
||||
CheckFolderExists(path);
|
||||
|
||||
|
@ -12,7 +12,7 @@ public interface IDiskProvider
|
||||
void SetPermissions(string path, string mask, string user, string group);
|
||||
long? GetTotalSize(string path);
|
||||
DateTime FolderGetCreationTimeUtc(string path);
|
||||
DateTime FolderGetLastWrite(string path);
|
||||
DateTime FolderGetLastWriteUtc(string path);
|
||||
DateTime FileGetCreationTimeUtc(string path);
|
||||
DateTime FileGetLastWrite(string path);
|
||||
DateTime FileGetLastWriteUtc(string path);
|
||||
|
@ -59,8 +59,8 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ArchiveProvider.cs" />
|
||||
<Compile Include="ConvertBase32.cs" />
|
||||
<Compile Include="ArchiveService.cs" />
|
||||
<Compile Include="Cache\Cached.cs" />
|
||||
<Compile Include="Cache\CacheManager.cs" />
|
||||
<Compile Include="Cache\ICached.cs" />
|
||||
|
@ -11,9 +11,9 @@ public static class PathExtensions
|
||||
private const string APP_CONFIG_FILE = "config.xml";
|
||||
private const string NZBDRONE_DB = "nzbdrone.db";
|
||||
private const string NZBDRONE_LOG_DB = "logs.db";
|
||||
private const string BACKUP_ZIP_FILE = "NzbDrone_Backup.zip";
|
||||
private const string NLOG_CONFIG_FILE = "nlog.config";
|
||||
private const string UPDATE_CLIENT_EXE = "NzbDrone.Update.exe";
|
||||
private const string BACKUP_FOLDER = "Backups";
|
||||
|
||||
private static readonly string UPDATE_SANDBOX_FOLDER_NAME = "nzbdrone_update" + Path.DirectorySeparatorChar;
|
||||
private static readonly string UPDATE_PACKAGE_FOLDER_NAME = "NzbDrone" + Path.DirectorySeparatorChar;
|
||||
@ -226,9 +226,9 @@ public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo)
|
||||
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE);
|
||||
}
|
||||
|
||||
public static string GetConfigBackupFile(this IAppFolderInfo appFolderInfo)
|
||||
public static string GetBackupFolder(this IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
return Path.Combine(GetAppDataPath(appFolderInfo), BACKUP_ZIP_FILE);
|
||||
return Path.Combine(GetAppDataPath(appFolderInfo), BACKUP_FOLDER);
|
||||
}
|
||||
|
||||
public static string GetNzbDroneDatabase(this IAppFolderInfo appFolderInfo)
|
||||
|
@ -18,7 +18,7 @@ public class CleanupFixture : CoreTest
|
||||
|
||||
private void WithExpired()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderGetLastWrite(It.IsAny<String>()))
|
||||
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderGetLastWriteUtc(It.IsAny<String>()))
|
||||
.Returns(DateTime.UtcNow.AddDays(-10));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Setup(s => s.FileGetLastWriteUtc(It.IsAny<String>()))
|
||||
@ -27,7 +27,7 @@ private void WithExpired()
|
||||
|
||||
private void WithNonExpired()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderGetLastWrite(It.IsAny<String>()))
|
||||
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderGetLastWriteUtc(It.IsAny<String>()))
|
||||
.Returns(DateTime.UtcNow.AddDays(-3));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Setup(s => s.FileGetLastWriteUtc(It.IsAny<String>()))
|
||||
|
11
src/NzbDrone.Core/Backup/Backup.cs
Normal file
11
src/NzbDrone.Core/Backup/Backup.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Backup
|
||||
{
|
||||
public class Backup
|
||||
{
|
||||
public String Path { get; set; }
|
||||
public BackupType Type { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
}
|
||||
}
|
24
src/NzbDrone.Core/Backup/BackupCommand.cs
Normal file
24
src/NzbDrone.Core/Backup/BackupCommand.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.Backup
|
||||
{
|
||||
public class BackupCommand : Command
|
||||
{
|
||||
public BackupType Type { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum BackupType
|
||||
{
|
||||
Scheduled = 0 ,
|
||||
Manual = 1,
|
||||
Update = 2
|
||||
}
|
||||
}
|
168
src/NzbDrone.Core/Backup/BackupService.cs
Normal file
168
src/NzbDrone.Core/Backup/BackupService.cs
Normal file
@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Marr.Data;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.Backup
|
||||
{
|
||||
public interface IBackupService
|
||||
{
|
||||
void Backup(BackupType backupType);
|
||||
List<Backup> GetBackups();
|
||||
}
|
||||
|
||||
public class BackupService : IBackupService, IExecute<BackupCommand>
|
||||
{
|
||||
private readonly IDatabase _maindDb;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IArchiveService _archiveService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private string _backupTempFolder;
|
||||
|
||||
private static readonly Regex BackupFileRegex = new Regex(@"nzbdrone_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public BackupService(IDatabase maindDb,
|
||||
IDiskProvider diskProvider,
|
||||
IAppFolderInfo appFolderInfo,
|
||||
IArchiveService archiveService,
|
||||
Logger logger)
|
||||
{
|
||||
_maindDb = maindDb;
|
||||
_diskProvider = diskProvider;
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_archiveService = archiveService;
|
||||
_logger = logger;
|
||||
|
||||
_backupTempFolder = Path.Combine(_appFolderInfo.TempFolder, "nzbdrone_backup");
|
||||
}
|
||||
|
||||
public void Backup(BackupType backupType)
|
||||
{
|
||||
_logger.ProgressInfo("Starting Backup");
|
||||
|
||||
var backupFilename = String.Format("nzbdrone_backup_{0:yyyy.MM.dd_HH.mm.ss}.zip", DateTime.Now);
|
||||
var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
|
||||
|
||||
Cleanup();
|
||||
|
||||
if (backupType != BackupType.Manual)
|
||||
{
|
||||
CleanupOldBackups(backupType);
|
||||
}
|
||||
|
||||
_diskProvider.EnsureFolder(_backupTempFolder);
|
||||
_diskProvider.EnsureFolder(_appFolderInfo.GetBackupFolder());
|
||||
|
||||
BackupConfigFile();
|
||||
BackupDatabase();
|
||||
|
||||
_logger.ProgressDebug("Creating backup zip");
|
||||
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
|
||||
_logger.ProgressDebug("Backup zip created");
|
||||
}
|
||||
|
||||
public List<Backup> GetBackups()
|
||||
{
|
||||
var backups = new List<Backup>();
|
||||
|
||||
foreach (var backupType in Enum.GetValues(typeof(BackupType)).Cast<BackupType>())
|
||||
{
|
||||
var folder = GetBackupFolder(backupType);
|
||||
|
||||
if (_diskProvider.FolderExists(folder))
|
||||
{
|
||||
backups.AddRange(GetBackupFiles(folder).Select(b => new Backup
|
||||
{
|
||||
Path = Path.GetFileName(b),
|
||||
Type = backupType,
|
||||
Time = _diskProvider.FileGetLastWriteUtc(b)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return backups;
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
if (_diskProvider.FolderExists(_backupTempFolder))
|
||||
{
|
||||
_diskProvider.EmptyFolder(_backupTempFolder);
|
||||
}
|
||||
}
|
||||
|
||||
private void BackupDatabase()
|
||||
{
|
||||
_logger.ProgressDebug("Backing up database");
|
||||
|
||||
using (var unitOfWork = new UnitOfWork(() => _maindDb.GetDataMapper()))
|
||||
{
|
||||
unitOfWork.BeginTransaction();
|
||||
|
||||
var databaseFile = _appFolderInfo.GetNzbDroneDatabase();
|
||||
var tempDatabaseFile = Path.Combine(_backupTempFolder, Path.GetFileName(databaseFile));
|
||||
|
||||
_diskProvider.CopyFile(databaseFile, tempDatabaseFile, true);
|
||||
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
private void BackupConfigFile()
|
||||
{
|
||||
_logger.ProgressDebug("Backing up config.xml");
|
||||
|
||||
var configFile = _appFolderInfo.GetConfigPath();
|
||||
var tempConfigFile = Path.Combine(_backupTempFolder, Path.GetFileName(configFile));
|
||||
|
||||
_diskProvider.CopyFile(configFile, tempConfigFile, true);
|
||||
}
|
||||
|
||||
private void CleanupOldBackups(BackupType backupType)
|
||||
{
|
||||
_logger.Debug("Cleaning up old backup files");
|
||||
var files = GetBackupFiles(GetBackupFolder(backupType));
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var lastWriteTime = _diskProvider.FileGetLastWriteUtc(file);
|
||||
|
||||
if (lastWriteTime.AddDays(28) < DateTime.UtcNow)
|
||||
{
|
||||
_logger.Debug("Deleting old backup file: {0}", file);
|
||||
_diskProvider.DeleteFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Finished cleaning up old backup files");
|
||||
}
|
||||
|
||||
private String GetBackupFolder(BackupType backupType)
|
||||
{
|
||||
return Path.Combine(_appFolderInfo.GetBackupFolder(), backupType.ToString().ToLower());
|
||||
}
|
||||
|
||||
private IEnumerable<String> GetBackupFiles(String path)
|
||||
{
|
||||
var files = _diskProvider.GetFiles(path, SearchOption.TopDirectoryOnly);
|
||||
|
||||
return files.Where(f => BackupFileRegex.IsMatch(f));
|
||||
}
|
||||
|
||||
public void Execute(BackupCommand message)
|
||||
{
|
||||
Backup(message.Type);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Backup;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
@ -56,6 +57,7 @@ public void Handle(ApplicationStartedEvent message)
|
||||
new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName},
|
||||
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName},
|
||||
new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
|
||||
new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName},
|
||||
|
||||
new ScheduledTask
|
||||
{
|
||||
|
@ -128,7 +128,7 @@ public void Cleanup()
|
||||
|
||||
foreach (var folder in _diskProvider.GetDirectories(_configService.RecycleBin))
|
||||
{
|
||||
if (_diskProvider.FolderGetLastWrite(folder).AddDays(7) > DateTime.UtcNow)
|
||||
if (_diskProvider.FolderGetLastWriteUtc(folder).AddDays(7) > DateTime.UtcNow)
|
||||
{
|
||||
logger.Debug("Folder hasn't expired yet, skipping: {0}", folder);
|
||||
continue;
|
||||
|
@ -104,6 +104,9 @@
|
||||
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Annotations\FieldDefinitionAttribute.cs" />
|
||||
<Compile Include="Backup\Backup.cs" />
|
||||
<Compile Include="Backup\BackupCommand.cs" />
|
||||
<Compile Include="Backup\BackupService.cs" />
|
||||
<Compile Include="Blacklisting\Blacklist.cs" />
|
||||
<Compile Include="Blacklisting\BlacklistRepository.cs" />
|
||||
<Compile Include="Blacklisting\BlacklistService.cs" />
|
||||
|
@ -168,3 +168,7 @@ td.delete-episode-file-cell {
|
||||
.episode-number-cell {
|
||||
cursor : default;
|
||||
}
|
||||
|
||||
.backup-type-cell {
|
||||
width : 20px;
|
||||
}
|
||||
|
@ -95,6 +95,8 @@
|
||||
}
|
||||
|
||||
th {
|
||||
cursor : default;
|
||||
|
||||
&.sortable {
|
||||
&:hover {
|
||||
background : @table-bg-hover;
|
||||
|
@ -12,7 +12,6 @@ define(
|
||||
'Release/ReleaseLayout',
|
||||
'System/SystemLayout',
|
||||
'SeasonPass/SeasonPassLayout',
|
||||
'System/Update/UpdateLayout',
|
||||
'Series/Editor/SeriesEditorLayout'
|
||||
], function (NzbDroneController,
|
||||
AppLayout,
|
||||
@ -25,7 +24,6 @@ define(
|
||||
ReleaseLayout,
|
||||
SystemLayout,
|
||||
SeasonPassLayout,
|
||||
UpdateLayout,
|
||||
SeriesEditorLayout) {
|
||||
return NzbDroneController.extend({
|
||||
|
||||
@ -71,11 +69,6 @@ define(
|
||||
this.showMainRegion(new SeasonPassLayout());
|
||||
},
|
||||
|
||||
update: function () {
|
||||
this.setTitle('Updates');
|
||||
this.showMainRegion(new UpdateLayout());
|
||||
},
|
||||
|
||||
seriesEditor: function () {
|
||||
this.setTitle('Series Editor');
|
||||
this.showMainRegion(new SeriesEditorLayout());
|
||||
|
19
src/UI/System/Backup/BackupCollection.js
Normal file
19
src/UI/System/Backup/BackupCollection.js
Normal file
@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
define(
|
||||
[
|
||||
'backbone.pageable',
|
||||
'System/Backup/BackupModel'
|
||||
], function (PageableCollection, BackupModel) {
|
||||
return PageableCollection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/system/backup',
|
||||
model: BackupModel,
|
||||
|
||||
state: {
|
||||
sortKey : 'time',
|
||||
order : 1,
|
||||
pageSize : 100000
|
||||
},
|
||||
|
||||
mode: 'client'
|
||||
});
|
||||
});
|
10
src/UI/System/Backup/BackupEmptyView.js
Normal file
10
src/UI/System/Backup/BackupEmptyView.js
Normal file
@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
define(
|
||||
[
|
||||
'marionette'
|
||||
], function (Marionette) {
|
||||
return Marionette.ItemView.extend({
|
||||
template: 'System/Backup/BackupEmptyViewTemplate'
|
||||
});
|
||||
});
|
1
src/UI/System/Backup/BackupEmptyViewTemplate.html
Normal file
1
src/UI/System/Backup/BackupEmptyViewTemplate.html
Normal file
@ -0,0 +1 @@
|
||||
<div>No backups are available</div>
|
12
src/UI/System/Backup/BackupFilenameCell.js
Normal file
12
src/UI/System/Backup/BackupFilenameCell.js
Normal file
@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
define(
|
||||
[
|
||||
'Cells/TemplatedCell'
|
||||
], function (TemplatedCell) {
|
||||
return TemplatedCell.extend({
|
||||
|
||||
className: 'series-title',
|
||||
template : 'System/Backup/BackupFilenameCellTemplate'
|
||||
|
||||
});
|
||||
});
|
2
src/UI/System/Backup/BackupFilenameCellTemplate.html
Normal file
2
src/UI/System/Backup/BackupFilenameCellTemplate.html
Normal file
@ -0,0 +1,2 @@
|
||||
<a href="{{urlBack}}/backup/{{type}}/{{name}}" class="no-router">{{name}}</a>
|
||||
|
106
src/UI/System/Backup/BackupLayout.js
Normal file
106
src/UI/System/Backup/BackupLayout.js
Normal file
@ -0,0 +1,106 @@
|
||||
'use strict';
|
||||
define(
|
||||
[
|
||||
'vent',
|
||||
'marionette',
|
||||
'backgrid',
|
||||
'System/Backup/BackupCollection',
|
||||
'Cells/RelativeDateCell',
|
||||
'System/Backup/BackupFilenameCell',
|
||||
'System/Backup/BackupTypeCell',
|
||||
'System/Backup/BackupEmptyView',
|
||||
'Shared/LoadingView',
|
||||
'Shared/Toolbar/ToolbarLayout'
|
||||
], function (vent, Marionette, Backgrid, BackupCollection, RelativeDateCell, BackupFilenameCell, BackupTypeCell, EmptyView, LoadingView, ToolbarLayout) {
|
||||
return Marionette.Layout.extend({
|
||||
template: 'System/Backup/BackupLayoutTemplate',
|
||||
|
||||
regions: {
|
||||
backups : '#x-backups',
|
||||
toolbar : '#x-backup-toolbar'
|
||||
},
|
||||
|
||||
columns: [
|
||||
{
|
||||
name : 'type',
|
||||
label : '',
|
||||
sortable : false,
|
||||
cell : BackupTypeCell
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : 'Name',
|
||||
sortable : false,
|
||||
cell : BackupFilenameCell
|
||||
},
|
||||
{
|
||||
name : 'time',
|
||||
label : 'Time',
|
||||
sortable : false,
|
||||
cell : RelativeDateCell
|
||||
}
|
||||
],
|
||||
|
||||
leftSideButtons: {
|
||||
type : 'default',
|
||||
storeState: false,
|
||||
collapse : false,
|
||||
items :
|
||||
[
|
||||
{
|
||||
title : 'Backup',
|
||||
icon : 'icon-file-text',
|
||||
command : 'backup',
|
||||
properties : { type: 'manual' },
|
||||
successMessage: 'Database and settings were backed up successfully',
|
||||
errorMessage : 'Backup Failed!'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.backupCollection = new BackupCollection();
|
||||
|
||||
this.listenTo(this.backupCollection, 'sync', this._showBackups);
|
||||
this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete);
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this._showToolbar();
|
||||
this.backups.show(new LoadingView());
|
||||
|
||||
this.backupCollection.fetch();
|
||||
},
|
||||
|
||||
_showBackups: function () {
|
||||
|
||||
if (this.backupCollection.length === 0) {
|
||||
this.backups.show(new EmptyView());
|
||||
}
|
||||
|
||||
else {
|
||||
this.backups.show(new Backgrid.Grid({
|
||||
columns : this.columns,
|
||||
collection: this.backupCollection,
|
||||
className : 'table table-hover'
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_showToolbar : function () {
|
||||
this.toolbar.show(new ToolbarLayout({
|
||||
left :
|
||||
[
|
||||
this.leftSideButtons
|
||||
],
|
||||
context: this
|
||||
}));
|
||||
},
|
||||
|
||||
_commandComplete: function (options) {
|
||||
if (options.command.get('name') === 'backup') {
|
||||
this.backupCollection.fetch();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
10
src/UI/System/Backup/BackupLayoutTemplate.html
Normal file
10
src/UI/System/Backup/BackupLayoutTemplate.html
Normal file
@ -0,0 +1,10 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="x-backup-toolbar"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="x-backups" class="table-responsive"/>
|
||||
</div>
|
||||
</div>
|
9
src/UI/System/Backup/BackupModel.js
Normal file
9
src/UI/System/Backup/BackupModel.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
define(
|
||||
[
|
||||
'backbone'
|
||||
], function (Backbone) {
|
||||
return Backbone.Model.extend({
|
||||
|
||||
});
|
||||
});
|
33
src/UI/System/Backup/BackupTypeCell.js
Normal file
33
src/UI/System/Backup/BackupTypeCell.js
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
define(
|
||||
[
|
||||
'Cells/NzbDroneCell'
|
||||
], function (NzbDroneCell) {
|
||||
return NzbDroneCell.extend({
|
||||
|
||||
className: 'backup-type-cell',
|
||||
|
||||
render: function () {
|
||||
this.$el.empty();
|
||||
|
||||
var icon = 'icon-time';
|
||||
var title = 'Scheduled';
|
||||
|
||||
var type = this.model.get(this.column.get('name'));
|
||||
|
||||
if (type === 'manual') {
|
||||
icon = 'icon-book';
|
||||
title = 'Manual';
|
||||
}
|
||||
|
||||
else if (type === 'update') {
|
||||
icon = 'icon-retweet';
|
||||
title = 'Before update';
|
||||
}
|
||||
|
||||
this.$el.html('<i class="{0}" title="{1}"></i>'.format(icon, title));
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
});
|
@ -7,6 +7,7 @@ define(
|
||||
'System/Info/SystemInfoLayout',
|
||||
'System/Logs/LogsLayout',
|
||||
'System/Update/UpdateLayout',
|
||||
'System/Backup/BackupLayout',
|
||||
'Shared/Messenger'
|
||||
], function ($,
|
||||
Backbone,
|
||||
@ -14,26 +15,30 @@ define(
|
||||
SystemInfoLayout,
|
||||
LogsLayout,
|
||||
UpdateLayout,
|
||||
BackupLayout,
|
||||
Messenger) {
|
||||
return Marionette.Layout.extend({
|
||||
template: 'System/SystemLayoutTemplate',
|
||||
|
||||
regions: {
|
||||
info : '#info',
|
||||
info : '#info',
|
||||
logs : '#logs',
|
||||
updates: '#updates'
|
||||
updates : '#updates',
|
||||
backup : '#backup'
|
||||
},
|
||||
|
||||
ui: {
|
||||
infoTab : '.x-info-tab',
|
||||
logsTab : '.x-logs-tab',
|
||||
updatesTab: '.x-updates-tab'
|
||||
infoTab : '.x-info-tab',
|
||||
logsTab : '.x-logs-tab',
|
||||
updatesTab : '.x-updates-tab',
|
||||
backupTab : '.x-backup-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'
|
||||
},
|
||||
@ -52,6 +57,9 @@ define(
|
||||
case 'updates':
|
||||
this._showUpdates();
|
||||
break;
|
||||
case 'backup':
|
||||
this._showBackup();
|
||||
break;
|
||||
default:
|
||||
this._showInfo();
|
||||
}
|
||||
@ -91,6 +99,16 @@ define(
|
||||
this._navigate('system/updates');
|
||||
},
|
||||
|
||||
_showBackup: function (e) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.backup.show(new BackupLayout());
|
||||
this.ui.backupTab.tab('show');
|
||||
this._navigate('system/backup');
|
||||
},
|
||||
|
||||
_shutdown: function () {
|
||||
$.ajax({
|
||||
url: window.NzbDrone.ApiRoot + '/system/shutdown',
|
||||
|
@ -2,6 +2,7 @@
|
||||
<li><a href="#info" class="x-info-tab no-router">Info</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="#backup" class="x-backup-tab no-router">Backup</a></li>
|
||||
<li class="lifecycle-controls pull-right">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-default btn-icon-only x-shutdown" title="Shutdown" data-container="body">
|
||||
@ -18,4 +19,5 @@
|
||||
<div class="tab-pane" id="info"></div>
|
||||
<div class="tab-pane" id="logs"></div>
|
||||
<div class="tab-pane" id="updates"></div>
|
||||
<div class="tab-pane" id="backup"></div>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user