Lots and Lots of work

pull/1425/head
Jamie.Rees 8 years ago
parent 539419750b
commit 596008b06c

@ -20,7 +20,8 @@ namespace Ombi.Api.TvMaze
var request = new Request("search/shows", Uri, HttpMethod.Get); var request = new Request("search/shows", Uri, HttpMethod.Get);
request.AddQueryString("q", searchTerm); request.AddQueryString("q", searchTerm);
request.AddHeader("Content-Type", "application/json"); //request.ContentHeaders.Add("Content-Type", "application/json");
request.ContentHeaders.Add(new KeyValuePair<string, string>("Content-Type","application/json"));
return await Api.Request<List<TvMazeSearch>>(request); return await Api.Request<List<TvMazeSearch>>(request);
} }

@ -82,7 +82,7 @@ namespace Ombi.Api
{ {
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return; if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return;
var builder = new UriBuilder(_modified); var builder = new UriBuilder(FullUri);
var startingTag = string.Empty; var startingTag = string.Empty;
var hasQuery = false; var hasQuery = false;
if (string.IsNullOrEmpty(builder.Query)) if (string.IsNullOrEmpty(builder.Query))

@ -31,6 +31,6 @@ namespace Ombi.Core.Claims
public const string Admin = nameof(Admin); public const string Admin = nameof(Admin);
public const string AutoApproveMovie = nameof(AutoApproveMovie); public const string AutoApproveMovie = nameof(AutoApproveMovie);
public const string AutoApproveTv = nameof(AutoApproveTv); public const string AutoApproveTv = nameof(AutoApproveTv);
public const string PowerUser = nameof(PowerUser);
} }
} }

@ -8,7 +8,7 @@ namespace Ombi.Core
{ {
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies(); Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies();
Task<IEnumerable<SearchMovieViewModel>> PopularMovies(); Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
Task<IEnumerable<SearchMovieViewModel>> ProcessMovieSearch(string search); Task<IEnumerable<SearchMovieViewModel>> Search(string search);
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies(); Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies(); Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
Task<IEnumerable<SearchMovieViewModel>> LookupImdbInformation(IEnumerable<SearchMovieViewModel> movies); Task<IEnumerable<SearchMovieViewModel>> LookupImdbInformation(IEnumerable<SearchMovieViewModel> movies);

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Search;
namespace Ombi.Core.Engine
{
public interface ITvSearchEngine
{
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
}
}

@ -3,6 +3,7 @@ using System.Linq;
using System.Security.Principal; using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using AutoMapper; using AutoMapper;
using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.IdentityResolver; using Ombi.Core.IdentityResolver;
@ -18,19 +19,22 @@ namespace Ombi.Core.Engine
public class MovieSearchEngine : BaseMediaEngine, IMovieEngine public class MovieSearchEngine : BaseMediaEngine, IMovieEngine
{ {
public MovieSearchEngine(IPrincipal identity, IRequestService service, IMovieDbApi movApi, IMapper mapper, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings) public MovieSearchEngine(IPrincipal identity, IRequestService service, IMovieDbApi movApi, IMapper mapper, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings,
ILogger<MovieSearchEngine> logger)
: base(identity, service) : base(identity, service)
{ {
MovieApi = movApi; MovieApi = movApi;
Mapper = mapper; Mapper = mapper;
PlexSettings = plexSettings; PlexSettings = plexSettings;
EmbySettings = embySettings; EmbySettings = embySettings;
Logger = logger;
} }
private IMovieDbApi MovieApi { get; } private IMovieDbApi MovieApi { get; }
private IMapper Mapper { get; } private IMapper Mapper { get; }
private ISettingsService<PlexSettings> PlexSettings { get; } private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; } private ISettingsService<EmbySettings> EmbySettings { get; }
private ILogger<MovieSearchEngine> Logger { get; }
public async Task<IEnumerable<SearchMovieViewModel>> LookupImdbInformation(IEnumerable<SearchMovieViewModel> movies) public async Task<IEnumerable<SearchMovieViewModel>> LookupImdbInformation(IEnumerable<SearchMovieViewModel> movies)
{ {
@ -58,11 +62,12 @@ namespace Ombi.Core.Engine
return retVal; return retVal;
} }
public async Task<IEnumerable<SearchMovieViewModel>> ProcessMovieSearch(string search) public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
{ {
var result = await MovieApi.SearchMovie(search); var result = await MovieApi.SearchMovie(search);
if (result != null) if (result != null)
{ {
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result); return await TransformMovieResultsToResponse(result);
} }
return null; return null;
@ -72,6 +77,7 @@ namespace Ombi.Core.Engine
var result = await MovieApi.PopularMovies(); var result = await MovieApi.PopularMovies();
if (result != null) if (result != null)
{ {
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result); return await TransformMovieResultsToResponse(result);
} }
return null; return null;
@ -82,6 +88,7 @@ namespace Ombi.Core.Engine
var result = await MovieApi.TopRated(); var result = await MovieApi.TopRated();
if (result != null) if (result != null)
{ {
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result); return await TransformMovieResultsToResponse(result);
} }
return null; return null;
@ -92,6 +99,7 @@ namespace Ombi.Core.Engine
var result = await MovieApi.Upcoming(); var result = await MovieApi.Upcoming();
if (result != null) if (result != null)
{ {
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result); return await TransformMovieResultsToResponse(result);
} }
return null; return null;
@ -101,6 +109,8 @@ namespace Ombi.Core.Engine
var result = await MovieApi.NowPlaying(); var result = await MovieApi.NowPlaying();
if (result != null) if (result != null)
{ {
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result); return await TransformMovieResultsToResponse(result);
} }
return null; return null;
@ -125,7 +135,7 @@ namespace Ombi.Core.Engine
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie,
Dictionary<int, RequestModel> existingRequests, PlexSettings plexSettings, EmbySettings embySettings) Dictionary<int, RequestModel> existingRequests, PlexSettings plexSettings, EmbySettings embySettings)
{ {
if (plexSettings.Enable) if (plexSettings.Enable)
{ {
// var content = PlexContentRepository.GetAll(); // var content = PlexContentRepository.GetAll();

@ -1,13 +1,20 @@
using System.Security.Principal; using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using AutoMapper; using AutoMapper;
using Ombi.Api.TvMaze; using Ombi.Api.TvMaze;
using Ombi.Api.TvMaze.Models;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Requests.Models; using Ombi.Core.Requests.Models;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External; using Ombi.Core.Settings.Models.External;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
{ {
public class TvSearchEngine : BaseMediaEngine public class TvSearchEngine : BaseMediaEngine, ITvSearchEngine
{ {
public TvSearchEngine(IPrincipal identity, IRequestService service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings) public TvSearchEngine(IPrincipal identity, IRequestService service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings)
@ -24,6 +31,74 @@ namespace Ombi.Core.Engine
private ISettingsService<PlexSettings> PlexSettings { get; } private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; } private ISettingsService<EmbySettings> EmbySettings { get; }
public async Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm)
{
var searchResult = await TvMazeApi.Search(searchTerm);
if (searchResult != null)
{
return await ProcessResults(searchResult);
}
return null;
}
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults(IEnumerable<TvMazeSearch> items)
{
var existingRequests = await GetRequests(RequestType.TvShow);
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items)
{
retVal.Add(ProcessResult(tvMazeSearch, existingRequests, plexSettings, embySettings));
}
return retVal;
}
private SearchTvShowViewModel ProcessResult(TvMazeSearch item, Dictionary<int, RequestModel> existingRequests, PlexSettings plexSettings, EmbySettings embySettings)
{
var viewT = Mapper.Map<SearchTvShowViewModel>(item);
if (embySettings.Enable)
{
//var embyShow = EmbyChecker.GetTvShow(embyCached.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId);
//if (embyShow != null)
//{
// viewT.Available = true;
//}
}
if (plexSettings.Enable)
{
//var plexShow = PlexChecker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4),
// providerId);
//if (plexShow != null)
//{
// viewT.Available = true;
// viewT.PlexUrl = plexShow.Url;
//}
}
if (item.show?.externals?.thetvdb != null && !viewT.Available)
{
var tvdbid = (int)item.show.externals.thetvdb;
if (existingRequests.ContainsKey(tvdbid))
{
var dbt = existingRequests[tvdbid];
viewT.Requested = true;
viewT.Episodes = dbt.Episodes.ToList();
viewT.Approved = dbt.Approved;
}
//if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid))
// // compare to the sonarr/sickrage db
//{
// viewT.Requested = true;
//}
}
return viewT;
}
} }
} }

@ -6,9 +6,10 @@ namespace Ombi.Core.IdentityResolver
{ {
public interface IUserIdentityManager public interface IUserIdentityManager
{ {
Task CreateUser(UserDto user); Task<UserDto> CreateUser(UserDto user);
Task<bool> CredentialsValid(string username, string password); Task<bool> CredentialsValid(string username, string password);
Task<UserDto> GetUser(string username); Task<UserDto> GetUser(string username);
Task<IEnumerable<UserDto>> GetUsers(); Task<IEnumerable<UserDto>> GetUsers();
Task DeleteUser(UserDto user);
} }
} }

@ -68,13 +68,20 @@ namespace Ombi.Core.IdentityResolver
return Mapper.Map<List<UserDto>>(await UserRepository.GetUsers()); return Mapper.Map<List<UserDto>>(await UserRepository.GetUsers());
} }
public async Task CreateUser(UserDto userDto) public async Task<UserDto> CreateUser(UserDto userDto)
{ {
var user = Mapper.Map<User>(userDto); var user = Mapper.Map<User>(userDto);
var result = HashPassword(user.Password); var result = HashPassword(user.Password);
user.Password = result.HashedPass; user.Password = result.HashedPass;
user.Salt = result.Salt; user.Salt = result.Salt;
await UserRepository.CreateUser(user); await UserRepository.CreateUser(user);
return Mapper.Map<UserDto>(user);
}
public async Task DeleteUser(UserDto user)
{
await UserRepository.DeleteUser(Mapper.Map<User>(user));
} }
private UserHash HashPassword(string password) private UserHash HashPassword(string password)

@ -0,0 +1,47 @@
using System.Collections.Generic;
using Ombi.Core.Models.Requests;
namespace Ombi.Core.Models.Search
{
public class SearchTvShowViewModel : SearchViewModel
{
public SearchTvShowViewModel()
{
Episodes = new List<EpisodesModel>();
}
public int Id { get; set; }
public string SeriesName { get; set; }
public List<string> Aliases { get; set; }
public string Banner { get; set; }
public int SeriesId { get; set; }
public string Status { get; set; }
public string FirstAired { get; set; }
public string Network { get; set; }
public string NetworkId { get; set; }
public string Runtime { get; set; }
public List<string> Genre { get; set; }
public string Overview { get; set; }
public int LastUpdated { get; set; }
public string AirsDayOfWeek { get; set; }
public string AirsTime { get; set; }
public string Rating { get; set; }
public string ImdbId { get; set; }
public int SiteRating { get; set; }
public List<EpisodesModel> Episodes { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Trailer { get; set; }
/// <summary>
/// This is used from the Trakt API
/// </summary>
/// <value>
/// The trailer.
/// </value>
public string Homepage { get; set; }
}
}

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace Ombi.Core.Models.UI
{
public class UserViewModel
{
public string Id { get; set; }
public string Username { get; set; }
public string Alias { get; set; }
public List<string> Claims { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
public UserType UserType { get; set; }
}
}

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Principal;
namespace Ombi.Core.Models namespace Ombi.Core.Models
{ {
@ -13,6 +14,9 @@ namespace Ombi.Core.Models
public string Password { get; set; } public string Password { get; set; }
public byte[] Salt { get; set; } public byte[] Salt { get; set; }
public UserType UserType { get; set; } public UserType UserType { get; set; }
} }
public enum UserType public enum UserType
{ {
@ -20,4 +24,6 @@ namespace Ombi.Core.Models
PlexUser = 2, PlexUser = 2,
EmbyUser = 3, EmbyUser = 3,
} }
} }

@ -16,6 +16,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" /> <ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" /> <ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" /> <ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />

@ -46,6 +46,7 @@ namespace Ombi.DependencyInjection
{ {
services.AddTransient<IMovieEngine, MovieSearchEngine>(); services.AddTransient<IMovieEngine, MovieSearchEngine>();
services.AddTransient<IRequestEngine, RequestEngine>(); services.AddTransient<IRequestEngine, RequestEngine>();
services.AddTransient<ITvSearchEngine, TvSearchEngine>();
return services; return services;
} }

@ -0,0 +1,19 @@
using System.Text.RegularExpressions;
namespace Ombi.Helpers
{
public static class HtmlHelper
{
public static string RemoveHtml(this string value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
var step1 = Regex.Replace(value, @"<[^>]+>|&nbsp;", "").Trim();
var step2 = Regex.Replace(step1, @"\s{2,}", " ");
return step2;
}
}
}

@ -1,6 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using AutoMapper; using AutoMapper;
using Ombi.Core.Models; using Ombi.Core.Models;
using Ombi.Core.Models.UI;
using Ombi.Store.Entities; using Ombi.Store.Entities;
namespace Ombi.Mapping.Profiles namespace Ombi.Mapping.Profiles
@ -11,6 +15,14 @@ namespace Ombi.Mapping.Profiles
{ {
CreateMap<User, UserDto>().ReverseMap(); CreateMap<User, UserDto>().ReverseMap();
CreateMap<UserDto, UserViewModel>()
.ForMember(dest => dest.Claims, opts => opts.MapFrom(src => src.Claims.Select(x => x.Value).ToList())); // Map the claims to a List<string>
CreateMap<string, Claim>()
.ConstructUsing(str => new Claim(ClaimTypes.Role, str)); // This is used for the UserViewModel List<string> claims => UserDto List<claim>
CreateMap<UserViewModel, UserDto>();
CreateMap<string, DateTime>().ConvertUsing<StringToDateTimeConverter>(); CreateMap<string, DateTime>().ConvertUsing<StringToDateTimeConverter>();
} }
} }

@ -0,0 +1,28 @@
using System.Globalization;
using AutoMapper;
using Ombi.Api.TvMaze.Models;
using Ombi.Core.Models.Search;
using Ombi.Helpers;
namespace Ombi.Mapping.Profiles
{
public class TvProfile : Profile
{
public TvProfile()
{
CreateMap<TvMazeSearch, SearchTvShowViewModel>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.show.externals.thetvdb))
.ForMember(dest => dest.FirstAired, opts => opts.MapFrom(src => src.show.premiered))
.ForMember(dest => dest.ImdbId, opts => opts.MapFrom(src => src.show.externals.imdb))
.ForMember(dest => dest.Network, opts => opts.MapFrom(src => src.show.network.name))
.ForMember(dest => dest.NetworkId, opts => opts.MapFrom(src => src.show.network.id.ToString()))
.ForMember(dest => dest.Overview, opts => opts.MapFrom(src => src.show.summary.RemoveHtml()))
.ForMember(dest => dest.Rating, opts => opts.MapFrom(src => src.score.ToString(CultureInfo.CurrentUICulture)))
.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.show.runtime.ToString()))
.ForMember(dest => dest.SeriesId, opts => opts.MapFrom(src => src.show.id))
.ForMember(dest => dest.SeriesName, opts => opts.MapFrom(src => src.show.name))
.ForMember(dest => dest.Banner, opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.show.image.medium) ? src.show.image.medium.Replace("http","https") : string.Empty))
.ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.show.status));
}
}
}

@ -1,4 +1,6 @@
using System; using System;
using System.Linq;
using System.Reflection;
using Hangfire; using Hangfire;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -14,7 +16,8 @@ namespace Ombi.Schedule
public override object ActivateJob(Type type) public override object ActivateJob(Type type)
{ {
return _container.GetService(type); var i = type.GetTypeInfo().ImplementedInterfaces.FirstOrDefault();
return _container.GetService(i);
} }
} }
} }

@ -14,7 +14,7 @@ namespace Ombi.Schedule
private IPlexContentCacher Cacher { get; } private IPlexContentCacher Cacher { get; }
public void Setup() public void Setup()
{ {
RecurringJob.AddOrUpdate(() => Cacher.CacheContent(), Cron.Hourly, TimeZoneInfo.Utc, "cacher"); RecurringJob.AddOrUpdate(() => Cacher.CacheContent(), Cron.Minutely);
} }
} }
} }

@ -28,6 +28,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Logging;
using Ombi.Api.Plex; using Ombi.Api.Plex;
using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models;
using Ombi.Core.Settings; using Ombi.Core.Settings;
@ -37,14 +38,16 @@ namespace Ombi.Schedule.Jobs
{ {
public partial class PlexContentCacher : IPlexContentCacher public partial class PlexContentCacher : IPlexContentCacher
{ {
public PlexContentCacher(ISettingsService<PlexSettings> plex, IPlexApi plexApi) public PlexContentCacher(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentCacher> logger)
{ {
Plex = plex; Plex = plex;
PlexApi = plexApi; PlexApi = plexApi;
Logger = logger;
} }
private ISettingsService<PlexSettings> Plex { get; } private ISettingsService<PlexSettings> Plex { get; }
private IPlexApi PlexApi { get; } private IPlexApi PlexApi { get; }
private ILogger<PlexContentCacher> Logger { get; }
public void CacheContent() public void CacheContent()
{ {
@ -57,6 +60,8 @@ namespace Ombi.Schedule.Jobs
{ {
return; return;
} }
Logger.LogInformation("Starting Plex Content Cacher");
//TODO //TODO
//var libraries = CachedLibraries(plexSettings); //var libraries = CachedLibraries(plexSettings);

@ -9,6 +9,7 @@
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.12" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.6.12" />
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" /> <PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" /> <PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
<PackageReference Include="Serilog" Version="2.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -0,0 +1,8 @@
namespace Ombi.Settings.Settings.Models
{
public class CustomizationSettings : Settings
{
public string ApplicationName { get; set; }
public string Logo { get; set; }
}
}

@ -91,8 +91,8 @@ namespace Ombi.Settings.Settings
modified.Id = entity.Id; modified.Id = entity.Id;
var globalSettings = new GlobalSettings { SettingsName = EntityName, Content = JsonConvert.SerializeObject(modified, SerializerSettings.Settings), Id = entity.Id }; var globalSettings = new GlobalSettings { SettingsName = EntityName, Content = JsonConvert.SerializeObject(modified, SerializerSettings.Settings), Id = entity.Id };
globalSettings.Content = EncryptSettings(globalSettings); entity.Content = EncryptSettings(globalSettings);
await Repo.UpdateAsync(globalSettings).ConfigureAwait(false); await Repo.UpdateAsync(entity).ConfigureAwait(false);
return true; return true;
} }

@ -15,5 +15,6 @@ namespace Ombi.Store.Context
DbSet<GlobalSettings> Settings { get; set; } DbSet<GlobalSettings> Settings { get; set; }
DbSet<User> Users { get; set; } DbSet<User> Users { get; set; }
EntityEntry<GlobalSettings> Entry(GlobalSettings settings); EntityEntry<GlobalSettings> Entry(GlobalSettings settings);
EntityEntry<TEntity> Attach<TEntity>(TEntity entity) where TEntity : class;
} }
} }

@ -9,5 +9,6 @@ namespace Ombi.Store.Repository
Task CreateUser(User user); Task CreateUser(User user);
Task<User> GetUser(string username); Task<User> GetUser(string username);
Task<IEnumerable<User>> GetUsers(); Task<IEnumerable<User>> GetUsers();
Task DeleteUser(User user);
} }
} }

@ -76,7 +76,6 @@ namespace Ombi.Store.Repository
public void Update(GlobalSettings entity) public void Update(GlobalSettings entity)
{ {
Db.Entry(entity).State = EntityState.Modified;
Db.SaveChanges(); Db.SaveChanges();
} }
} }

@ -58,5 +58,11 @@ namespace Ombi.Store.Repository
{ {
return await Db.Users.ToListAsync(); return await Db.Users.ToListAsync();
} }
public async Task DeleteUser(User user)
{
Db.Users.Remove(user);
await Db.SaveChangesAsync();
}
} }
} }

@ -6,7 +6,7 @@ namespace Ombi.Attributes
{ {
public AdminAttribute() public AdminAttribute()
{ {
base.Roles = "Admin"; Roles = "Admin";
} }
} }

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Ombi.Core.Claims;
namespace Ombi.Attributes namespace Ombi.Attributes
{ {
@ -6,7 +7,12 @@ namespace Ombi.Attributes
{ {
public PowerUserAttribute() public PowerUserAttribute()
{ {
Roles = "Admin, PowerUser"; var roles = new []
{
OmbiClaims.Admin,
OmbiClaims.PowerUser
};
Roles = string.Join(",",roles);
} }
} }
} }

@ -30,8 +30,8 @@ namespace Ombi.Auth
/// <summary> /// <summary>
/// The expiration time for the generated tokens. /// The expiration time for the generated tokens.
/// </summary> /// </summary>
/// <remarks>The default is five minutes (300 seconds).</remarks> /// <remarks>The default is 7 Days.</remarks>
public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(5); public TimeSpan Expiration { get; set; } = TimeSpan.FromDays(7);
/// <summary> /// <summary>
/// The signing key to use when generating tokens. /// The signing key to use when generating tokens.

@ -2,12 +2,14 @@
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Claims; using Ombi.Core.Claims;
using Ombi.Core.IdentityResolver; using Ombi.Core.IdentityResolver;
using Ombi.Core.Models; using Ombi.Core.Models;
using Ombi.Core.Models.UI;
using Ombi.Models; using Ombi.Models;
namespace Ombi.Controllers namespace Ombi.Controllers
@ -15,12 +17,14 @@ namespace Ombi.Controllers
[PowerUser] [PowerUser]
public class IdentityController : BaseV1ApiController public class IdentityController : BaseV1ApiController
{ {
public IdentityController(IUserIdentityManager identity) public IdentityController(IUserIdentityManager identity, IMapper mapper)
{ {
IdentityManager = identity; IdentityManager = identity;
Mapper = mapper;
} }
private IUserIdentityManager IdentityManager { get; } private IUserIdentityManager IdentityManager { get; }
private IMapper Mapper { get; }
[HttpGet] [HttpGet]
public async Task<UserDto> GetUser() public async Task<UserDto> GetUser()
@ -58,6 +62,26 @@ namespace Ombi.Controllers
return true; return true;
} }
[HttpGet("Users")]
public async Task<IEnumerable<UserViewModel>> GetAllUsers()
{
return Mapper.Map<IEnumerable<UserViewModel>>(await IdentityManager.GetUsers());
}
[HttpPost]
public async Task<UserViewModel> CreateUser([FromBody] UserViewModel user)
{
var userResult = await IdentityManager.CreateUser(Mapper.Map<UserDto>(user));
return Mapper.Map<UserViewModel>(userResult);
}
[HttpDelete]
public async Task<StatusCodeResult> DeleteUser([FromBody] UserViewModel user)
{
await IdentityManager.DeleteUser(Mapper.Map<UserDto>(user));
return Ok();
}
} }
} }

@ -1,10 +1,12 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Ombi.Core; using Ombi.Core;
using Ombi.Core.Engine;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
namespace Ombi.Controllers namespace Ombi.Controllers
@ -12,17 +14,22 @@ namespace Ombi.Controllers
[Authorize] [Authorize]
public class SearchController : BaseV1ApiController public class SearchController : BaseV1ApiController
{ {
public SearchController(IMovieEngine movie) public SearchController(IMovieEngine movie, ITvSearchEngine tvEngine, ILogger<SearchController> logger)
{ {
MovieEngine = movie; MovieEngine = movie;
TvEngine = tvEngine;
Logger = logger;
} }
private ILogger<SearchController> Logger { get; }
private IMovieEngine MovieEngine { get; } private IMovieEngine MovieEngine { get; }
private ITvSearchEngine TvEngine { get; }
[HttpGet("movie/{searchTerm}")] [HttpGet("movie/{searchTerm}")]
public async Task<IEnumerable<SearchMovieViewModel>> SearchMovie(string searchTerm) public async Task<IEnumerable<SearchMovieViewModel>> SearchMovie(string searchTerm)
{ {
return await MovieEngine.ProcessMovieSearch(searchTerm); Logger.LogDebug("Searching : {searchTerm}", searchTerm);
return await MovieEngine.Search(searchTerm);
} }
[HttpPost("movie/extrainfo")] [HttpPost("movie/extrainfo")]
@ -52,7 +59,10 @@ namespace Ombi.Controllers
return await MovieEngine.UpcomingMovies(); return await MovieEngine.UpcomingMovies();
} }
[HttpGet("tv/{searchTerm}")]
public async Task<IEnumerable<SearchTvShowViewModel>> SearchTv(string searchTerm)
{
return await TvEngine.Search(searchTerm);
}
} }
} }

@ -1,11 +1,11 @@
using System; using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Core.Settings.Models; using Ombi.Core.Settings.Models;
using Ombi.Core.Settings.Models.External; using Ombi.Core.Settings.Models.External;
using Ombi.Settings.Settings.Models;
namespace Ombi.Controllers namespace Ombi.Controllers
{ {
@ -68,6 +68,19 @@ namespace Ombi.Controllers
return await Save(settings); return await Save(settings);
} }
[HttpGet("customization")]
[AllowAnonymous]
public async Task<CustomizationSettings> CustomizationSettings()
{
return await Get<CustomizationSettings>();
}
[HttpPost("customization")]
public async Task<bool> CustomizationSettings([FromBody]CustomizationSettings settings)
{
return await Save(settings);
}
private async Task<T> Get<T>() private async Task<T> Get<T>()
{ {

File diff suppressed because one or more lines are too long

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx.10.12-x64;ubuntu.16.10-x64;debian.8-x64;</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;osx.10.12-x64;ubuntu.16.10-x64;debian.8-x64;centos.7-x64;</RuntimeIdentifiers>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup> </PropertyGroup>
@ -30,6 +30,11 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.0" /> <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.0" />
<PackageReference Include="Serilog" Version="2.4.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.File" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.SQLite" Version="3.8.3" />
<PackageReference Include="System.Security.Cryptography.Csp" Version="4.3.0" /> <PackageReference Include="System.Security.Cryptography.Csp" Version="4.3.0" />
</ItemGroup> </ItemGroup>
@ -42,4 +47,34 @@
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" /> <ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" /> <ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Update="wwwroot\app\interfaces\ISearchMovieResult - Copy.js">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\interfaces\ISearchTvResult.ts">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\search\moviesearch - Copy.component.html">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\search\moviesearch - Copy.component.js">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\search\moviesearch - Copy.component.js.map">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\search\moviesearch - Copy.component.ts">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\usermanagement\request.component.js">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\usermanagement\request.component.js.map">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\usermanagement\request.component.ts">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project> </Project>

@ -45,14 +45,14 @@ namespace Ombi
// Validate the token expiry // Validate the token expiry
ValidateLifetime = true, ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here: // If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero ClockSkew = TimeSpan.Zero,
}; };
app.UseJwtBearerAuthentication(new JwtBearerOptions app.UseJwtBearerAuthentication(new JwtBearerOptions
{ {
AutomaticAuthenticate = true, AutomaticAuthenticate = true,
AutomaticChallenge = true, AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters TokenValidationParameters = tokenValidationParameters,
}); });
app.UseMiddleware<TokenProviderMiddleware>(Options.Create(tokenProviderOptions)); app.UseMiddleware<TokenProviderMiddleware>(Options.Create(tokenProviderOptions));

@ -1,4 +1,5 @@
using System; using System;
using System.IO;
using System.Security.Principal; using System.Security.Principal;
using AutoMapper; using AutoMapper;
using AutoMapper.EquivalencyExpression; using AutoMapper.EquivalencyExpression;
@ -15,6 +16,8 @@ using Microsoft.Extensions.Logging;
using Ombi.DependencyInjection; using Ombi.DependencyInjection;
using Ombi.Mapping; using Ombi.Mapping;
using Ombi.Schedule; using Ombi.Schedule;
using Serilog;
using Serilog.Events;
namespace Ombi namespace Ombi
{ {
@ -30,6 +33,22 @@ namespace Ombi
.AddEnvironmentVariables(); .AddEnvironmentVariables();
Configuration = builder.Build(); Configuration = builder.Build();
if (env.IsDevelopment())
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Logs", "log-{Date}.txt"))
.WriteTo.SQLite("Ombi.db", "Logs", LogEventLevel.Debug)
.CreateLogger();
}
if (env.IsProduction())
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Logs", "log-{Date}.txt"))
.WriteTo.SQLite("Ombi.db", "Logs", LogEventLevel.Debug)
.CreateLogger();
}
} }
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
@ -45,15 +64,16 @@ namespace Ombi
expression.AddCollectionMappers(); expression.AddCollectionMappers();
}); });
services.RegisterDependencies(); // Ioc and EF services.RegisterDependencies(); // Ioc and EF
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IPrincipal>(sp => sp.GetService<IHttpContextAccessor>().HttpContext.User); services.AddScoped<IPrincipal>(sp => sp.GetService<IHttpContextAccessor>().HttpContext.User);
services.AddHangfire(x => services.AddHangfire(x =>
{ {
x.UseMemoryStorage(new MemoryStorageOptions()); x.UseMemoryStorage(new MemoryStorageOptions());
//x.UseActivator(new IoCJobActivator(services.BuildServiceProvider())); x.UseActivator(new IoCJobActivator(services.BuildServiceProvider()));
}); });
} }
@ -61,9 +81,11 @@ namespace Ombi
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ {
loggerFactory.AddConsole(Configuration.GetSection("Logging")); //loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug(); //loggerFactory.AddDebug();
loggerFactory.AddSerilog();
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
@ -79,13 +101,13 @@ namespace Ombi
ConfigureAuth(app); ConfigureAuth(app);
//var provider = new FileExtensionContentTypeProvider(); var provider = new FileExtensionContentTypeProvider();
//provider.Mappings[".map"] = "application/octet-stream"; provider.Mappings[".map"] = "application/octet-stream";
//app.UseStaticFiles(new StaticFileOptions() app.UseStaticFiles(new StaticFileOptions()
//{ {
// ContentTypeProvider = provider ContentTypeProvider = provider
//}); });
app.UseMvc(routes => app.UseMvc(routes =>
{ {

@ -239,4 +239,11 @@ button.list-group-item:focus {
text-align: center; text-align: center;
font-size: 15px; font-size: 15px;
padding: 3px 0; padding: 3px 0;
}
.nav .open > a,
.nav .open > a:hover,
.nav .open > a:focus {
background-color: $bg-colour;
border-color: $bg-colour-disabled;
} }

@ -3,17 +3,17 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^4.0.2", "@angular/animations": "^4.1.0",
"@angular/common": "^4.0.2", "@angular/common": "^4.1.0",
"@angular/compiler": "^4.0.2", "@angular/compiler": "^4.1.0",
"@angular/compiler-cli": "^4.0.2", "@angular/compiler-cli": "^4.1.0",
"@angular/core": "^4.0.2", "@angular/core": "^4.1.0",
"@angular/forms": "^4.0.2", "@angular/forms": "^4.1.0",
"@angular/http": "^4.0.2", "@angular/http": "^4.1.0",
"@angular/platform-browser": "^4.0.2", "@angular/platform-browser": "^4.1.0",
"@angular/platform-browser-dynamic": "^4.0.2", "@angular/platform-browser-dynamic": "^4.1.0",
"@angular/platform-server": "^4.0.2", "@angular/platform-server": "^4.1.0",
"@angular/router": "^4.0.0", "@angular/router": "^4.1.0",
"@types/jquery": "^2.0.33", "@types/jquery": "^2.0.33",
"@types/systemjs": "^0.20.2", "@types/systemjs": "^0.20.2",
"angular2-jwt": "0.2.0", "angular2-jwt": "0.2.0",

@ -9,25 +9,29 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" [routerLink]="['/']">Ombi</a>
<div *ngIf="customizationSettings.applicationName; then aplicationNameBlock; else ombiBlock"></div>
<ng-template #aplicationNameBlock><a class="navbar-brand" [routerLink]="['/']">{{customizationSettings.applicationName}}</a></ng-template>
<ng-template #ombiBlock><a class="navbar-brand" [routerLink]="['/']">Ombi</a></ng-template>
</div> </div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li><a [routerLinkActive]="['active']" [routerLink]="['/search']"><i class="fa fa-search"></i> Search</a></li> <li [routerLinkActive]="['active']"><a [routerLink]="['/search']"><i class="fa fa-search"></i> Search</a></li>
</ul> </ul>
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li><a [routerLinkActive]="['active']" [routerLink]="['/requests']"><i class="fa fa-plus"></i> Requests</a></li> <li [routerLinkActive]="['active']"><a [routerLink]="['/requests']"><i class="fa fa-plus"></i> Requests</a></li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li><a [routerLinkActive]="['active']" [routerLink]="['/Settings/Ombi']"><i class="fa fa-cog"></i> Settings</a></li> <li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Ombi']"><i class="fa fa-cog"></i> Settings</a></li>
<li class="dropdown"> <li [routerLinkActive]="['active']" class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> Welcome {{username}} <span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> Welcome {{username}} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li><a [routerLink]="['/user/changepassword']"><i class="fa fa-key"></i> Change Password</a></li> <li [routerLinkActive]="['active']"><a [routerLink]="['/user/changepassword']"><i class="fa fa-key"></i> Change Password</a></li>
<li><a (click)="logOut()"><i class="fa fa-sign-out"></i> Logout</a></li> <li [routerLinkActive]="['active']"><a (click)="logOut()"><i class="fa fa-sign-out"></i> Logout</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>

@ -1,8 +1,11 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { NotificationService } from './services/notification.service'; import { NotificationService } from './services/notification.service';
import { SettingsService } from './services/settings.service';
import { AuthService } from './auth/auth.service'; import { AuthService } from './auth/auth.service';
import { ICustomizationSettings } from './interfaces/ISettings';
@Component({ @Component({
selector: 'ombi', selector: 'ombi',
moduleId: module.id, moduleId: module.id,
@ -10,10 +13,14 @@ import { AuthService } from './auth/auth.service';
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
constructor(public notificationService: NotificationService, public authService: AuthService, private router: Router) { constructor(public notificationService: NotificationService, public authService: AuthService, private router: Router, private settingsService : SettingsService) {
} }
customizationSettings: ICustomizationSettings;
ngOnInit(): void { ngOnInit(): void {
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
this.router.events.subscribe(() => { this.router.events.subscribe(() => {
this.username = localStorage.getItem('currentUser'); this.username = localStorage.getItem('currentUser');
this.showNav = this.authService.loggedIn(); this.showNav = this.authService.loggedIn();

@ -10,11 +10,14 @@ import { HttpModule } from '@angular/http';
import { InfiniteScrollModule } from 'ngx-infinite-scroll' import { InfiniteScrollModule } from 'ngx-infinite-scroll'
// Components
import { SearchComponent } from './search/search.component'; import { SearchComponent } from './search/search.component';
import { MovieSearchComponent } from './search/moviesearch.component'; import { MovieSearchComponent } from './search/moviesearch.component';
import { TvSearchComponent } from './search/tvsearch.component';
import { RequestComponent } from './requests/request.component'; import { RequestComponent } from './requests/request.component';
import { LoginComponent } from './login/login.component'; import { LoginComponent } from './login/login.component';
import { LandingPageComponent } from './landingpage/landingpage.component'; import { LandingPageComponent } from './landingpage/landingpage.component';
import { UserManagementComponent } from './usermanagement/usermanagement.component';
import { PageNotFoundComponent } from './errors/not-found.component'; import { PageNotFoundComponent } from './errors/not-found.component';
// Services // Services
@ -44,6 +47,7 @@ const routes: Routes = [
{ path: 'requests', component: RequestComponent, canActivate: [AuthGuard] }, { path: 'requests', component: RequestComponent, canActivate: [AuthGuard] },
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },
{ path: 'landingpage', component: LandingPageComponent }, { path: 'landingpage', component: LandingPageComponent },
{ path: 'usermanagement', component: UserManagementComponent, canActivate: [AuthGuard] },
]; ];
@NgModule({ @NgModule({
@ -69,7 +73,9 @@ const routes: Routes = [
RequestComponent, RequestComponent,
LoginComponent, LoginComponent,
MovieSearchComponent, MovieSearchComponent,
LandingPageComponent TvSearchComponent,
LandingPageComponent,
UserManagementComponent
], ],
providers: [ providers: [
SearchService, SearchService,

@ -0,0 +1,28 @@
export interface ISearchTvResult {
id: number,
seriesName: string,
aliases: string[],
banner: string,
seriesId: number,
status: string,
firstAired: string,
network: string,
networkId: string,
runtime: string,
genre: string[],
overview: string,
lastUpdated: number,
airsDayOfWeek: string,
airsTime: string,
rating: string,
imdbId: string,
siteRating: number,
trailer: string,
homepage:string,
episodes:IEpisodeModel[],
}
export interface IEpisodeModel {
seasonNumber: number,
episodeNumber:number,
}

@ -48,4 +48,9 @@ export interface ILandingPageSettings extends ISettings {
timeLimit: boolean, timeLimit: boolean,
startDateTime: Date, startDateTime: Date,
endDateTime:Date, endDateTime:Date,
}
export interface ICustomizationSettings extends ISettings {
applicationName: string,
logo:string
} }

@ -0,0 +1,16 @@
export interface IUser {
id: string,
username: string,
alias: string,
claims: string[],
emailAddress: string,
password: string,
userType : UserType,
}
export enum UserType {
LocalUser = 1,
PlexUser = 2,
EmbyUser = 3
}

@ -3,3 +3,9 @@
background: #333333 !important; background: #333333 !important;
border-radius: 2% border-radius: 2%
} }
.landing-logo {
position: relative;
right: 20%;
width: 300px
}

@ -1,8 +1,12 @@
<div *ngIf="landingPageSettings"> <div *ngIf="landingPageSettings && customizationSettings">
<h1>Hey! Welcome back to {{websiteName}}</h1>
<h3 *ngIf="settings.noticeEnabled" style="background-color: {{settings.noticeBackgroundColor}}">{{settings.noticeText}}</h3> <div *ngIf="customizationSettings.logo" class="landing-logo">
<img [src]="customizationSettings.logo" />
</div>
<h1>Hey! Welcome back to {{customizationSettings.applicationName}}</h1>
<h3 *ngIf="landingPageSettings.noticeEnabled" [innerHtml]="landingPageSettings.noticeText" style="background-color: {{landingPageSettings.noticeBackgroundColor}}"></h3>
<div class="col-md-3 landing-box"> <div class="col-md-3 landing-box">
<div> <div>
Request Something Request Something

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { SettingsService } from '../services/settings.service'; import { SettingsService } from '../services/settings.service';
import { ILandingPageSettings } from '../interfaces/ISettings'; import { ILandingPageSettings, ICustomizationSettings } from '../interfaces/ISettings';
@Component({ @Component({
selector: 'ombi', selector: 'ombi',
@ -12,16 +12,11 @@ export class LandingPageComponent implements OnInit {
constructor(private settingsService: SettingsService) { } constructor(private settingsService: SettingsService) { }
websiteName: string; customizationSettings : ICustomizationSettings;
landingPageSettings: ILandingPageSettings; landingPageSettings: ILandingPageSettings;
ngOnInit(): void { ngOnInit(): void {
this.settingsService.getLandingPage().subscribe(x => { this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
this.landingPageSettings = x; this.settingsService.getLandingPage().subscribe(x => this.landingPageSettings = x);
console.log(x);
});
this.websiteName = "Ombi";
} }
} }

@ -16,8 +16,7 @@ import { IRequestModel } from '../interfaces/IRequestModel';
@Component({ @Component({
selector: 'ombi', selector: 'ombi',
moduleId: module.id, moduleId: module.id,
templateUrl: './request.component.html', templateUrl: './request.component.html'
providers: [RequestService]
}) })
export class RequestComponent implements OnInit { export class RequestComponent implements OnInit {
constructor(private requestService: RequestService) { constructor(private requestService: RequestService) {

@ -7,17 +7,16 @@
<ul id="nav-tabs" class="nav nav-tabs" role="tablist"> <ul id="nav-tabs" class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"> <li role="presentation" class="active">
<a id="movieTabButton" href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> Movies</a> <a id="movieTabButton" href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab" (click)="selectTab('movies')"><i class="fa fa-film"></i> Movies</a>
</li> </li>
<li role="presentation"> <!--<li role="presentation">
<a id="actorTabButton" href="#ActorsTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-users"></i> Actors</a> <a id="actorTabButton" href="#ActorsTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-users"></i> Actors</a>
</li> </li>-->
<li role="presentation"> <li role="presentation">
<a id="tvTabButton" href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i> TV Shows</a> <a id="tvTabButton" href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTab('tv')"><i class="fa fa-television"></i> TV Shows</a>
</li> </li>
<!-- <!--
@ -30,9 +29,11 @@
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
<movie-search></movie-search> <div *ngIf="showMovie">
<movie-search></movie-search>
</div>
<!-- Actors tab --> <!--
<div role="tabpanel" class="tab-pane" id="ActorsTab"> <div role="tabpanel" class="tab-pane" id="ActorsTab">
<div class="input-group"> <div class="input-group">
<input id="actorSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons"> <input id="actorSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons">
@ -46,35 +47,14 @@
<br /> <br />
<br /> <br />
<!-- Movie content -->
<div id="actorMovieList"> <div id="actorMovieList">
</div> </div>
</div> </div>-->
<div *ngIf="showTv">
<tv-search></tv-search>
<!-- TV tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab">
<div class="input-group">
<input id="tvSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons">
<div class="input-group-addon">
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
@UI.Search_Suggestions
<i class="fa fa-chevron-down"></i>
</a>
<ul class="dropdown-menu">
<li><a id="popularShows">Popular Shows</a></li>
<li><a id="trendingShows" href="#">Trending Shows</a></li>
<li><a id="mostWatchedShows" href="#">Most Watched Shows</a></li>
<li><a id="anticipatedShows" href="#">Most Anticipated Shows</a></li>
</ul>
</div><i id="tvSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- TV content -->
<div id="tvList">
</div>
</div> </div>
</div> </div>

@ -23,6 +23,10 @@ export class SearchComponent implements OnInit {
movieResults: ISearchMovieResult[]; movieResults: ISearchMovieResult[];
result: IRequestEngineResult; result: IRequestEngineResult;
tabSelected: string;
showTv: boolean;
showMovie:boolean;
constructor(private searchService: SearchService, private requestService: RequestService, private notificationService : NotificationService) { constructor(private searchService: SearchService, private requestService: RequestService, private notificationService : NotificationService) {
this.searchChanged this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event .debounceTime(600) // Wait Xms afterthe last event before emitting last event
@ -44,6 +48,8 @@ export class SearchComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.selectTab("movies");
this.searchText = ""; this.searchText = "";
this.movieResults = []; this.movieResults = [];
this.result = { this.result = {
@ -52,6 +58,8 @@ export class SearchComponent implements OnInit {
} }
} }
search(text: any) { search(text: any) {
this.searchChanged.next(text.target.value); this.searchChanged.next(text.target.value);
} }
@ -70,6 +78,19 @@ export class SearchComponent implements OnInit {
}); });
} }
selectTab(tabName: string) {
console.log(tabName);
this.tabSelected = tabName;
if (tabName === 'movies') {
this.showMovie = true;
this.showTv = false;
} else {
this.showMovie = false;
this.showTv = true;
}
}
popularMovies() { popularMovies() {
this.clearResults(); this.clearResults();
this.searchService.popularMovies().subscribe(x => { this.searchService.popularMovies().subscribe(x => {

@ -0,0 +1,161 @@
<!-- Movie tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab">
<div class="input-group">
<input id="tvSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
<div class="input-group-addon">
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Suggestions
<i class="fa fa-chevron-down"></i>
</a>
<ul class="dropdown-menu">
<li><a id="popularShows">Popular Shows</a></li>
<li><a id="trendingShows">Trending Shows</a></li>
<li><a id="mostWatchedShows">Most Watched Shows</a></li>
<li><a id="anticipatedShows">Most Anticipated Shows</a></li>
</ul>
</div><i id="tvSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- Movie content -->
<div id="actorMovieList">
</div>
<br />
<br />
<!-- TV content -->
<div id="tvList">
<div *ngFor="let result of tvResults">
<div class="row">
<div class="col-sm-2">
<img *ngIf="result.banner" class="img-responsive" width="150" [src]="result.banner" alt="poster">
</div>
<div class="col-sm-8">
<div>
<a href="http://www.imdb.com/title/{{result.imdbId}}/" target="_blank">
{{result.seriesName}} ({{result.firstAired}})
</a><span *ngIf="result.status" class="label label-primary" style="font-size:60%" target="_blank">{{result.status}}</span>
<span *ngIf="result.firstAired" class="label label-info" target="_blank">Air Date: {{result.firstAired}}</span>
<span *ngIf="result.releaseDate" class="label label-info" target="_blank">Release Date: {{result.releaseDate | date: 'dd/MM/yyyy'}}</span>
<span *ngIf="result.available" class="label label-success">Available</span>
<span *ngIf="result.approved && !result.available" class="label label-info">Processing Request</span>
<div *ngIf="result.requested && !result.available; then requested else notRequested"></div>
<template #requested>
<span *ngIf="!result.available" class="label label-warning">Pending Approval</span>
</template>
<template #notRequested>
<span *ngIf="!result.available" class="label label-danger">Not Yet Requested</span>
</template>
<span id="{{id}}netflixTab"></span>
<a *ngIf="result.homepage" href="{{result.homepage}}" target="_blank"><span class="label label-info">HomePage</span></a>
<a *ngIf="result.trailer" href="{{result.trailer}}" target="_blank"><span class="label label-info">Trailer</span></a>
<br/>
<br/>
</div>
<p style="font-size:0.9rem !important">{{result.overview}}</p>
</div>
<div class="col-sm-2">
<input name="{{type}}Id" type="text" value="{{result.id}}" hidden="hidden" />
<div *ngIf="result.available">
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Available</button>
<div *ngIf="result.url">
<br />
<br />
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{result.url}}" target="_blank"><i class="fa fa-eye"></i> View In Plex</a>
</div>
</div>
<div *ngIf="result.requested; then requestedBtn else notRequestedBtn"></div>
<template #requestedBtn>
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]><i class="fa fa-check"></i> Requested</button>
</template>
<template #notRequestedBtn>
<button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline" (click)="request(result)"><i class="fa fa-plus"></i> Request</button>
</template>
<!--{{#if_eq type "tv"}}
{{#if_eq tvFullyAvailable true}}
@*//TODO Not used yet*@
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button><br />
{{else}}
{{#if_eq enableTvRequestsForOnlySeries true}}
<button id="{{id}}" style="text-align: right" class="btn {{#if available}}btn-success-outline{{else}}btn-primary-outline dropdownTv{{/if}} btn-primary-outline" season-select="0" type="button" {{#if available}} disabled{{/if}}><i class="fa fa-plus"></i> {{#if available}}@UI.Search_Available{{else}}@UI.Search_Request{{/if}}</button>
{{else}}
<div class="dropdown">
<button id="{{id}}" class="btn {{#if available}}btn-success-outline{{else}}btn-primary-outline{{/if}} dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> {{#if available}}@UI.Search_Available{{else}}@UI.Search_Request {{/if}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{id}}" season-select="0" class="dropdownTv " href="#">@UI.Search_AllSeasons</a></li>
{{#if_eq disableTvRequestsBySeason false}}
<li><a id="{{id}}" season-select="1" class="dropdownTv" href="#">@UI.Search_FirstSeason</a></li>
<li><a id="{{id}}" season-select="2" class="dropdownTv" href="#">@UI.Search_LatestSeason</a></li>
<li><a id="SeasonSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#seasonsModal" href="#">@UI.Search_SelectSeason...</a></li>
{{/if_eq}}
{{#if_eq disableTvRequestsByEpisode false}}
<li><a id="EpisodeSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#episodesModal" href="#">@UI.Search_SelectEpisode...</a></li>
{{/if_eq}}
</ul>
</div>
{{/if_eq}}
{{#if available}}
{{#if url}}
<br />
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a>
{{/if}}
{{/if}}
{{/if_eq}}
{{/if_eq}}-->
<br />
<div *ngIf="result.available">
<input name="providerId" type="text" value="{{id}}" hidden="hidden" />
<input name="type" type="text" value="{{type}}" hidden="hidden" />
<div class="dropdown">
<button class="btn btn-sm btn-danger-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-exclamation"></i> @UI.Search_ReportIssue
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a issue-select="0" class="dropdownIssue" href="#">WrongAudio</a></li>
<li><a issue-select="1" class="dropdownIssue" href="#">NoSubs</a></li>
<li><a issue-select="2" class="dropdownIssue" href="#">WrongContent</a></li>
<li><a issue-select="3" class="dropdownIssue" href="#">Playback</a></li>
<li><a issue-select="4" class="dropdownIssue" href="#" data-toggle="modal" data-target="#issuesModal">Other</a></li>
</ul>
</div>
</div>
</div>
</div>
<hr />
</div>
</div>
</div>

@ -0,0 +1,61 @@
import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import { SearchService } from '../services/search.service';
//import { RequestService } from '../services/request.service';
//import { NotificationService } from '../services/notification.service';
import { ISearchTvResult } from '../interfaces/ISearchTvResult';
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
@Component({
selector: 'tv-search',
moduleId: module.id,
templateUrl: './tvsearch.component.html',
})
export class TvSearchComponent implements OnInit {
searchText: string;
searchChanged: Subject<string> = new Subject<string>();
tvResults: ISearchTvResult[];
result: IRequestEngineResult;
constructor(private searchService: SearchService/*, private requestService: RequestService, private notificationService: NotificationService*/) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.clearResults();
return;
}
this.searchService.searchTv(this.searchText).subscribe(x => {
this.tvResults = x;
});
});
}
ngOnInit(): void {
this.searchText = "";
this.tvResults = [];
this.result = {
message: "",
requestAdded: false
}
}
search(text: any) {
this.searchChanged.next(text.target.value);
}
private clearResults() {
this.tvResults = [];
}
}

@ -4,6 +4,7 @@ import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
import { ServiceAuthHelpers } from './service.helpers'; import { ServiceAuthHelpers } from './service.helpers';
import {IUser} from '../interfaces/IUser';
@Injectable() @Injectable()
@ -11,7 +12,11 @@ export class IdentityService extends ServiceAuthHelpers {
constructor(http: AuthHttp, private regularHttp : Http) { constructor(http: AuthHttp, private regularHttp : Http) {
super(http, '/api/v1/Identity/'); super(http, '/api/v1/Identity/');
} }
createUser(username:string,password:string): Observable<boolean> { createWizardUser(username:string,password:string): Observable<boolean> {
return this.regularHttp.post(`${this.url}/Wizard/`, JSON.stringify({username:username, password:password}), { headers: this.headers }).map(this.extractData); return this.regularHttp.post(`${this.url}/Wizard/`, JSON.stringify({username:username, password:password}), { headers: this.headers }).map(this.extractData);
} }
getUsers(): Observable<IUser[]> {
return this.http.get(`${this.url}/Users`).map(this.extractData);
}
} }

@ -4,6 +4,7 @@ import { Observable } from 'rxjs/Rx';
import { ServiceAuthHelpers } from './service.helpers'; import { ServiceAuthHelpers } from './service.helpers';
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult'; import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
import { ISearchTvResult } from '../interfaces/ISearchTvResult';
@Injectable() @Injectable()
export class SearchService extends ServiceAuthHelpers { export class SearchService extends ServiceAuthHelpers {
@ -30,4 +31,8 @@ export class SearchService extends ServiceAuthHelpers {
extraInfo(movies: ISearchMovieResult[]): Observable<ISearchMovieResult[]> { extraInfo(movies: ISearchMovieResult[]): Observable<ISearchMovieResult[]> {
return this.http.post(`${this.url}/Movie/extrainfo`, JSON.stringify(movies), { headers: this.headers }).map(this.extractData); return this.http.post(`${this.url}/Movie/extrainfo`, JSON.stringify(movies), { headers: this.headers }).map(this.extractData);
} }
searchTv(searchTerm: string): Observable<ISearchTvResult[]> {
return this.http.get(`${this.url}/Tv/` + searchTerm).map(this.extractData);
}
} }

@ -4,7 +4,7 @@ import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
import { ServiceAuthHelpers } from './service.helpers'; import { ServiceAuthHelpers } from './service.helpers';
import { IOmbiSettings, IEmbySettings, IPlexSettings, ISonarrSettings,ILandingPageSettings } from '../interfaces/ISettings'; import { IOmbiSettings, IEmbySettings, IPlexSettings, ISonarrSettings,ILandingPageSettings, ICustomizationSettings } from '../interfaces/ISettings';
@Injectable() @Injectable()
export class SettingsService extends ServiceAuthHelpers { export class SettingsService extends ServiceAuthHelpers {
@ -53,4 +53,15 @@ export class SettingsService extends ServiceAuthHelpers {
return this.httpAuth.post(`${this.url}/LandingPage`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData); return this.httpAuth.post(`${this.url}/LandingPage`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
} }
// Using http since we need it not to be authenticated to get the customization settings
getCustomization(): Observable<ICustomizationSettings> {
return this.nonAuthHttp.get(`${this.url}/customization`).map(this.extractData);
}
saveCustomization(settings: ICustomizationSettings): Observable<boolean> {
return this.httpAuth.post(`${this.url}/customization`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
}
} }

@ -0,0 +1,34 @@
<settings-menu></settings-menu>
<fieldset *ngIf="settings">
<legend>Customization</legend>
<div class="form-group">
<label for="applicationName" class="control-label">Application Name</label>
<div>
<input type="text" [(ngModel)]="settings.applicationName" class="form-control form-control-custom " id="applicationName" name="applicationName" placeholder="Ombi" value="{{settings.applicationName}}">
</div>
</div>
<div class="form-group">
<label for="logo" class="control-label">Custom Logo</label>
<div>
<input type="text" [(ngModel)]="settings.logo" class="form-control form-control-custom " id="logo" name="logo" value="{{settings.logo}}">
</div>
</div>
<small>This will be used on all of the notifications e.g. Newsletter, email notification and also the Landing page</small>
<div class="form-group">
<label for="logo" class="control-label">Logo Preview:</label>
<div>
<img *ngIf="settings.logo" [src]="settings.logo" style="width: 300px"/>
</div>
</div>
<div class="form-group">
<div>
<button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</fieldset>

@ -0,0 +1,32 @@
import { Component, OnInit } from '@angular/core';
import { ICustomizationSettings } from '../../interfaces/ISettings'
import { SettingsService } from '../../services/settings.service';
import { NotificationService } from "../../services/notification.service";
@Component({
selector: 'ombi',
moduleId: module.id,
templateUrl: './customization.component.html',
})
export class CustomizationComponent implements OnInit {
constructor(private settingsService: SettingsService, private notificationService: NotificationService) { }
settings: ICustomizationSettings;
ngOnInit(): void {
this.settingsService.getCustomization().subscribe(x => this.settings = x);
}
save() {
this.settingsService.saveCustomization(this.settings).subscribe(x => {
if (x) {
this.notificationService.success("Settings Saved", "Successfully saved Ombi settings");
} else {
this.notificationService.success("Settings Saved", "There was an error when saving the Ombi settings");
}
});
}
}

@ -1,4 +1,6 @@
<div class="col-sm-8 col-sm-push-1"> 
<settings-menu></settings-menu>
<div *ngIf="settings">
<fieldset> <fieldset>
<legend>Emby Configuration</legend> <legend>Emby Configuration</legend>

@ -1,4 +1,6 @@
<div class="col-sm-8 col-sm-push-1" *ngIf="settings"> 
<settings-menu></settings-menu>
<div *ngIf="settings">
<fieldset> <fieldset>
<legend>Landing Page Configuration</legend> <legend>Landing Page Configuration</legend>

@ -1,5 +1,6 @@
<div class="col-sm-8 col-sm-push-1"> 
<fieldset> <settings-menu></settings-menu>
<fieldset *ngIf="settings">
<legend>Ombi Configuration</legend> <legend>Ombi Configuration</legend>
<div class="form-group"> <div class="form-group">
@ -47,5 +48,4 @@
<button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button> <button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div> </div>
</div> </div>
</fieldset> </fieldset>
</div>

@ -1,4 +1,6 @@
<div class="col-sm-8 col-sm-push-1"> 
<settings-menu></settings-menu>
<div *ngIf="settings">
<fieldset> <fieldset>
<legend>Plex Configuration</legend> <legend>Plex Configuration</legend>

@ -12,6 +12,7 @@ import { OmbiComponent } from './ombi/ombi.component'
import { PlexComponent } from './plex/plex.component' import { PlexComponent } from './plex/plex.component'
import { EmbyComponent } from './emby/emby.component' import { EmbyComponent } from './emby/emby.component'
import { LandingPageComponent } from './landingpage/landingpage.component' import { LandingPageComponent } from './landingpage/landingpage.component'
import { CustomizationComponent } from './customization/customization.component'
import { SettingsMenuComponent } from './settingsmenu.component'; import { SettingsMenuComponent } from './settingsmenu.component';
@ -22,6 +23,7 @@ const routes: Routes = [
{ path: 'Settings/Plex', component: PlexComponent, canActivate: [AuthGuard] }, { path: 'Settings/Plex', component: PlexComponent, canActivate: [AuthGuard] },
{ path: 'Settings/Emby', component: EmbyComponent, canActivate: [AuthGuard] }, { path: 'Settings/Emby', component: EmbyComponent, canActivate: [AuthGuard] },
{ path: 'Settings/LandingPage', component: LandingPageComponent, canActivate: [AuthGuard] }, { path: 'Settings/LandingPage', component: LandingPageComponent, canActivate: [AuthGuard] },
{ path: 'Settings/Customization', component: CustomizationComponent, canActivate: [AuthGuard] },
]; ];
@NgModule({ @NgModule({
@ -40,6 +42,7 @@ const routes: Routes = [
PlexComponent, PlexComponent,
EmbyComponent, EmbyComponent,
LandingPageComponent, LandingPageComponent,
CustomizationComponent,
], ],
exports: [ exports: [
RouterModule RouterModule

@ -1 +1,64 @@
<p-menu [model]="menu"></p-menu> <ul class="nav nav-tabs">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Ombi']">Ombi</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Customization']">Customization</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/LandingPage']">Landing Page</a></li>
<li class="dropdown" [routerLinkActive]="['active']">
<a class="dropdown-toggle" data-toggle="dropdown">
Media Server <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Plex']">Plex</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Emby']">Emby</a></li>
</ul>
</li>
<li class="dropdown" [routerLinkActive]="['active']">
<a class="dropdown-toggle" data-toggle="dropdown">
TV <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Sonarr']" >Sonarr</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/SickRage']" >SickRage</a></li>
</ul>
</li>
<li class="dropdown" [routerLinkActive]="['active']">
<a class="dropdown-toggle" data-toggle="dropdown">
Movies <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/CouchPotato']">CouchPotato</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Radarr']">Radarr</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Watcher']">Watcher</a></li>
</ul>
</li>
<li class="dropdown" [routerLinkActive]="['active']">
<a class="dropdown-toggle" data-toggle="dropdown">
Notifications <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Email']">Email</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Newsletter']">Newsletter</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Pushbullet']">Pushbullet</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Pushover']">Pushover</a></li>
</ul>
</li>
<li class="dropdown" [routerLinkActive]="['active']">
<a class="dropdown-toggle" data-toggle="dropdown">
System <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Update']">Update</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Logs']">Logs</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/ScheduledJobs']">Scheduled Jobs</a></li>
</ul>
</li>
</ul>
<hr/>

@ -1,27 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component } from '@angular/core';
import { MenuItem } from 'primeng/primeng';
@Component({ @Component({
selector: 'settings-menu', selector: 'settings-menu',
moduleId: module.id, moduleId: module.id,
templateUrl: './settingsmenu.component.html' templateUrl: './settingsmenu.component.html'
}) })
export class SettingsMenuComponent implements OnInit { export class SettingsMenuComponent {
private menu: MenuItem[];
ngOnInit() {
this.menu = [{
label: 'File',
items: [
{ label: 'Ombi', icon: 'fa-plus', routerLink:"/Settings/Ombi" },
{ label: 'Open', icon: 'fa-download' }
]
},
{
label: 'Edit',
items: [
{ label: 'Undo', icon: 'fa-refresh' },
{ label: 'Redo', icon: 'fa-repeat' }
]
}];
}
} }

@ -1,4 +1,6 @@
<div class="col-sm-8 col-sm-push-1"> 
<settings-menu></settings-menu>
<div *ngIf="settings">
<form class="form-horizontal" method="POST" id="mainForm"> <form class="form-horizontal" method="POST" id="mainForm">
<fieldset> <fieldset>
<legend>Sonarr Settings</legend> <legend>Sonarr Settings</legend>

@ -0,0 +1,74 @@
<h1>User Management</h1>
<!--Search-->
<div class="row">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-search"></i>
</div>
<input type="text" class="form-control" placeholder="Search" [(ngModel)]="searchTerm">
</div>
</div>
</div>
<!-- Table -->
<table class="table table-striped table-hover table-responsive table-condensed">
<thead>
<tr>
<th>
<a (click)="changeSort('username')">
Username
<!--<span ng-show="sortType == 'username' && !sortReverse" class="fa fa-caret-down"></span>
<span ng-show="sortType == 'username' && sortReverse" class="fa fa-caret-up"></span>-->
</a>
</th>
<th>
<a>
Alias
</a>
</th>
<th>
<a>
Email
</a>
</th>
<th>
Roles
</th>
<th>
<a>
User Type
</a>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let u of users">
<td>
{{u.username}}
</td>
<td>
{{u.alias}}
</td>
<td>
{{u.emailAddress}}
</td>
<td>
<span *ngFor="let claim of u.claims">{{claim}}</span>
</td>
<td ng-hide="hideColumns">
<span ng-if="u.userType === 1">Local User</span>
<span ng-if="u.userType === 2">Plex User</span>
<span ng-if="u.userType === 3">Emby User</span>
</td>
<td>
<a (click)="edit(u)" class="btn btn-sm btn-info-outline">Details/Edit</a>
</td>
</tr>
</tbody>
</table>

@ -0,0 +1,38 @@
import { Component, OnInit } from '@angular/core';
import { IUser } from '../interfaces/IUser';
import { IdentityService } from '../services/identity.service';
@Component({
selector: 'ombi',
moduleId: module.id,
templateUrl: './usermanagement.component.html'
})
export class UserManagementComponent implements OnInit {
constructor(private identityService: IdentityService) { }
ngOnInit(): void {
this.users = [];
this.identityService.getUsers().subscribe(x => {
this.users = x;
});
}
users: IUser[];
selectedUser: IUser;
edit(user: IUser) {
this.selectedUser = user;
}
changeSort(username: string) {
//??????
}
//private removeRequestFromUi(key : IRequestModel) {
// var index = this.requests.indexOf(key, 0);
// if (index > -1) {
// this.requests.splice(index, 1);
// }
//}
}

@ -21,7 +21,7 @@ export class CreateAdminComponent {
password: string; password: string;
createUser() { createUser() {
this.identityService.createUser(this.username, this.password).subscribe(x => { this.identityService.createWizardUser(this.username, this.password).subscribe(x => {
if (x) { if (x) {
// Log me in. // Log me in.
this.auth.login({ username: this.username, password: this.password }).subscribe(c => { this.auth.login({ username: this.username, password: this.password }).subscribe(c => {

@ -24,6 +24,7 @@ after_build:
- dotnet publish -c Release -r osx.10.12-x64 - dotnet publish -c Release -r osx.10.12-x64
- dotnet publish -c Release -r ubuntu.16.10-x64 - dotnet publish -c Release -r ubuntu.16.10-x64
- dotnet publish -c Release -r debian.8-x64 - dotnet publish -c Release -r debian.8-x64
- dotnet publish -c Release -r centos.7-x64
- cmd: >- - cmd: >-
7z a Ombi_windows.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\win10-x64\publish 7z a Ombi_windows.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\win10-x64\publish
@ -35,6 +36,9 @@ after_build:
7z a Ombi_debian.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\debian.8-x64\publish 7z a Ombi_debian.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\debian.8-x64\publish
7z a Ombi_centos.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\centos.7-x64\publish
appveyor PushArtifact Ombi_windows.zip appveyor PushArtifact Ombi_windows.zip
@ -49,6 +53,9 @@ after_build:
appveyor PushArtifact Ombi_debian.zip appveyor PushArtifact Ombi_debian.zip
appveyor PushArtifact Ombi_centos.zip
cache: cache:
- '%USERPROFILE%\.nuget\packages' - '%USERPROFILE%\.nuget\packages'

Loading…
Cancel
Save