mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 21:33:15 -07:00
commit
b100fbe678
28 changed files with 383 additions and 72 deletions
|
@ -64,7 +64,7 @@ namespace PlexRequests.Api
|
||||||
};
|
};
|
||||||
|
|
||||||
var options = new SonarrAddSeries();
|
var options = new SonarrAddSeries();
|
||||||
if (episodes == true)
|
if (episodes)
|
||||||
{
|
{
|
||||||
options.addOptions = new AddOptions
|
options.addOptions = new AddOptions
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,7 +31,9 @@ namespace PlexRequests.Core.Models
|
||||||
{
|
{
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
public bool UpdateAvailable { get; set; }
|
public bool UpdateAvailable { get; set; }
|
||||||
public int ReleasesBehind { get; set; }
|
|
||||||
public string UpdateUri { get; set; }
|
public string UpdateUri { get; set; }
|
||||||
|
public string DownloadUri { get; set; }
|
||||||
|
public string ReleaseNotes { get; set; }
|
||||||
|
public string ReleaseTitle { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -33,6 +33,7 @@ namespace PlexRequests.Core.SettingModels
|
||||||
{
|
{
|
||||||
public class CouchPotatoSettings : Settings
|
public class CouchPotatoSettings : Settings
|
||||||
{
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
public string Ip { get; set; }
|
public string Ip { get; set; }
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
public string ApiKey { get; set; }
|
public string ApiKey { get; set; }
|
||||||
|
|
|
@ -72,6 +72,10 @@ namespace PlexRequests.Core
|
||||||
model.UpdateUri = latestRelease.Result.HtmlUrl;
|
model.UpdateUri = latestRelease.Result.HtmlUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.ReleaseNotes = latestRelease.Result.Body;
|
||||||
|
model.DownloadUri = latestRelease.Result.Assets[0].BrowserDownloadUrl;
|
||||||
|
model.ReleaseTitle = latestRelease.Result.Name;
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,13 @@
|
||||||
#endregion
|
#endregion
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using Nancy.Authentication.Forms;
|
using Nancy.Authentication.Forms;
|
||||||
using Nancy.Security;
|
using Nancy.Security;
|
||||||
|
|
||||||
|
using PlexRequests.Helpers;
|
||||||
using PlexRequests.Store;
|
using PlexRequests.Store;
|
||||||
|
|
||||||
namespace PlexRequests.Core
|
namespace PlexRequests.Core
|
||||||
|
@ -44,7 +46,7 @@ namespace PlexRequests.Core
|
||||||
private static ISqliteConfiguration Db { get; set; }
|
private static ISqliteConfiguration Db { get; set; }
|
||||||
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
|
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
|
||||||
{
|
{
|
||||||
var repo = new UserRepository<UserModel>(Db);
|
var repo = new UserRepository<UsersModel>(Db);
|
||||||
|
|
||||||
var user = repo.Get(identifier.ToString());
|
var user = repo.Get(identifier.ToString());
|
||||||
|
|
||||||
|
@ -61,35 +63,65 @@ namespace PlexRequests.Core
|
||||||
|
|
||||||
public static Guid? ValidateUser(string username, string password)
|
public static Guid? ValidateUser(string username, string password)
|
||||||
{
|
{
|
||||||
var repo = new UserRepository<UserModel>(Db);
|
var repo = new UserRepository<UsersModel>(Db);
|
||||||
var users = repo.GetAll();
|
var users = repo.GetAll();
|
||||||
var userRecord = users.FirstOrDefault(u => u.UserName.Equals(username, StringComparison.InvariantCultureIgnoreCase) && u.Password.Equals(password)); // TODO hashing
|
|
||||||
|
|
||||||
if (userRecord == null)
|
foreach (var u in users)
|
||||||
{
|
{
|
||||||
return null;
|
if (username == u.UserName)
|
||||||
|
{
|
||||||
|
var passwordMatch = PasswordHasher.VerifyPassword(password, u.Salt, u.Hash);
|
||||||
|
if (passwordMatch)
|
||||||
|
{
|
||||||
|
return new Guid(u.UserGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
return new Guid(userRecord.User);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool DoUsersExist()
|
public static bool DoUsersExist()
|
||||||
{
|
{
|
||||||
var repo = new UserRepository<UserModel>(Db);
|
var repo = new UserRepository<UsersModel>(Db);
|
||||||
var users = repo.GetAll();
|
var users = repo.GetAll();
|
||||||
|
|
||||||
return users.Any();
|
return users.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Guid? CreateUser(string username, string password)
|
public static Guid? CreateUser(string username, string password)
|
||||||
{
|
{
|
||||||
var repo = new UserRepository<UserModel>(Db);
|
var repo = new UserRepository<UsersModel>(Db);
|
||||||
|
var salt = PasswordHasher.GenerateSalt();
|
||||||
|
|
||||||
var userModel = new UserModel { UserName = username, User = Guid.NewGuid().ToString(), Password = password };
|
var userModel = new UsersModel { UserName = username, UserGuid = Guid.NewGuid().ToString(), Salt = salt, Hash = PasswordHasher.ComputeHash(password, salt)};
|
||||||
repo.Insert(userModel);
|
repo.Insert(userModel);
|
||||||
|
|
||||||
var userRecord = repo.Get(userModel.User);
|
var userRecord = repo.Get(userModel.UserGuid);
|
||||||
|
|
||||||
return new Guid(userRecord.User);
|
return new Guid(userRecord.UserGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool UpdateUser(string username, string oldPassword, string newPassword)
|
||||||
|
{
|
||||||
|
var repo = new UserRepository<UsersModel>(Db);
|
||||||
|
var users = repo.GetAll();
|
||||||
|
var userToChange = users.FirstOrDefault(x => x.UserName == username);
|
||||||
|
if (userToChange == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash);
|
||||||
|
if (!passwordMatch)
|
||||||
|
{
|
||||||
|
throw new SecurityException("Password does not match");
|
||||||
|
}
|
||||||
|
|
||||||
|
var newSalt = PasswordHasher.GenerateSalt();
|
||||||
|
var newHash = PasswordHasher.ComputeHash(newPassword, newSalt);
|
||||||
|
|
||||||
|
userToChange.Hash = newHash;
|
||||||
|
userToChange.Salt = newSalt;
|
||||||
|
|
||||||
|
return repo.Update(userToChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
50
PlexRequests.Helpers.Tests/PasswordHasherTests.cs
Normal file
50
PlexRequests.Helpers.Tests/PasswordHasherTests.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: AssemblyHelperTests.cs
|
||||||
|
// Created By: Jamie Rees
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// ************************************************************************/
|
||||||
|
#endregion
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace PlexRequests.Helpers.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class PasswordHasherTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestHash()
|
||||||
|
{
|
||||||
|
var password = "abcdef";
|
||||||
|
var salt = PasswordHasher.GenerateSalt();
|
||||||
|
var hash = PasswordHasher.ComputeHash(password, salt);
|
||||||
|
|
||||||
|
Assert.That(hash, Is.Not.EqualTo(password));
|
||||||
|
|
||||||
|
var match = PasswordHasher.VerifyPassword(password, salt, hash);
|
||||||
|
|
||||||
|
Assert.That(match, Is.True);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,6 +70,7 @@
|
||||||
</Otherwise>
|
</Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="PasswordHasherTests.cs" />
|
||||||
<Compile Include="HtmlRemoverTests.cs" />
|
<Compile Include="HtmlRemoverTests.cs" />
|
||||||
<Compile Include="AssemblyHelperTests.cs" />
|
<Compile Include="AssemblyHelperTests.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
|
69
PlexRequests.Helpers/PasswordHasher.cs
Normal file
69
PlexRequests.Helpers/PasswordHasher.cs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: PasswordHasher.cs
|
||||||
|
// Created By: Jamie Rees
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// ************************************************************************/
|
||||||
|
#endregion
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
namespace PlexRequests.Helpers
|
||||||
|
{
|
||||||
|
|
||||||
|
public static class PasswordHasher
|
||||||
|
{
|
||||||
|
// 24 = 192 bits
|
||||||
|
private const int SaltByteSize = 24;
|
||||||
|
private const int HashByteSize = 24;
|
||||||
|
private const int HasingIterationsCount = 10101;
|
||||||
|
|
||||||
|
public static byte[] ComputeHash(string password, byte[] salt, int iterations = HasingIterationsCount, int hashByteSize = HashByteSize)
|
||||||
|
{
|
||||||
|
var hashGenerator = new Rfc2898DeriveBytes(password, salt) { IterationCount = iterations };
|
||||||
|
return hashGenerator.GetBytes(hashByteSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] GenerateSalt(int saltByteSize = SaltByteSize)
|
||||||
|
{
|
||||||
|
var saltGenerator = new RNGCryptoServiceProvider();
|
||||||
|
var salt = new byte[saltByteSize];
|
||||||
|
saltGenerator.GetBytes(salt);
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool VerifyPassword(string password, byte[] passwordSalt, byte[] passwordHash)
|
||||||
|
{
|
||||||
|
var computedHash = ComputeHash(password, passwordSalt);
|
||||||
|
return AreHashesEqual(computedHash, passwordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Length constant verification - prevents timing attack
|
||||||
|
private static bool AreHashesEqual(IReadOnlyList<byte> firstHash, IReadOnlyList<byte> secondHash)
|
||||||
|
{
|
||||||
|
var minHashLength = firstHash.Count <= secondHash.Count ? firstHash.Count : secondHash.Count;
|
||||||
|
var xor = firstHash.Count ^ secondHash.Count;
|
||||||
|
for (var i = 0; i < minHashLength; i++)
|
||||||
|
xor |= firstHash[i] ^ secondHash[i];
|
||||||
|
return 0 == xor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,6 +53,7 @@
|
||||||
<Compile Include="LoggingHelper.cs" />
|
<Compile Include="LoggingHelper.cs" />
|
||||||
<Compile Include="MemoryCacheProvider.cs" />
|
<Compile Include="MemoryCacheProvider.cs" />
|
||||||
<Compile Include="ObjectCopier.cs" />
|
<Compile Include="ObjectCopier.cs" />
|
||||||
|
<Compile Include="PasswordHasher.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="SerializerSettings.cs" />
|
<Compile Include="SerializerSettings.cs" />
|
||||||
<Compile Include="StringCipher.cs" />
|
<Compile Include="StringCipher.cs" />
|
||||||
|
|
|
@ -62,12 +62,9 @@ namespace PlexRequests.Services.Notification
|
||||||
|
|
||||||
public static void Subscribe(INotification notification)
|
public static void Subscribe(INotification notification)
|
||||||
{
|
{
|
||||||
Log.Trace("Subscribing Observer {0}", notification.NotificationName);
|
|
||||||
INotification notificationValue;
|
INotification notificationValue;
|
||||||
if (Observers.TryGetValue(notification.NotificationName, out notificationValue))
|
if (Observers.TryGetValue(notification.NotificationName, out notificationValue))
|
||||||
{
|
{
|
||||||
Log.Trace("Observer {0} already exists", notification.NotificationName);
|
|
||||||
// Observer already exists
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,8 @@
|
||||||
<Compile Include="Repository\RequestJsonRepository.cs" />
|
<Compile Include="Repository\RequestJsonRepository.cs" />
|
||||||
<Compile Include="GenericRepository.cs" />
|
<Compile Include="GenericRepository.cs" />
|
||||||
<Compile Include="RequestedModel.cs" />
|
<Compile Include="RequestedModel.cs" />
|
||||||
|
<Compile Include="UserEntity.cs" />
|
||||||
|
<Compile Include="UsersModel.cs" />
|
||||||
<Compile Include="UserRepository.cs" />
|
<Compile Include="UserRepository.cs" />
|
||||||
<Compile Include="Sql.Designer.cs">
|
<Compile Include="Sql.Designer.cs">
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
|
@ -77,7 +79,6 @@
|
||||||
<DependentUpon>Sql.resx</DependentUpon>
|
<DependentUpon>Sql.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="TableCreation.cs" />
|
<Compile Include="TableCreation.cs" />
|
||||||
<Compile Include="UserModel.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="sqlite3.dll">
|
<None Include="sqlite3.dll">
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
--Any DB changes need to be made in this file.
|
--Any DB changes need to be made in this file.
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS User
|
CREATE TABLE IF NOT EXISTS Users
|
||||||
(
|
(
|
||||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
User varchar(50) NOT NULL ,
|
UserGuid varchar(50) NOT NULL ,
|
||||||
UserName varchar(50) NOT NULL,
|
UserName varchar(50) NOT NULL,
|
||||||
Password varchar(100) NOT NULL
|
Salt BLOB NOT NULL,
|
||||||
|
Hash BLOB NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#region Copyright
|
#region Copyright
|
||||||
// /************************************************************************
|
// /************************************************************************
|
||||||
// Copyright (c) 2016 Jamie Rees
|
// Copyright (c) 2016 Jamie Rees
|
||||||
// File: UserModel.cs
|
// File: UserEntity.cs
|
||||||
// Created By: Jamie Rees
|
// Created By: Jamie Rees
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
@ -28,11 +28,12 @@ using Dapper.Contrib.Extensions;
|
||||||
|
|
||||||
namespace PlexRequests.Store
|
namespace PlexRequests.Store
|
||||||
{
|
{
|
||||||
[Table("User")]
|
public class UserEntity
|
||||||
public class UserModel : Entity
|
|
||||||
{
|
{
|
||||||
public string User { get; set; }
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
public string UserName { get; set; }
|
public string UserName { get; set; }
|
||||||
public string Password { get; set; }
|
public string UserGuid { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,14 +32,14 @@ using Dapper.Contrib.Extensions;
|
||||||
|
|
||||||
namespace PlexRequests.Store
|
namespace PlexRequests.Store
|
||||||
{
|
{
|
||||||
public class UserRepository<T> : IRepository<T> where T : UserModel
|
public class UserRepository<T> : IRepository<T> where T : UserEntity
|
||||||
{
|
{
|
||||||
public UserRepository(ISqliteConfiguration config)
|
public UserRepository(ISqliteConfiguration config)
|
||||||
{
|
{
|
||||||
Config = config;
|
Config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ISqliteConfiguration Config { get; set; }
|
private ISqliteConfiguration Config { get; }
|
||||||
public long Insert(T entity)
|
public long Insert(T entity)
|
||||||
{
|
{
|
||||||
using (var cnn = Config.DbConnection())
|
using (var cnn = Config.DbConnection())
|
||||||
|
@ -65,7 +65,7 @@ namespace PlexRequests.Store
|
||||||
{
|
{
|
||||||
db.Open();
|
db.Open();
|
||||||
var result = db.GetAll<T>();
|
var result = db.GetAll<T>();
|
||||||
var selected = result.FirstOrDefault(x => x.User == id);
|
var selected = result.FirstOrDefault(x => x.UserGuid == id);
|
||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
37
PlexRequests.Store/UsersModel.cs
Normal file
37
PlexRequests.Store/UsersModel.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: UserModel.cs
|
||||||
|
// Created By: Jamie Rees
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// ************************************************************************/
|
||||||
|
#endregion
|
||||||
|
using Dapper.Contrib.Extensions;
|
||||||
|
|
||||||
|
namespace PlexRequests.Store
|
||||||
|
{
|
||||||
|
[Table("Users")]
|
||||||
|
public class UsersModel : UserEntity
|
||||||
|
{
|
||||||
|
public byte[] Hash { get; set; }
|
||||||
|
public byte[] Salt { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -198,7 +198,11 @@ $(document).on("click", ".approve", function (e) {
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
|
|
||||||
if (checkJsonResponse(response)) {
|
if (checkJsonResponse(response)) {
|
||||||
generateNotify("Success! Request Approved.", "success");
|
if (response.message) {
|
||||||
|
generateNotify(response.message, "success");
|
||||||
|
} else {
|
||||||
|
generateNotify("Success! Request Approved.", "success");
|
||||||
|
}
|
||||||
|
|
||||||
$("button[custom-button='" + buttonId + "']").remove();
|
$("button[custom-button='" + buttonId + "']").remove();
|
||||||
$("#" + buttonId + "notapproved").prop("class", "fa fa-check");
|
$("#" + buttonId + "notapproved").prop("class", "fa fa-check");
|
||||||
|
@ -227,7 +231,7 @@ $(document).on("click", ".clear", function (e) {
|
||||||
|
|
||||||
if (checkJsonResponse(response)) {
|
if (checkJsonResponse(response)) {
|
||||||
generateNotify("Success! Issues Cleared.", "info");
|
generateNotify("Success! Issues Cleared.", "info");
|
||||||
$('#issueArea'+buttonId).html("<div>Issue: None</div>");
|
$('#issueArea' + buttonId).html("<div>Issue: None</div>");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (e) {
|
error: function (e) {
|
||||||
|
@ -258,7 +262,7 @@ $(document).on("click", ".change", function (e) {
|
||||||
if (checkJsonResponse(response)) {
|
if (checkJsonResponse(response)) {
|
||||||
generateNotify("Success! Availibility changed.", "info");
|
generateNotify("Success! Availibility changed.", "info");
|
||||||
var button = $("button[custom-availibility='" + buttonId + "']");
|
var button = $("button[custom-availibility='" + buttonId + "']");
|
||||||
var icon = $('#availableIcon'+buttonId);
|
var icon = $('#availableIcon' + buttonId);
|
||||||
|
|
||||||
if (response.available) {
|
if (response.available) {
|
||||||
button.text("Mark Unavailable");
|
button.text("Mark Unavailable");
|
||||||
|
@ -333,7 +337,8 @@ function buildRequestContext(result, type) {
|
||||||
issues: result.issues,
|
issues: result.issues,
|
||||||
otherMessage: result.otherMessage,
|
otherMessage: result.otherMessage,
|
||||||
requestId: result.id,
|
requestId: result.id,
|
||||||
adminNote: result.adminNotes
|
adminNote: result.adminNotes,
|
||||||
|
imdb: result.imdbId
|
||||||
};
|
};
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
|
@ -139,7 +139,8 @@ function buildMovieContext(result) {
|
||||||
voteCount: result.voteCount,
|
voteCount: result.voteCount,
|
||||||
voteAverage: result.voteAverage,
|
voteAverage: result.voteAverage,
|
||||||
year: year,
|
year: year,
|
||||||
type: "movie"
|
type: "movie",
|
||||||
|
imdb: result.imdbId
|
||||||
};
|
};
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
@ -154,7 +155,8 @@ function buildTvShowContext(result) {
|
||||||
title: result.seriesName,
|
title: result.seriesName,
|
||||||
overview: result.overview,
|
overview: result.overview,
|
||||||
year: year,
|
year: year,
|
||||||
type: "tv"
|
type: "tv",
|
||||||
|
imdb: result.imdbId
|
||||||
};
|
};
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
using System.Dynamic;
|
using System.Dynamic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
using MarkdownSharp;
|
||||||
|
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using Nancy.Extensions;
|
using Nancy.Extensions;
|
||||||
using Nancy.ModelBinding;
|
using Nancy.ModelBinding;
|
||||||
|
@ -357,7 +359,14 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
var result = EmailService.SaveSettings(settings);
|
var result = EmailService.SaveSettings(settings);
|
||||||
|
|
||||||
NotificationService.Subscribe(new EmailMessageNotification(EmailService));
|
if (settings.Enabled)
|
||||||
|
{
|
||||||
|
NotificationService.Subscribe(new EmailMessageNotification(EmailService));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NotificationService.UnSubscribe(new EmailMessageNotification(EmailService));
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info("Saved email settings, result: {0}", result);
|
Log.Info("Saved email settings, result: {0}", result);
|
||||||
return Response.AsJson(result
|
return Response.AsJson(result
|
||||||
|
@ -369,6 +378,8 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
var checker = new StatusChecker();
|
var checker = new StatusChecker();
|
||||||
var status = checker.GetStatus();
|
var status = checker.GetStatus();
|
||||||
|
var md = new Markdown();
|
||||||
|
status.ReleaseNotes = md.Transform(status.ReleaseNotes);
|
||||||
return View["Status", status];
|
return View["Status", status];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,8 +400,14 @@ namespace PlexRequests.UI.Modules
|
||||||
Log.Trace(settings.DumpJson());
|
Log.Trace(settings.DumpJson());
|
||||||
|
|
||||||
var result = PushbulletService.SaveSettings(settings);
|
var result = PushbulletService.SaveSettings(settings);
|
||||||
|
if (settings.Enabled)
|
||||||
NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
|
{
|
||||||
|
NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info("Saved email settings, result: {0}", result);
|
Log.Info("Saved email settings, result: {0}", result);
|
||||||
return Response.AsJson(result
|
return Response.AsJson(result
|
||||||
|
|
|
@ -121,12 +121,12 @@ namespace PlexRequests.UI.Modules
|
||||||
Log.Info("Sent successfully, Approving request now.");
|
Log.Info("Sent successfully, Approving request now.");
|
||||||
request.Approved = true;
|
request.Approved = true;
|
||||||
var requestResult = Service.UpdateRequest(request);
|
var requestResult = Service.UpdateRequest(request);
|
||||||
Log.Trace("Approval result: {0}",requestResult);
|
Log.Trace("Approval result: {0}", requestResult);
|
||||||
if (requestResult)
|
if (requestResult)
|
||||||
{
|
{
|
||||||
return Response.AsJson(new JsonResponseModel { Result = true });
|
return Response.AsJson(new JsonResponseModel { Result = true });
|
||||||
}
|
}
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Updated Sonarr but could not approve it in PlexRequests :("});
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Updated Sonarr but could not approve it in PlexRequests :(" });
|
||||||
}
|
}
|
||||||
return Response.AsJson(new JsonResponseModel
|
return Response.AsJson(new JsonResponseModel
|
||||||
{
|
{
|
||||||
|
@ -177,6 +177,21 @@ namespace PlexRequests.UI.Modules
|
||||||
var cpSettings = CpService.GetSettings();
|
var cpSettings = CpService.GetSettings();
|
||||||
var cp = new CouchPotatoApi();
|
var cp = new CouchPotatoApi();
|
||||||
Log.Info("Adding movie to CP : {0}", request.Title);
|
Log.Info("Adding movie to CP : {0}", request.Title);
|
||||||
|
if (!cpSettings.Enabled)
|
||||||
|
{
|
||||||
|
// Approve it
|
||||||
|
request.Approved = true;
|
||||||
|
|
||||||
|
// Update the record
|
||||||
|
var inserted = Service.UpdateRequest(request);
|
||||||
|
return Response.AsJson(inserted
|
||||||
|
? new JsonResponseModel { Result = true, Message = "This has been approved, but It has not been sent to CouchPotato because it has not been configured." }
|
||||||
|
: new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = false,
|
||||||
|
Message = "We could not approve this request. Please try again or check the logs."
|
||||||
|
});
|
||||||
|
}
|
||||||
var result = cp.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, cpSettings.ProfileId);
|
var result = cp.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, cpSettings.ProfileId);
|
||||||
Log.Trace("Adding movie to CP result {0}", result);
|
Log.Trace("Adding movie to CP result {0}", result);
|
||||||
if (result)
|
if (result)
|
||||||
|
@ -188,7 +203,7 @@ namespace PlexRequests.UI.Modules
|
||||||
var inserted = Service.UpdateRequest(request);
|
var inserted = Service.UpdateRequest(request);
|
||||||
|
|
||||||
return Response.AsJson(inserted
|
return Response.AsJson(inserted
|
||||||
? new JsonResponseModel {Result = true}
|
? new JsonResponseModel { Result = true }
|
||||||
: new JsonResponseModel
|
: new JsonResponseModel
|
||||||
{
|
{
|
||||||
Result = false,
|
Result = false,
|
||||||
|
@ -239,7 +254,7 @@ namespace PlexRequests.UI.Modules
|
||||||
}
|
}
|
||||||
if (r.Type == RequestType.TvShow)
|
if (r.Type == RequestType.TvShow)
|
||||||
{
|
{
|
||||||
var sender = new TvSender(SonarrApi,SickRageApi);
|
var sender = new TvSender(SonarrApi, SickRageApi);
|
||||||
var sr = SickRageSettings.GetSettings();
|
var sr = SickRageSettings.GetSettings();
|
||||||
var sonarr = SonarrSettings.GetSettings();
|
var sonarr = SonarrSettings.GetSettings();
|
||||||
if (sr.Enabled)
|
if (sr.Enabled)
|
||||||
|
|
|
@ -30,6 +30,8 @@ using System.Dynamic;
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using Nancy.Authentication.Forms;
|
using Nancy.Authentication.Forms;
|
||||||
using Nancy.Extensions;
|
using Nancy.Extensions;
|
||||||
|
using Nancy.Responses.Negotiation;
|
||||||
|
using Nancy.Security;
|
||||||
|
|
||||||
using PlexRequests.Core;
|
using PlexRequests.Core;
|
||||||
using PlexRequests.UI.Models;
|
using PlexRequests.UI.Models;
|
||||||
|
@ -81,7 +83,6 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
return View["Login/Register", model];
|
return View["Login/Register", model];
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Post["/register"] = x =>
|
Post["/register"] = x =>
|
||||||
|
@ -96,6 +97,30 @@ namespace PlexRequests.UI.Modules
|
||||||
Session[SessionKeys.UsernameKey] = username;
|
Session[SessionKeys.UsernameKey] = username;
|
||||||
return this.LoginAndRedirect((Guid)userId);
|
return this.LoginAndRedirect((Guid)userId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Get["/changepassword"] = _ => ChangePassword();
|
||||||
|
Post["/changepassword"] = _ => ChangePasswordPost();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Negotiator ChangePassword()
|
||||||
|
{
|
||||||
|
this.RequiresAuthentication();
|
||||||
|
return View["ChangePassword"];
|
||||||
|
}
|
||||||
|
|
||||||
|
private Negotiator ChangePasswordPost()
|
||||||
|
{
|
||||||
|
var username = Context.CurrentUser.UserName;
|
||||||
|
var oldPass = Request.Form.OldPassword;
|
||||||
|
var newPassword = Request.Form.NewPassword;
|
||||||
|
var newPasswordAgain = Request.Form.NewPasswordAgain;
|
||||||
|
if (!newPassword.Equals(newPasswordAgain))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = UserMapper.UpdateUser(username, oldPass, newPassword);
|
||||||
|
return View["ChangePassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -39,6 +39,7 @@ using PlexRequests.Api.Interfaces;
|
||||||
using PlexRequests.Core;
|
using PlexRequests.Core;
|
||||||
using PlexRequests.Core.SettingModels;
|
using PlexRequests.Core.SettingModels;
|
||||||
using PlexRequests.Helpers;
|
using PlexRequests.Helpers;
|
||||||
|
using PlexRequests.Helpers.Exceptions;
|
||||||
using PlexRequests.Services.Interfaces;
|
using PlexRequests.Services.Interfaces;
|
||||||
using PlexRequests.Services.Notification;
|
using PlexRequests.Services.Notification;
|
||||||
using PlexRequests.Store;
|
using PlexRequests.Store;
|
||||||
|
@ -176,27 +177,24 @@ namespace PlexRequests.UI.Modules
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Debug("movie with id {0} doesnt exists", movieId);
|
Log.Debug("movie with id {0} doesnt exists", movieId);
|
||||||
var cpSettings = CpService.GetSettings();
|
|
||||||
if (cpSettings.ApiKey == null)
|
|
||||||
{
|
|
||||||
Log.Warn("CP apiKey is null");
|
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = "CouchPotato is not yet configured, If you are the Admin, please log in." });
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Trace("Settings: ");
|
|
||||||
Log.Trace(cpSettings.DumpJson);
|
|
||||||
|
|
||||||
var movieApi = new TheMovieDbApi();
|
var movieApi = new TheMovieDbApi();
|
||||||
var movieInfo = movieApi.GetMovieInformation(movieId).Result;
|
var movieInfo = movieApi.GetMovieInformation(movieId).Result;
|
||||||
Log.Trace("Getting movie info from TheMovieDb");
|
Log.Trace("Getting movie info from TheMovieDb");
|
||||||
Log.Trace(movieInfo.DumpJson);
|
Log.Trace(movieInfo.DumpJson);
|
||||||
|
//#if !DEBUG
|
||||||
//#if !DEBUG
|
try
|
||||||
if (CheckIfTitleExistsInPlex(movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString()))
|
|
||||||
{
|
{
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{movieInfo.Title} is already in Plex!" });
|
if (CheckIfTitleExistsInPlex(movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString()))
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{movieInfo.Title} is already in Plex!" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//#endif
|
catch (ApplicationSettingsException)
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {movieInfo.Title} is in Plex, are you sure it's correctly setup?" });
|
||||||
|
}
|
||||||
|
//#endif
|
||||||
|
|
||||||
var model = new RequestedModel
|
var model = new RequestedModel
|
||||||
{
|
{
|
||||||
|
@ -219,6 +217,11 @@ namespace PlexRequests.UI.Modules
|
||||||
Log.Trace(settings.DumpJson());
|
Log.Trace(settings.DumpJson());
|
||||||
if (!settings.RequireApproval)
|
if (!settings.RequireApproval)
|
||||||
{
|
{
|
||||||
|
var cpSettings = CpService.GetSettings();
|
||||||
|
|
||||||
|
Log.Trace("Settings: ");
|
||||||
|
Log.Trace(cpSettings.DumpJson);
|
||||||
|
|
||||||
Log.Info("Adding movie to CP (No approval required)");
|
Log.Info("Adding movie to CP (No approval required)");
|
||||||
var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri, cpSettings.ProfileId);
|
var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri, cpSettings.ProfileId);
|
||||||
Log.Debug("Adding movie to CP result {0}", result);
|
Log.Debug("Adding movie to CP result {0}", result);
|
||||||
|
@ -266,13 +269,19 @@ namespace PlexRequests.UI.Modules
|
||||||
var tvApi = new TvMazeApi();
|
var tvApi = new TvMazeApi();
|
||||||
|
|
||||||
var showInfo = tvApi.ShowLookupByTheTvDbId(showId);
|
var showInfo = tvApi.ShowLookupByTheTvDbId(showId);
|
||||||
|
//#if !DEBUG
|
||||||
//#if !DEBUG
|
try
|
||||||
if (CheckIfTitleExistsInPlex(showInfo.name, showInfo.premiered?.Substring(0, 4))) // Take only the year Format = 2014-01-01
|
|
||||||
{
|
{
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{showInfo.name} is already in Plex!" });
|
if (CheckIfTitleExistsInPlex(showInfo.name, showInfo.premiered?.Substring(0, 4))) // Take only the year Format = 2014-01-01
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{showInfo.name} is already in Plex!" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//#endif
|
catch (ApplicationSettingsException)
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {showInfo.name} is in Plex, are you sure it's correctly setup?" });
|
||||||
|
}
|
||||||
|
//#endif
|
||||||
|
|
||||||
DateTime firstAir;
|
DateTime firstAir;
|
||||||
DateTime.TryParse(showInfo.premiered, out firstAir);
|
DateTime.TryParse(showInfo.premiered, out firstAir);
|
||||||
|
@ -290,7 +299,8 @@ namespace PlexRequests.UI.Modules
|
||||||
Approved = false,
|
Approved = false,
|
||||||
RequestedBy = Session[SessionKeys.UsernameKey].ToString(),
|
RequestedBy = Session[SessionKeys.UsernameKey].ToString(),
|
||||||
Issues = IssueState.None,
|
Issues = IssueState.None,
|
||||||
LatestTv = latest
|
LatestTv = latest,
|
||||||
|
ImdbId = showInfo.externals?.imdb ?? string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,10 @@
|
||||||
<HintPath>..\packages\Humanizer.Core.2.0.1\lib\dotnet\Humanizer.dll</HintPath>
|
<HintPath>..\packages\Humanizer.Core.2.0.1\lib\dotnet\Humanizer.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="MarkdownSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\MarkdownSharp.1.13.0.0\lib\35\MarkdownSharp.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
|
@ -328,6 +332,9 @@
|
||||||
<Content Include="Views\Admin\Sickrage.cshtml">
|
<Content Include="Views\Admin\Sickrage.cshtml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Views\Login\ChangePassword.cshtml">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<None Include="Web.Debug.config">
|
<None Include="Web.Debug.config">
|
||||||
<DependentUpon>web.config</DependentUpon>
|
<DependentUpon>web.config</DependentUpon>
|
||||||
</None>
|
</None>
|
||||||
|
|
|
@ -14,7 +14,20 @@
|
||||||
<form class="form-horizontal" method="POST" id="mainForm">
|
<form class="form-horizontal" method="POST" id="mainForm">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>CouchPotato Settings</legend>
|
<legend>CouchPotato Settings</legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
@if (Model.Enabled)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="Enabled" name="Enabled" checked="checked"><text>Enabled</text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="Enabled" name="Enabled"><text>Enabled</text>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="Ip" class="control-label">CouchPotato Hostname or IP</label>
|
<label for="Ip" class="control-label">CouchPotato Hostname or IP</label>
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<label class="control-label">Update Available: </label>
|
<label class="control-label">Update Available: </label>
|
||||||
@if (Model.UpdateAvailable)
|
@if (Model.UpdateAvailable)
|
||||||
{
|
{
|
||||||
<label class="control-label"><a href="@Model.UpdateUri" target="_blank"><i class="fa fa-check"></i> Click Here!</a></label>
|
<label class="control-label"><a href="@Model.UpdateUri" target="_blank"><i class="fa fa-check"></i></a></label>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -23,6 +23,16 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (Model.UpdateAvailable)
|
||||||
|
{
|
||||||
|
<h2>
|
||||||
|
<a href="@Model.DownloadUri">@Model.ReleaseTitle</a>
|
||||||
|
</h2>
|
||||||
|
<hr/>
|
||||||
|
<label>Release Notes:</label>
|
||||||
|
@Html.Raw(Model.ReleaseNotes)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
9
PlexRequests.UI/Views/Login/ChangePassword.cshtml
Normal file
9
PlexRequests.UI/Views/Login/ChangePassword.cshtml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<form method="POST">
|
||||||
|
<br />
|
||||||
|
Old Password <input class="form-control" name="OldPassword" type="password" />
|
||||||
|
New Password <input class="form-control" name="NewPassword" type="password" />
|
||||||
|
New Password again <input class="form-control" name="NewPasswordAgain" type="password" />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<input class="btn btn-success-outline" type="submit" value="Change Password" />
|
||||||
|
</form>
|
|
@ -94,7 +94,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-5 ">
|
<div class="col-sm-5 ">
|
||||||
<div>
|
<div>
|
||||||
<a href="https://www.themoviedb.org/{{type}}/{{id}}">
|
<a href="http://www.imdb.com/title/{{imdb}}/" targe="_blank">
|
||||||
<h4 class="request-title">{{title}} ({{year}})</h4>
|
<h4 class="request-title">{{title}} ({{year}})</h4>
|
||||||
</a>
|
</a>
|
||||||
<span class="label label-success">{{status}}</span>
|
<span class="label label-success">{{status}}</span>
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-5 ">
|
<div class="col-sm-5 ">
|
||||||
<div>
|
<div>
|
||||||
<a href="https://www.themoviedb.org/{{type}}/{{id}}">
|
<a href="http://www.imdb.com/title/{{imdb}}/" targe="_blank">
|
||||||
<h4>{{title}} ({{year}})</h4>
|
<h4>{{title}} ({{year}})</h4>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<package id="FluentScheduler" version="3.1.46" targetFramework="net46" />
|
<package id="FluentScheduler" version="3.1.46" targetFramework="net46" />
|
||||||
<package id="FluentValidation" version="6.2.1.0" targetFramework="net46" />
|
<package id="FluentValidation" version="6.2.1.0" targetFramework="net46" />
|
||||||
<package id="Humanizer.Core" version="2.0.1" targetFramework="net452" />
|
<package id="Humanizer.Core" version="2.0.1" targetFramework="net452" />
|
||||||
|
<package id="MarkdownSharp" version="1.13.0.0" targetFramework="net46" />
|
||||||
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net452" />
|
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net452" />
|
||||||
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
|
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
|
||||||
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />
|
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue