From b8165b311cbe07d88e7179d78a211177b35d0d22 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Tue, 20 Jul 2021 21:39:44 -0500 Subject: [PATCH] Misc Polishing (#413) * Ensure that after we assign a role to a user, we show it immediately * Cached libraryType api as that is not going to change in a viewing session. Moved some components around to tighten bundles. * Cleaned up more TODOs * Refactored Configuration to use getter and setters so that the interface is a lot cleaner. Updated HashUtil to use JWT Secret instead of Machine name (as docker machine name is random each boot). --- API/Controllers/SettingsController.cs | 34 +- API/Data/Seed.cs | 17 +- .../ApplicationServiceExtensions.cs | 2 +- API/Program.cs | 219 +++++------ API/Startup.cs | 2 +- Kavita.Common/Configuration.cs | 366 ++++++++++++------ Kavita.Common/HashUtil.cs | 2 +- Kavita.Common/Kavita.Common.csproj | 1 + .../edit-series-modal.component.html | 1 - UI/Web/src/app/_models/in-progress-chapter.ts | 1 - UI/Web/src/app/_services/library.service.ts | 16 +- .../directory-picker.component.ts | 2 - .../edit-rbs-modal.component.ts | 6 +- .../manage-library.component.ts | 2 +- .../manage-settings.component.html | 8 +- .../manage-users/manage-users.component.ts | 5 + UI/Web/src/app/app.module.ts | 4 + .../library-card/library-card.component.html | 0 .../library-card/library-card.component.scss | 0 .../library-card/library-card.component.ts | 0 .../manga-reader/manga-reader.component.html | 2 +- .../register-member.component.html | 0 .../register-member.component.scss | 0 .../register-member.component.ts | 0 .../series-card/series-card.component.html | 0 .../series-card/series-card.component.scss | 0 .../series-card/series-card.component.ts | 8 +- UI/Web/src/app/shared/shared.module.ts | 10 +- .../src/app/typeahead/typeahead.component.ts | 7 - 29 files changed, 408 insertions(+), 307 deletions(-) rename UI/Web/src/app/{shared => }/library-card/library-card.component.html (100%) rename UI/Web/src/app/{shared => }/library-card/library-card.component.scss (100%) rename UI/Web/src/app/{shared => }/library-card/library-card.component.ts (100%) rename UI/Web/src/app/{shared => }/register-member/register-member.component.html (100%) rename UI/Web/src/app/{shared => }/register-member/register-member.component.scss (100%) rename UI/Web/src/app/{shared => }/register-member/register-member.component.ts (100%) rename UI/Web/src/app/{shared => }/series-card/series-card.component.html (100%) rename UI/Web/src/app/{shared => }/series-card/series-card.component.scss (100%) rename UI/Web/src/app/{shared => }/series-card/series-card.component.ts (92%) diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index 2ead0916c..df275bb67 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using API.Data; using API.DTOs; using API.Entities.Enums; using API.Extensions; @@ -13,7 +12,6 @@ using Kavita.Common; using Kavita.Common.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace API.Controllers @@ -24,26 +22,24 @@ namespace API.Controllers private readonly ILogger _logger; private readonly IUnitOfWork _unitOfWork; private readonly ITaskScheduler _taskScheduler; - private readonly IConfiguration _configuration; - public SettingsController(ILogger logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler, IConfiguration configuration) + public SettingsController(ILogger logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler) { _logger = logger; _unitOfWork = unitOfWork; _taskScheduler = taskScheduler; - _configuration = configuration; } - [HttpGet("")] + [HttpGet] public async Task> GetSettings() { var settingsDto = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); - settingsDto.Port = Configuration.GetPort(Program.GetAppSettingFilename()); - settingsDto.LoggingLevel = Configuration.GetLogLevel(Program.GetAppSettingFilename()); + settingsDto.Port = Configuration.Port; + settingsDto.LoggingLevel = Configuration.LogLevel; return Ok(settingsDto); } - [HttpPost("")] + [HttpPost] public async Task> UpdateSettings(ServerSettingDto updateSettingsDto) { _logger.LogInformation("{UserName} is updating Server Settings", User.GetUsername()); @@ -61,9 +57,6 @@ namespace API.Controllers // We do not allow CacheDirectory changes, so we will ignore. var currentSettings = await _unitOfWork.SettingsRepository.GetSettingsAsync(); - var logLevelOptions = new LogLevelOptions(); - _configuration.GetSection("Logging:LogLevel").Bind(logLevelOptions); - foreach (var setting in currentSettings) { if (setting.Key == ServerSettingKey.TaskBackup && updateSettingsDto.TaskBackup != setting.Value) @@ -78,24 +71,24 @@ namespace API.Controllers _unitOfWork.SettingsRepository.Update(setting); } - if (setting.Key == ServerSettingKey.Port && updateSettingsDto.Port + "" != setting.Value) + if (setting.Key == ServerSettingKey.Port && updateSettingsDto.Port + string.Empty != setting.Value) { - setting.Value = updateSettingsDto.Port + ""; + setting.Value = updateSettingsDto.Port + string.Empty; // Port is managed in appSetting.json - Configuration.UpdatePort(Program.GetAppSettingFilename(), updateSettingsDto.Port); + Configuration.Port = updateSettingsDto.Port; _unitOfWork.SettingsRepository.Update(setting); } - if (setting.Key == ServerSettingKey.LoggingLevel && updateSettingsDto.LoggingLevel + "" != setting.Value) + if (setting.Key == ServerSettingKey.LoggingLevel && updateSettingsDto.LoggingLevel + string.Empty != setting.Value) { - setting.Value = updateSettingsDto.LoggingLevel + ""; - Configuration.UpdateLogLevel(Program.GetAppSettingFilename(), updateSettingsDto.LoggingLevel); + setting.Value = updateSettingsDto.LoggingLevel + string.Empty; + Configuration.LogLevel = updateSettingsDto.LoggingLevel; _unitOfWork.SettingsRepository.Update(setting); } - if (setting.Key == ServerSettingKey.AllowStatCollection && updateSettingsDto.AllowStatCollection + "" != setting.Value) + if (setting.Key == ServerSettingKey.AllowStatCollection && updateSettingsDto.AllowStatCollection + string.Empty != setting.Value) { - setting.Value = updateSettingsDto.AllowStatCollection + ""; + setting.Value = updateSettingsDto.AllowStatCollection + string.Empty; _unitOfWork.SettingsRepository.Update(setting); if (!updateSettingsDto.AllowStatCollection) { @@ -108,7 +101,6 @@ namespace API.Controllers } } - _configuration.GetSection("Logging:LogLevel:Default").Value = updateSettingsDto.LoggingLevel + ""; if (!_unitOfWork.HasChanges()) return Ok("Nothing was updated"); if (!_unitOfWork.HasChanges() || !await _unitOfWork.CommitAsync()) diff --git a/API/Data/Seed.cs b/API/Data/Seed.cs index 2c7eb373b..7185b403d 100644 --- a/API/Data/Seed.cs +++ b/API/Data/Seed.cs @@ -29,7 +29,7 @@ namespace API.Data var exists = await roleManager.RoleExistsAsync(role.Name); if (!exists) { - await roleManager.CreateAsync(role); + await roleManager.CreateAsync(role); } } } @@ -37,7 +37,7 @@ namespace API.Data public static async Task SeedSettings(DataContext context) { await context.Database.EnsureCreatedAsync(); - + IList defaultSettings = new List() { new() {Key = ServerSettingKey.CacheDirectory, Value = CacheService.CacheDirectory}, @@ -46,7 +46,7 @@ namespace API.Data new () {Key = ServerSettingKey.TaskBackup, Value = "weekly"}, new () {Key = ServerSettingKey.BackupDirectory, Value = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "backups/"))}, new () {Key = ServerSettingKey.Port, Value = "5000"}, // Not used from DB, but DB is sync with appSettings.json - new () {Key = ServerSettingKey.AllowStatCollection, Value = "true"}, + new () {Key = ServerSettingKey.AllowStatCollection, Value = "true"}, }; foreach (var defaultSetting in defaultSettings) @@ -61,14 +61,13 @@ namespace API.Data await context.SaveChangesAsync(); // Port and LoggingLevel are managed in appSettings.json. Update the DB values to match - var configFile = Program.GetAppSettingFilename(); context.ServerSetting.FirstOrDefault(s => s.Key == ServerSettingKey.Port).Value = - Configuration.GetPort(configFile) + ""; + Configuration.Port + string.Empty; context.ServerSetting.FirstOrDefault(s => s.Key == ServerSettingKey.LoggingLevel).Value = - Configuration.GetLogLevel(configFile); - + Configuration.LogLevel + string.Empty; + await context.SaveChangesAsync(); - + } } -} \ No newline at end of file +} diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index 0a43db49e..f33b72809 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -43,7 +43,7 @@ namespace API.Extensions services.AddDbContext(options => { options.UseSqlite(config.GetConnectionString("DefaultConnection")); - options.EnableSensitiveDataLogging(env.IsDevelopment() || Configuration.GetLogLevel(Program.GetAppSettingFilename()).Equals("Debug")); + options.EnableSensitiveDataLogging(env.IsDevelopment() || Configuration.LogLevel.Equals("Debug")); }); } diff --git a/API/Program.cs b/API/Program.cs index 238036960..a3b0e6773 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -18,132 +18,117 @@ using Sentry; namespace API { - public class Program - { - private static int _httpPort; + public class Program + { + private static readonly int HttpPort = Configuration.Port; - protected Program() - { - } + protected Program() + { + } - public static string GetAppSettingFilename() - { - var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); - var isDevelopment = environment == Environments.Development; - return "appsettings" + (isDevelopment ? ".Development" : "") + ".json"; - } + public static async Task Main(string[] args) + { + Console.OutputEncoding = System.Text.Encoding.UTF8; - public static async Task Main(string[] args) - { - Console.OutputEncoding = System.Text.Encoding.UTF8; + // Before anything, check if JWT has been generated properly or if user still has default + if (!Configuration.CheckIfJwtTokenSet() && + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != Environments.Development) + { + Console.WriteLine("Generating JWT TokenKey for encrypting user sessions..."); + var rBytes = new byte[128]; + using (var crypto = new RNGCryptoServiceProvider()) crypto.GetBytes(rBytes); + Configuration.JwtToken = Convert.ToBase64String(rBytes).Replace("/", string.Empty); + } - // Before anything, check if JWT has been generated properly or if user still has default - if (!Configuration.CheckIfJwtTokenSet(GetAppSettingFilename()) && Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != Environments.Development) + var host = CreateHostBuilder(args).Build(); + + using var scope = host.Services.CreateScope(); + var services = scope.ServiceProvider; + + try + { + var context = services.GetRequiredService(); + var roleManager = services.GetRequiredService>(); + // Apply all migrations on startup + await context.Database.MigrateAsync(); + await Seed.SeedRoles(roleManager); + await Seed.SeedSettings(context); + } + catch (Exception ex) + { + var logger = services.GetRequiredService>(); + logger.LogError(ex, "An error occurred during migration"); + } + + await host.RunAsync(); + } + + private static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { - Console.WriteLine("Generating JWT TokenKey for encrypting user sessions..."); - var rBytes = new byte[128]; - using (var crypto = new RNGCryptoServiceProvider()) crypto.GetBytes(rBytes); - var base64 = Convert.ToBase64String(rBytes).Replace("/", ""); - Configuration.UpdateJwtToken(GetAppSettingFilename(), base64); - } + webBuilder.UseKestrel((opts) => + { + opts.ListenAnyIP(HttpPort, options => { options.Protocols = HttpProtocols.Http1AndHttp2; }); + }); - // Get HttpPort from Config - _httpPort = Configuration.GetPort(GetAppSettingFilename()); + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + if (environment != Environments.Development) + { + webBuilder.UseSentry(options => + { + options.Dsn = "https://40f4e7b49c094172a6f99d61efb2740f@o641015.ingest.sentry.io/5757423"; + options.MaxBreadcrumbs = 200; + options.AttachStacktrace = true; + options.Debug = false; + options.SendDefaultPii = false; + options.DiagnosticLevel = SentryLevel.Debug; + options.ShutdownTimeout = TimeSpan.FromSeconds(5); + options.Release = BuildInfo.Version.ToString(); + options.AddExceptionFilterForType(); + options.AddExceptionFilterForType(); + options.AddExceptionFilterForType(); + options.AddExceptionFilterForType(); - - var host = CreateHostBuilder(args).Build(); - - using var scope = host.Services.CreateScope(); - var services = scope.ServiceProvider; - - try - { - var context = services.GetRequiredService(); - var roleManager = services.GetRequiredService>(); - // Apply all migrations on startup - await context.Database.MigrateAsync(); - await Seed.SeedRoles(roleManager); - await Seed.SeedSettings(context); - } - catch (Exception ex) - { - var logger = services.GetRequiredService >(); - logger.LogError(ex, "An error occurred during migration"); - } - - await host.RunAsync(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseKestrel((opts) => - { - opts.ListenAnyIP(_httpPort, options => + options.BeforeSend = sentryEvent => + { + if (sentryEvent.Exception != null + && sentryEvent.Exception.Message.StartsWith("[GetCoverImage]") + && sentryEvent.Exception.Message.StartsWith("[BookService]") + && sentryEvent.Exception.Message.StartsWith("[ExtractArchive]") + && sentryEvent.Exception.Message.StartsWith("[GetSummaryInfo]") + && sentryEvent.Exception.Message.StartsWith("[GetSummaryInfo]") + && sentryEvent.Exception.Message.StartsWith("[GetNumberOfPagesFromArchive]") + && sentryEvent.Exception.Message.Contains("EPUB parsing error") + && sentryEvent.Exception.Message.Contains("Unsupported EPUB version") + && sentryEvent.Exception.Message.Contains("Incorrect EPUB") + && sentryEvent.Exception.Message.Contains("Access is Denied")) { - options.Protocols = HttpProtocols.Http1AndHttp2; - }); - }); + return null; // Don't send this event to Sentry + } - var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); - if (environment != Environments.Development) - { - webBuilder.UseSentry(options => + sentryEvent.ServerName = null; // Never send Server Name to Sentry + return sentryEvent; + }; + + options.ConfigureScope(scope => + { + scope.User = new User() { - options.Dsn = "https://40f4e7b49c094172a6f99d61efb2740f@o641015.ingest.sentry.io/5757423"; - options.MaxBreadcrumbs = 200; - options.AttachStacktrace = true; - options.Debug = false; - options.SendDefaultPii = false; - options.DiagnosticLevel = SentryLevel.Debug; - options.ShutdownTimeout = TimeSpan.FromSeconds(5); - options.Release = BuildInfo.Version.ToString(); - options.AddExceptionFilterForType(); - options.AddExceptionFilterForType(); - options.AddExceptionFilterForType(); - options.AddExceptionFilterForType(); + Id = HashUtil.AnonymousToken() + }; + scope.Contexts.App.Name = BuildInfo.AppName; + scope.Contexts.App.Version = BuildInfo.Version.ToString(); + scope.Contexts.App.StartTime = DateTime.UtcNow; + scope.Contexts.App.Hash = HashUtil.AnonymousToken(); + scope.Contexts.App.Build = BuildInfo.Release; + scope.SetTag("culture", Thread.CurrentThread.CurrentCulture.Name); + scope.SetTag("branch", BuildInfo.Branch); + }); + }); + } - options.BeforeSend = sentryEvent => - { - if (sentryEvent.Exception != null - && sentryEvent.Exception.Message.StartsWith("[GetCoverImage]") - && sentryEvent.Exception.Message.StartsWith("[BookService]") - && sentryEvent.Exception.Message.StartsWith("[ExtractArchive]") - && sentryEvent.Exception.Message.StartsWith("[GetSummaryInfo]") - && sentryEvent.Exception.Message.StartsWith("[GetSummaryInfo]") - && sentryEvent.Exception.Message.StartsWith("[GetNumberOfPagesFromArchive]") - && sentryEvent.Exception.Message.Contains("EPUB parsing error") - && sentryEvent.Exception.Message.Contains("Unsupported EPUB version") - && sentryEvent.Exception.Message.Contains("Incorrect EPUB") - && sentryEvent.Exception.Message.Contains("Access is Denied")) - { - return null; // Don't send this event to Sentry - } - - sentryEvent.ServerName = null; // Never send Server Name to Sentry - return sentryEvent; - }; - - options.ConfigureScope(scope => - { - scope.User = new User() - { - Id = HashUtil.AnonymousToken() - }; - scope.Contexts.App.Name = BuildInfo.AppName; - scope.Contexts.App.Version = BuildInfo.Version.ToString(); - scope.Contexts.App.StartTime = DateTime.UtcNow; - scope.Contexts.App.Hash = HashUtil.AnonymousToken(); - scope.Contexts.App.Build = BuildInfo.Release; - scope.SetTag("culture", Thread.CurrentThread.CurrentCulture.Name); - scope.SetTag("branch", BuildInfo.Branch); - }); - - }); - } - - webBuilder.UseStartup(); - }); - } + webBuilder.UseStartup(); + }); + } } diff --git a/API/Startup.cs b/API/Startup.cs index fea6d0603..61a104c1c 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -146,7 +146,7 @@ namespace API }); } - private void OnShutdown() + private static void OnShutdown() { Console.WriteLine("Server is shutting down. Please allow a few seconds to stop any background jobs..."); TaskScheduler.Client.Dispose(); diff --git a/Kavita.Common/Configuration.cs b/Kavita.Common/Configuration.cs index 755e57743..57e96b234 100644 --- a/Kavita.Common/Configuration.cs +++ b/Kavita.Common/Configuration.cs @@ -2,133 +2,259 @@ using System.IO; using System.Text.Json; using Kavita.Common.EnvironmentInfo; +using Microsoft.Extensions.Hosting; namespace Kavita.Common { - public static class Configuration - { - #region JWT Token - public static bool CheckIfJwtTokenSet(string filePath) - { - try { - var json = File.ReadAllText(filePath); - var jsonObj = JsonSerializer.Deserialize(json); - const string key = "TokenKey"; - - if (jsonObj.TryGetProperty(key, out JsonElement tokenElement)) - { - return tokenElement.GetString() != "super secret unguessable key"; - } + public static class Configuration + { + private static string AppSettingsFilename = GetAppSettingFilename(); + public static string Branch + { + get => GetBranch(GetAppSettingFilename()); + set => SetBranch(GetAppSettingFilename(), value); + } - return false; - - } - catch (Exception ex) { - Console.WriteLine("Error writing app settings: " + ex.Message); + public static int Port + { + get => GetPort(GetAppSettingFilename()); + set => SetPort(GetAppSettingFilename(), value); + } + + public static string JwtToken + { + get => GetJwtToken(GetAppSettingFilename()); + set => SetJwtToken(GetAppSettingFilename(), value); + } + + public static string LogLevel + { + get => GetLogLevel(GetAppSettingFilename()); + set => SetLogLevel(GetAppSettingFilename(), value); + } + + private static string GetAppSettingFilename() + { + if (!string.IsNullOrEmpty(AppSettingsFilename)) + { + return AppSettingsFilename; + } + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + var isDevelopment = environment == Environments.Development; + return "appsettings" + (isDevelopment ? ".Development" : "") + ".json"; + } + + #region JWT Token + + private static string GetJwtToken(string filePath) + { + try + { + var json = File.ReadAllText(filePath); + var jsonObj = JsonSerializer.Deserialize(json); + const string key = "TokenKey"; + + if (jsonObj.TryGetProperty(key, out JsonElement tokenElement)) + { + return tokenElement.GetString(); } + return string.Empty; + } + catch (Exception ex) + { + Console.WriteLine("Error reading app settings: " + ex.Message); + } + + return string.Empty; + } + + private static bool SetJwtToken(string filePath, string token) + { + try + { + var currentToken = GetJwtToken(filePath); + var json = File.ReadAllText(filePath) + .Replace("\"TokenKey\": \"" + currentToken, "\"TokenKey\": \"" + token); + File.WriteAllText(filePath, json); + return true; + } + catch (Exception) + { return false; - } - public static bool UpdateJwtToken(string filePath, string token) - { - try - { - var json = File.ReadAllText(filePath).Replace("super secret unguessable key", token); - File.WriteAllText(filePath, json); - return true; - } - catch (Exception) - { - return false; - } - } - #endregion - #region Port - public static bool UpdatePort(string filePath, int port) - { - if (new OsInfo(Array.Empty()).IsDocker) - { - return true; - } - - try - { - var currentPort = GetPort(filePath); - var json = File.ReadAllText(filePath).Replace("\"Port\": " + currentPort, "\"Port\": " + port); - File.WriteAllText(filePath, json); - return true; - } - catch (Exception) - { - return false; - } - } - public static int GetPort(string filePath) - { - const int defaultPort = 5000; - if (new OsInfo(Array.Empty()).IsDocker) - { - return defaultPort; - } - - try { - var json = File.ReadAllText(filePath); - var jsonObj = JsonSerializer.Deserialize(json); - const string key = "Port"; - - if (jsonObj.TryGetProperty(key, out JsonElement tokenElement)) - { - return tokenElement.GetInt32(); - } - } - catch (Exception ex) { - Console.WriteLine("Error writing app settings: " + ex.Message); - } + } + } + public static bool CheckIfJwtTokenSet() + { + //string filePath + try + { + return GetJwtToken(GetAppSettingFilename()) != "super secret unguessable key"; + } + catch (Exception ex) + { + Console.WriteLine("Error writing app settings: " + ex.Message); + } + + return false; + } + + public static bool UpdateJwtToken(string token) + { + try + { + var filePath = GetAppSettingFilename(); + var json = File.ReadAllText(filePath).Replace("super secret unguessable key", token); + File.WriteAllText(GetAppSettingFilename(), json); + return true; + } + catch (Exception) + { + return false; + } + } + + #endregion + + #region Port + + public static bool SetPort(string filePath, int port) + { + if (new OsInfo(Array.Empty()).IsDocker) + { + return true; + } + + try + { + var currentPort = GetPort(filePath); + var json = File.ReadAllText(filePath).Replace("\"Port\": " + currentPort, "\"Port\": " + port); + File.WriteAllText(filePath, json); + return true; + } + catch (Exception) + { + return false; + } + } + + public static int GetPort(string filePath) + { + Console.WriteLine(GetAppSettingFilename()); + const int defaultPort = 5000; + if (new OsInfo(Array.Empty()).IsDocker) + { return defaultPort; - } - #endregion - #region LogLevel - public static bool UpdateLogLevel(string filePath, string logLevel) - { - try - { - var currentLevel = GetLogLevel(filePath); - var json = File.ReadAllText(filePath).Replace($"\"Default\": \"{currentLevel}\"", $"\"Default\": \"{logLevel}\""); - File.WriteAllText(filePath, json); - return true; - } - catch (Exception) - { - return false; - } - } - public static string GetLogLevel(string filePath) - { - try { - var json = File.ReadAllText(filePath); - var jsonObj = JsonSerializer.Deserialize(json); - if (jsonObj.TryGetProperty("Logging", out JsonElement tokenElement)) - { - foreach (var property in tokenElement.EnumerateObject()) - { - if (!property.Name.Equals("LogLevel")) continue; - foreach (var logProperty in property.Value.EnumerateObject()) - { - if (logProperty.Name.Equals("Default")) - { - return logProperty.Value.GetString(); - } - } - } - } - } - catch (Exception ex) { - Console.WriteLine("Error writing app settings: " + ex.Message); - } + } - return "Information"; - } - #endregion - } -} \ No newline at end of file + try + { + var json = File.ReadAllText(filePath); + var jsonObj = JsonSerializer.Deserialize(json); + const string key = "Port"; + + if (jsonObj.TryGetProperty(key, out JsonElement tokenElement)) + { + return tokenElement.GetInt32(); + } + } + catch (Exception ex) + { + Console.WriteLine("Error writing app settings: " + ex.Message); + } + + return defaultPort; + } + + #endregion + + #region LogLevel + + public static bool SetLogLevel(string filePath, string logLevel) + { + try + { + var currentLevel = GetLogLevel(filePath); + var json = File.ReadAllText(filePath) + .Replace($"\"Default\": \"{currentLevel}\"", $"\"Default\": \"{logLevel}\""); + File.WriteAllText(filePath, json); + return true; + } + catch (Exception) + { + return false; + } + } + + public static string GetLogLevel(string filePath) + { + try + { + var json = File.ReadAllText(filePath); + var jsonObj = JsonSerializer.Deserialize(json); + if (jsonObj.TryGetProperty("Logging", out JsonElement tokenElement)) + { + foreach (var property in tokenElement.EnumerateObject()) + { + if (!property.Name.Equals("LogLevel")) continue; + foreach (var logProperty in property.Value.EnumerateObject()) + { + if (logProperty.Name.Equals("Default")) + { + return logProperty.Value.GetString(); + } + } + } + } + } + catch (Exception ex) + { + Console.WriteLine("Error writing app settings: " + ex.Message); + } + + return "Information"; + } + + #endregion + + public static string GetBranch(string filePath) + { + const string defaultBranch = "main"; + + try + { + var json = File.ReadAllText(filePath); + var jsonObj = JsonSerializer.Deserialize(json); + const string key = "Branch"; + + if (jsonObj.TryGetProperty(key, out JsonElement tokenElement)) + { + return tokenElement.GetString(); + } + } + catch (Exception ex) + { + Console.WriteLine("Error reading app settings: " + ex.Message); + } + + return defaultBranch; + } + + public static bool SetBranch(string filePath, string updatedBranch) + { + try + { + var currentBranch = GetBranch(filePath); + var json = File.ReadAllText(filePath) + .Replace("\"Branch\": " + currentBranch, "\"Branch\": " + updatedBranch); + File.WriteAllText(filePath, json); + return true; + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/Kavita.Common/HashUtil.cs b/Kavita.Common/HashUtil.cs index b83068f5f..0ae9efab8 100644 --- a/Kavita.Common/HashUtil.cs +++ b/Kavita.Common/HashUtil.cs @@ -34,7 +34,7 @@ namespace Kavita.Common /// public static string AnonymousToken() { - var seed = $"{Environment.ProcessorCount}_{Environment.OSVersion.Platform}_{Environment.MachineName}_{Environment.UserName}"; + var seed = $"{Environment.ProcessorCount}_{Environment.OSVersion.Platform}_{Configuration.JwtToken}_{Environment.UserName}"; return CalculateCrc(seed); } } diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index b19d8551f..b933425c0 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -10,6 +10,7 @@ + diff --git a/UI/Web/src/app/_modals/edit-series-modal/edit-series-modal.component.html b/UI/Web/src/app/_modals/edit-series-modal/edit-series-modal.component.html index 614880bb3..fe11d2451 100644 --- a/UI/Web/src/app/_modals/edit-series-modal/edit-series-modal.component.html +++ b/UI/Web/src/app/_modals/edit-series-modal/edit-series-modal.component.html @@ -160,7 +160,6 @@
diff --git a/UI/Web/src/app/_models/in-progress-chapter.ts b/UI/Web/src/app/_models/in-progress-chapter.ts index 1fd6133d2..57e75b0ed 100644 --- a/UI/Web/src/app/_models/in-progress-chapter.ts +++ b/UI/Web/src/app/_models/in-progress-chapter.ts @@ -1,4 +1,3 @@ -//TODO: Refactor this name to something better export interface InProgressChapter { id: number; range: string; diff --git a/UI/Web/src/app/_services/library.service.ts b/UI/Web/src/app/_services/library.service.ts index cfba02d99..d77871f91 100644 --- a/UI/Web/src/app/_services/library.service.ts +++ b/UI/Web/src/app/_services/library.service.ts @@ -14,7 +14,8 @@ export class LibraryService { baseUrl = environment.apiUrl; - libraryNames: {[key:number]: string} | undefined = undefined; + private libraryNames: {[key:number]: string} | undefined = undefined; + private libraryTypes: {[key: number]: LibraryType} | undefined = undefined; constructor(private httpClient: HttpClient) {} @@ -75,8 +76,17 @@ export class LibraryService { } getLibraryType(libraryId: number) { - // TODO: Cache this in browser - return this.httpClient.get(this.baseUrl + 'library/type?libraryId=' + libraryId); + if (this.libraryTypes != undefined && this.libraryTypes.hasOwnProperty(libraryId)) { + return of(this.libraryTypes[libraryId]); + } + return this.httpClient.get(this.baseUrl + 'library/type?libraryId=' + libraryId).pipe(map(l => { + if (this.libraryTypes === undefined) { + this.libraryTypes = {}; + } + + this.libraryTypes[libraryId] = l; + return this.libraryTypes[libraryId]; + })); } search(term: string) { diff --git a/UI/Web/src/app/admin/_modals/directory-picker/directory-picker.component.ts b/UI/Web/src/app/admin/_modals/directory-picker/directory-picker.component.ts index fc5a5d2ee..85fa530bb 100644 --- a/UI/Web/src/app/admin/_modals/directory-picker/directory-picker.component.ts +++ b/UI/Web/src/app/admin/_modals/directory-picker/directory-picker.component.ts @@ -42,7 +42,6 @@ export class DirectoryPickerComponent implements OnInit { } goBack() { - // BUG: When Going back to initial listing, this code gets stuck on first drive this.routeStack.pop(); const stackPeek = this.routeStack.peek(); if (stackPeek !== undefined) { @@ -53,7 +52,6 @@ export class DirectoryPickerComponent implements OnInit { this.currentRoot = ''; this.loadChildren(this.currentRoot); } - } loadChildren(path: string) { diff --git a/UI/Web/src/app/admin/_modals/edit-rbs-modal/edit-rbs-modal.component.ts b/UI/Web/src/app/admin/_modals/edit-rbs-modal/edit-rbs-modal.component.ts index ee659adf6..ff6346152 100644 --- a/UI/Web/src/app/admin/_modals/edit-rbs-modal/edit-rbs-modal.component.ts +++ b/UI/Web/src/app/admin/_modals/edit-rbs-modal/edit-rbs-modal.component.ts @@ -30,7 +30,7 @@ export class EditRbsModalComponent implements OnInit { } close() { - this.modal.close(false); + this.modal.close(undefined); } save() { @@ -42,8 +42,10 @@ export class EditRbsModalComponent implements OnInit { this.memberService.updateMemberRoles(this.member?.username, selectedRoles).subscribe(() => { if (this.member) { this.member.roles = selectedRoles; + this.modal.close(this.member); + return; } - this.modal.close(true); + this.modal.close(undefined); }); } diff --git a/UI/Web/src/app/admin/manage-library/manage-library.component.ts b/UI/Web/src/app/admin/manage-library/manage-library.component.ts index f0aae12e9..3825d25c8 100644 --- a/UI/Web/src/app/admin/manage-library/manage-library.component.ts +++ b/UI/Web/src/app/admin/manage-library/manage-library.component.ts @@ -69,7 +69,7 @@ export class ManageLibraryComponent implements OnInit, OnDestroy { this.libraryService.delete(library.id).pipe(take(1)).subscribe(() => { this.deletionInProgress = false; this.getLibraries(); - this.toastr.success('Library has been removed'); // BUG: This is not causing a refresh + this.toastr.success('Library has been removed'); }); } } diff --git a/UI/Web/src/app/admin/manage-settings/manage-settings.component.html b/UI/Web/src/app/admin/manage-settings/manage-settings.component.html index c3a94bd0e..62cd28a89 100644 --- a/UI/Web/src/app/admin/manage-settings/manage-settings.component.html +++ b/UI/Web/src/app/admin/manage-settings/manage-settings.component.html @@ -25,18 +25,14 @@
-   - Send anonymous usage and error information to Kavita's servers. This includes information on your browser, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes. Requires restart to take effect. - Send anonymous usage and error information to Kavita's servers. This includes information on your browser, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes. Requires restart to take effect. -

Send anonymous usage and error information to Kavita's servers. This includes information on your browser, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes. Requires restart to take effect

+ +

Send anonymous usage and error information to Kavita's servers. This includes information on your browser, error reporting as well as OS and runtime version. We will use this information to prioritize features, bug fixes, and preformance tuning. Requires restart to take effect.

- -

Reoccuring Tasks

  diff --git a/UI/Web/src/app/admin/manage-users/manage-users.component.ts b/UI/Web/src/app/admin/manage-users/manage-users.component.ts index 402cde3f2..15078cce1 100644 --- a/UI/Web/src/app/admin/manage-users/manage-users.component.ts +++ b/UI/Web/src/app/admin/manage-users/manage-users.component.ts @@ -82,6 +82,11 @@ export class ManageUsersComponent implements OnInit { openEditRole(member: Member) { const modalRef = this.modalService.open(EditRbsModalComponent); modalRef.componentInstance.member = member; + modalRef.closed.subscribe((updatedMember: Member) => { + if (updatedMember !== undefined) { + member = updatedMember; + } + }) } updatePassword(member: Member) { diff --git a/UI/Web/src/app/app.module.ts b/UI/Web/src/app/app.module.ts index cfb9f8084..b1d06d049 100644 --- a/UI/Web/src/app/app.module.ts +++ b/UI/Web/src/app/app.module.ts @@ -37,6 +37,8 @@ import { TypeaheadModule } from './typeahead/typeahead.module'; import { AllCollectionsComponent } from './all-collections/all-collections.component'; import { EditCollectionTagsComponent } from './_modals/edit-collection-tags/edit-collection-tags.component'; import { RecentlyAddedComponent } from './recently-added/recently-added.component'; +import { LibraryCardComponent } from './library-card/library-card.component'; +import { SeriesCardComponent } from './series-card/series-card.component'; let sentryProviders: any[] = []; @@ -100,6 +102,8 @@ if (environment.production) { AllCollectionsComponent, EditCollectionTagsComponent, RecentlyAddedComponent, + LibraryCardComponent, + SeriesCardComponent ], imports: [ HttpClientModule, diff --git a/UI/Web/src/app/shared/library-card/library-card.component.html b/UI/Web/src/app/library-card/library-card.component.html similarity index 100% rename from UI/Web/src/app/shared/library-card/library-card.component.html rename to UI/Web/src/app/library-card/library-card.component.html diff --git a/UI/Web/src/app/shared/library-card/library-card.component.scss b/UI/Web/src/app/library-card/library-card.component.scss similarity index 100% rename from UI/Web/src/app/shared/library-card/library-card.component.scss rename to UI/Web/src/app/library-card/library-card.component.scss diff --git a/UI/Web/src/app/shared/library-card/library-card.component.ts b/UI/Web/src/app/library-card/library-card.component.ts similarity index 100% rename from UI/Web/src/app/shared/library-card/library-card.component.ts rename to UI/Web/src/app/library-card/library-card.component.ts diff --git a/UI/Web/src/app/manga-reader/manga-reader.component.html b/UI/Web/src/app/manga-reader/manga-reader.component.html index 1a0cb3f61..9b44da93b 100644 --- a/UI/Web/src/app/manga-reader/manga-reader.component.html +++ b/UI/Web/src/app/manga-reader/manga-reader.component.html @@ -27,7 +27,7 @@
- +
diff --git a/UI/Web/src/app/shared/register-member/register-member.component.html b/UI/Web/src/app/register-member/register-member.component.html similarity index 100% rename from UI/Web/src/app/shared/register-member/register-member.component.html rename to UI/Web/src/app/register-member/register-member.component.html diff --git a/UI/Web/src/app/shared/register-member/register-member.component.scss b/UI/Web/src/app/register-member/register-member.component.scss similarity index 100% rename from UI/Web/src/app/shared/register-member/register-member.component.scss rename to UI/Web/src/app/register-member/register-member.component.scss diff --git a/UI/Web/src/app/shared/register-member/register-member.component.ts b/UI/Web/src/app/register-member/register-member.component.ts similarity index 100% rename from UI/Web/src/app/shared/register-member/register-member.component.ts rename to UI/Web/src/app/register-member/register-member.component.ts diff --git a/UI/Web/src/app/shared/series-card/series-card.component.html b/UI/Web/src/app/series-card/series-card.component.html similarity index 100% rename from UI/Web/src/app/shared/series-card/series-card.component.html rename to UI/Web/src/app/series-card/series-card.component.html diff --git a/UI/Web/src/app/shared/series-card/series-card.component.scss b/UI/Web/src/app/series-card/series-card.component.scss similarity index 100% rename from UI/Web/src/app/shared/series-card/series-card.component.scss rename to UI/Web/src/app/series-card/series-card.component.scss diff --git a/UI/Web/src/app/shared/series-card/series-card.component.ts b/UI/Web/src/app/series-card/series-card.component.ts similarity index 92% rename from UI/Web/src/app/shared/series-card/series-card.component.ts rename to UI/Web/src/app/series-card/series-card.component.ts index 115a1bbc5..5eccd3149 100644 --- a/UI/Web/src/app/shared/series-card/series-card.component.ts +++ b/UI/Web/src/app/series-card/series-card.component.ts @@ -7,10 +7,9 @@ import { EditSeriesModalComponent } from 'src/app/_modals/edit-series-modal/edit import { Series } from 'src/app/_models/series'; import { AccountService } from 'src/app/_services/account.service'; import { ImageService } from 'src/app/_services/image.service'; -import { LibraryService } from 'src/app/_services/library.service'; import { ActionFactoryService, Action, ActionItem } from 'src/app/_services/action-factory.service'; import { SeriesService } from 'src/app/_services/series.service'; -import { ConfirmService } from '../confirm.service'; +import { ConfirmService } from '../shared/confirm.service'; @Component({ selector: 'app-series-card', @@ -30,9 +29,8 @@ export class SeriesCardComponent implements OnInit, OnChanges { constructor(private accountService: AccountService, private router: Router, private seriesService: SeriesService, private toastr: ToastrService, - private libraryService: LibraryService, private modalService: NgbModal, - private confirmService: ConfirmService, public imageService: ImageService, - private actionFactoryService: ActionFactoryService) { + private modalService: NgbModal, private confirmService: ConfirmService, + public imageService: ImageService, private actionFactoryService: ActionFactoryService) { this.accountService.currentUser$.pipe(take(1)).subscribe(user => { if (user) { this.isAdmin = this.accountService.hasAdminRole(user); diff --git a/UI/Web/src/app/shared/shared.module.ts b/UI/Web/src/app/shared/shared.module.ts index 2d257faba..0623a9f37 100644 --- a/UI/Web/src/app/shared/shared.module.ts +++ b/UI/Web/src/app/shared/shared.module.ts @@ -3,14 +3,12 @@ import { CommonModule } from '@angular/common'; import { ReactiveFormsModule } from '@angular/forms'; import { CardItemComponent } from './card-item/card-item.component'; import { NgbCollapseModule, NgbDropdownModule, NgbPaginationModule, NgbProgressbarModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; -import { LibraryCardComponent } from './library-card/library-card.component'; -import { SeriesCardComponent } from './series-card/series-card.component'; import { CardDetailsModalComponent } from './_modals/card-details-modal/card-details-modal.component'; import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component'; import { SafeHtmlPipe } from './safe-html.pipe'; import { LazyLoadImageModule } from 'ng-lazyload-image'; import { CardActionablesComponent } from './card-item/card-actionables/card-actionables.component'; -import { RegisterMemberComponent } from './register-member/register-member.component'; +import { RegisterMemberComponent } from '../register-member/register-member.component'; import { ReadMoreComponent } from './read-more/read-more.component'; import { RouterModule } from '@angular/router'; import { DrawerComponent } from './drawer/drawer.component'; @@ -24,8 +22,6 @@ import { A11yClickDirective } from './a11y-click.directive'; declarations: [ RegisterMemberComponent, CardItemComponent, - LibraryCardComponent, - SeriesCardComponent, CardDetailsModalComponent, ConfirmDialogComponent, SafeHtmlPipe, @@ -49,10 +45,8 @@ import { A11yClickDirective } from './a11y-click.directive'; NgbPaginationModule // CardDetailLayoutComponent ], exports: [ - RegisterMemberComponent, // TODO: Move this out and put in normal app + RegisterMemberComponent, CardItemComponent, - LibraryCardComponent, // TODO: Move this out and put in normal app - SeriesCardComponent, // TODO: Move this out and put in normal app SafeHtmlPipe, CardActionablesComponent, ReadMoreComponent, diff --git a/UI/Web/src/app/typeahead/typeahead.component.ts b/UI/Web/src/app/typeahead/typeahead.component.ts index c106cefe8..3ebf81280 100644 --- a/UI/Web/src/app/typeahead/typeahead.component.ts +++ b/UI/Web/src/app/typeahead/typeahead.component.ts @@ -27,13 +27,6 @@ export class SelectionModel { }); } - // __lookupItem(item: T) { - // if (this._propAccessor != '') { - // // TODO: Implement this code to speedup lookups (use a map rather than array) - // } - // const dataItem = this._data.filter(data => data.value == d); - // } - /** * Will toggle if the data item is selected or not. If data option is not tracked, will add it and set state to true. * @param data Item to toggle