From b8622e5943b4316fea0ba0f82ebac8287659d4bc Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 3 Jan 2021 14:02:48 +0300 Subject: [PATCH 01/51] Order users by display name --- Oqtane.Client/Modules/Admin/Roles/Users.razor | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Roles/Users.razor b/Oqtane.Client/Modules/Admin/Roles/Users.razor index 9ceae846..3fb2bb95 100644 --- a/Oqtane.Client/Modules/Admin/Roles/Users.razor +++ b/Oqtane.Client/Modules/Admin/Roles/Users.razor @@ -89,7 +89,10 @@ else Role role = await RoleService.GetRoleAsync(roleid); name = role.Name; users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId); - users = users.Where(item => item.Role.Name == RoleNames.Registered).ToList(); + users = users + .Where(u => u.Role.Name == RoleNames.Registered) + .OrderBy(u => u.User.DisplayName) + .ToList(); await GetUserRoles(); } catch (Exception ex) From c86a8cbd2d8a8d1bf9f03569cf64c643d374c577 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 5 Jan 2021 03:02:02 +0300 Subject: [PATCH 02/51] Fix ResourceKey property --- Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor index 367abce8..70310bd4 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor @@ -60,7 +60,7 @@ - + From a61a2f748cc98d1da5a878a18586dd851fe472a3 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 5 Jan 2021 09:00:42 -0500 Subject: [PATCH 03/51] fixed issue with Sql Management and System Info missing icons after new installation --- Oqtane.Server/Repository/SiteRepository.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 0130e5e3..d8df82b6 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -525,12 +525,7 @@ namespace Oqtane.Repository }); pageTemplates.Add(new PageTemplate { - Name = "Sql Management", - Parent = "Admin", - Path = "admin/sql", - Icon = "spreadsheet", - IsNavigation = false, - IsPersonalizable = false, + Name = "Sql Management", Parent = "Admin", Path = "admin/sql", Icon = Icons.Spreadsheet, IsNavigation = false, IsPersonalizable = false, PagePermissions = new List { new Permission(PermissionNames.View, RoleNames.Host, true), @@ -552,12 +547,7 @@ namespace Oqtane.Repository }); pageTemplates.Add(new PageTemplate { - Name = "System Info", - Parent = "Admin", - Path = "admin/system", - Icon = "medical-cross", - IsNavigation = false, - IsPersonalizable = false, + Name = "System Info", Parent = "Admin", Path = "admin/system", Icon = Icons.MedicalCross, IsNavigation = false, IsPersonalizable = false, PagePermissions = new List { new Permission(PermissionNames.View, RoleNames.Host, true), From 91c5ff7b007c73889918035faf0a46041bc3b3f2 Mon Sep 17 00:00:00 2001 From: Pavel Vesely Date: Tue, 5 Jan 2021 19:52:14 +0100 Subject: [PATCH 04/51] UpdateSettings bugfix ISettingControl introduction --- .../Modules/Admin/Modules/Settings.razor | 18 ++++++++++-------- Oqtane.Shared/Interfaces/ISettingsControl.cs | 9 +++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 Oqtane.Shared/Interfaces/ISettingsControl.cs diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index a4bd4188..c6ebe5c3 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -1,4 +1,5 @@ @namespace Oqtane.Modules.Admin.Modules +@using Oqtane.Interfaces @inherits ModuleBase @inject NavigationManager NavigationManager @inject IThemeService ThemeService @@ -137,7 +138,7 @@ builder.AddComponentReferenceCapture(1, inst => { _settings = Convert.ChangeType(inst, _settingsModuleType); }); builder.CloseComponent(); }; - } + } } private async Task SaveModule() @@ -162,15 +163,16 @@ module.Permissions = _permissionGrid.GetPermissions(); await ModuleService.UpdateModuleAsync(module); - if (_settingsModuleType != null) + + if (_settings is ISettingsControl control) { - var moduleType = Type.GetType(ModuleState.ModuleType); - if (moduleType != null) - { - moduleType.GetMethod("UpdateSettings")?.Invoke(_settings, null); // method must be public in settings component - } + await control.UpdateSettings(); + } + else + { + // Compatibility 2.0 fallback + _settings?.GetType().GetMethod("UpdateSettings")?.Invoke(_settings, null); // method must be public in settings component } - NavigationManager.NavigateTo(NavigateUrl()); } diff --git a/Oqtane.Shared/Interfaces/ISettingsControl.cs b/Oqtane.Shared/Interfaces/ISettingsControl.cs new file mode 100644 index 00000000..c5753099 --- /dev/null +++ b/Oqtane.Shared/Interfaces/ISettingsControl.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Oqtane.Interfaces +{ + public interface ISettingsControl + { + Task UpdateSettings(); + } +} From de25e3fbf1ed31f2e6d3fe28ce79c69fd0ceca14 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 5 Jan 2021 14:47:09 -0500 Subject: [PATCH 05/51] fix navigation usability issue for shared add/edit page UI invoked by Control Panel and Page Management --- Oqtane.Client/Modules/Admin/Pages/Add.razor | 23 +++++++++++++++++-- Oqtane.Client/Modules/Admin/Pages/Edit.razor | 23 +++++++++++++++++-- .../Themes/Controls/ControlPanel.razor | 4 ++-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index 2a253ed1..171ab885 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -189,7 +189,7 @@ -@Localizer["Cancel"] + @code { private List _themeList; @@ -386,7 +386,14 @@ await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId); await logger.LogInformation("Page Added {Page}", page); - NavigationManager.NavigateTo(NavigateUrl(page.Path)); + if (PageState.QueryString.ContainsKey("cp")) + { + NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path)); + } + else + { + NavigationManager.NavigateTo(NavigateUrl(page.Path)); + } } else { @@ -401,6 +408,18 @@ } } + private void Cancel() + { + if (PageState.QueryString.ContainsKey("cp")) + { + NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path)); + } + else + { + NavigationManager.NavigateTo(NavigateUrl()); + } + } + private static bool PagePathIsUnique(string pagePath, int siteId, List existingPages) { return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath); diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 4a99881c..5efdd992 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -205,7 +205,7 @@ -@Localizer["Cancel"] + @code { private List _themeList; @@ -493,7 +493,14 @@ } await logger.LogInformation("Page Saved {Page}", page); - NavigationManager.NavigateTo(NavigateUrl(page.Path)); + if (PageState.QueryString.ContainsKey("cp")) + { + NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path)); + } + else + { + NavigationManager.NavigateTo(NavigateUrl(page.Path)); + } } else { @@ -507,6 +514,18 @@ } } + private void Cancel() + { + if (PageState.QueryString.ContainsKey("cp")) + { + NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path)); + } + else + { + NavigationManager.NavigateTo(NavigateUrl()); + } + } + private static bool PagePathIsUnique(string pagePath, int siteId, int pageId, List existingPages) { return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.PageId != pageId); diff --git a/Oqtane.Client/Themes/Controls/ControlPanel.razor b/Oqtane.Client/Themes/Controls/ControlPanel.razor index 931f3ff3..eee04121 100644 --- a/Oqtane.Client/Themes/Controls/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/ControlPanel.razor @@ -485,10 +485,10 @@ switch (location) { case "Add": - url = EditUrl(PageState.Page.Path, module.ModuleId, location, ""); + url = EditUrl(PageState.Page.Path, module.ModuleId, location, "cp=" + PageState.Page.PageId); break; case "Edit": - url = EditUrl(PageState.Page.Path, module.ModuleId, location, "id=" + PageState.Page.PageId.ToString()); + url = EditUrl(PageState.Page.Path, module.ModuleId, location, "id=" + PageState.Page.PageId.ToString() + "&cp=" + PageState.Page.PageId); break; } } From 778f9cb356d7c3df7bb974fcf321956b3b9db9af Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 5 Jan 2021 16:57:36 -0500 Subject: [PATCH 06/51] added better validaton and user feedback related to SMTP configuration --- Oqtane.Client/Modules/Admin/Site/Index.razor | 29 ++++++++++++------- .../Infrastructure/Jobs/NotificationJob.cs | 9 ++++-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 3910ab9b..ddf17450 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -129,9 +129,14 @@
+ + +
+ @Localizer["Please Note That SMTP Requires The Notification Job To Be Enabled In the Scheduled Jobs"] +
- + @@ -139,7 +144,7 @@
- + @@ -147,15 +152,18 @@
- + - +
- + @@ -163,7 +171,7 @@
- + @@ -205,7 +213,6 @@
- @Localizer["Cancel"]

@@ -232,7 +239,7 @@ private string _allowregistration; private string _smtphost = string.Empty; private string _smtpport = string.Empty; - private string _smtpssl = string.Empty; + private string _smtpssl = "False"; private string _smtpusername = string.Empty; private string _smtppassword = string.Empty; private string _pwaisenabled; @@ -287,7 +294,7 @@ var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); _smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty); _smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty); - _smtpssl = SettingService.GetSetting(settings, "SMTPSSL", string.Empty); + _smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False"); _smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty); _smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty); @@ -437,9 +444,9 @@ SettingService.SetSetting(settings, "SMTPPassword", _smtppassword); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); - await logger.LogInformation("Site Saved {Site}", site); + await logger.LogInformation("Site Settings Saved {Site}", site); - NavigationManager.NavigateTo(NavigateUrl()); + AddModuleMessage(Localizer["Site Settings Saved"], MessageType.Success); } } else diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 2e18950f..404829d7 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -47,7 +47,10 @@ namespace Oqtane.Infrastructure // get site settings List sitesettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList(); Dictionary settings = GetSettings(sitesettings); - if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "") + if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && + settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" && + settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" && + settings.ContainsKey("SMTPUsername") && settings["SMTPUsername"] != "") { // construct SMTP Client var client = new SmtpClient() @@ -112,7 +115,7 @@ namespace Oqtane.Infrastructure } else { - log += "SMTP Not Configured" + "
"; + log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Username Are All Required" + "
"; } } } From e3e5f782aaae890aa7011e91d4038eb7ea1e1222 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 5 Jan 2021 17:11:45 -0500 Subject: [PATCH 07/51] support for shared razor class library static resources in external module template --- .../Modules/Templates/External/Package/debug.cmd | 2 +- .../External/Server/[Owner].[Module].Server.csproj | 5 +++++ .../External/Server/wwwroot/_content/Placeholder.txt | 11 +++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/_content/Placeholder.txt diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd index c1971223..2137d929 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd @@ -4,4 +4,4 @@ XCOPY "..\Server\bin\Debug\net5.0\[Owner].[Module].Server.Oqtane.dll" "..\..\[Ro XCOPY "..\Server\bin\Debug\net5.0\[Owner].[Module].Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net5.0\" /Y XCOPY "..\Shared\bin\Debug\net5.0\[Owner].[Module].Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net5.0\" /Y XCOPY "..\Shared\bin\Debug\net5.0\[Owner].[Module].Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net5.0\" /Y -XCOPY "..\Server\wwwroot\Modules\[Owner].[Module]\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\Modules\[Owner].[Module]\" /Y /S /I +XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].[Module].Server.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].[Module].Server.csproj index 2a2a8a2f..cfce1a60 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].[Module].Server.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].[Module].Server.csproj @@ -19,6 +19,11 @@ + + + + + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/_content/Placeholder.txt b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/_content/Placeholder.txt new file mode 100644 index 00000000..5a324d79 --- /dev/null +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/_content/Placeholder.txt @@ -0,0 +1,11 @@ +The _content folder should only contain static resources from shared razor component libraries (RCLs). Static resources can be extracted from shared RCL Nuget packages by executing a Publish task on the module's Server project to a local folder and copying the files from the _content folder which is created. Each shared RCL would have its own appropriately named subfolder within the module's _content folder. + +ie. + +/_content + /Radzen.Blazor + /css + /fonts + /syncfusion.blazor + /scripts + /styles From 1276c0269e303ec3ef2d904fc2d6b4448855bd3f Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 7 Jan 2021 15:06:48 -0500 Subject: [PATCH 08/51] add SMTP sender email --- Oqtane.Client/Modules/Admin/Site/Index.razor | 15 +++++++++++++-- .../Infrastructure/Jobs/NotificationJob.cs | 6 +++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index ddf17450..63e58889 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -127,7 +127,7 @@
-
+
+ + + +
@@ -163,7 +163,7 @@
- + @@ -177,6 +177,14 @@
+ + + +
@@ -242,6 +250,7 @@ private string _smtpssl = "False"; private string _smtpusername = string.Empty; private string _smtppassword = string.Empty; + private string _smtpsender = string.Empty; private string _pwaisenabled; private int _pwaappiconfileid = -1; private FileManager _pwaappiconfilemanager; @@ -297,6 +306,7 @@ _smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False"); _smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty); _smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty); + _smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty); _pwaisenabled = site.PwaIsEnabled.ToString(); @@ -442,6 +452,7 @@ SettingService.SetSetting(settings, "SMTPSSL", _smtpssl); SettingService.SetSetting(settings, "SMTPUsername", _smtpusername); SettingService.SetSetting(settings, "SMTPPassword", _smtppassword); + SettingService.SetSetting(settings, "SMTPSender", _smtpsender); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await logger.LogInformation("Site Settings Saved {Site}", site); diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 404829d7..deafe30a 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -50,7 +50,7 @@ namespace Oqtane.Infrastructure if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" && settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" && - settings.ContainsKey("SMTPUsername") && settings["SMTPUsername"] != "") + settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "") { // construct SMTP Client var client = new SmtpClient() @@ -72,7 +72,7 @@ namespace Oqtane.Infrastructure foreach (Notification notification in notifications) { MailMessage mailMessage = new MailMessage(); - mailMessage.From = new MailAddress(settings["SMTPUsername"], site.Name); + mailMessage.From = new MailAddress(settings["SMTPSender"], site.Name); mailMessage.Subject = notification.Subject; if (notification.FromUserId != null) { @@ -115,7 +115,7 @@ namespace Oqtane.Infrastructure } else { - log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Username Are All Required" + "
"; + log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "
"; } } } From aa19a3583430ba6814ec89bbade07810b69157ec Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 10 Jan 2021 20:09:04 +0300 Subject: [PATCH 09/51] Add language repository & controller --- .../Controllers/LanguageController.cs | 52 +++++++++++++++++++ .../Repository/Context/TenantDBContext.cs | 4 +- .../Interfaces/ILanguageRepository.cs | 16 ++++++ .../Repository/LanguageRepository.cs | 35 +++++++++++++ Oqtane.Server/Startup.cs | 1 + Oqtane.Shared/Models/Language.cs | 25 +++++++++ 6 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 Oqtane.Server/Controllers/LanguageController.cs create mode 100644 Oqtane.Server/Repository/Interfaces/ILanguageRepository.cs create mode 100644 Oqtane.Server/Repository/LanguageRepository.cs create mode 100644 Oqtane.Shared/Models/Language.cs diff --git a/Oqtane.Server/Controllers/LanguageController.cs b/Oqtane.Server/Controllers/LanguageController.cs new file mode 100644 index 00000000..b2a38ee5 --- /dev/null +++ b/Oqtane.Server/Controllers/LanguageController.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Oqtane.Enums; +using Oqtane.Infrastructure; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Shared; + +namespace Oqtane.Controllers +{ + [Route(ControllerRoutes.Default)] + public class LanguageController : Controller + { + private readonly ILanguageRepository _languages; + private readonly ILogManager _logger; + + public LanguageController(ILanguageRepository language, ILogManager logger) + { + _languages = roles; + _logger = language; + } + + [HttpGet] + [Authorize(Roles = RoleNames.Registered)] + public IEnumerable Get(string siteid) => _languages.GetLanguages(int.Parse(siteid)); + + [HttpGet("{id}")] + [Authorize(Roles = RoleNames.Registered)] + public Language Get(int id) => _languages.GetLanguage(id); + + [HttpPost] + [Authorize(Roles = RoleNames.Admin)] + public Language Post([FromBody] Language language) + { + if (ModelState.IsValid) + { + language = _languages.AddLanguage(language); + _logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", role); + } + return role; + } + + [HttpDelete("{id}")] + [Authorize(Roles = RoleNames.Admin)] + public void Delete(int id) + { + _languages.DeleteLanguage(id); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id); + } + } +} diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index be085b20..79270ffb 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Oqtane.Models; @@ -21,6 +21,8 @@ namespace Oqtane.Repository public virtual DbSet Folder { get; set; } public virtual DbSet File { get; set; } + public virtual DbSet Languages { get; set; } + public TenantDBContext(ITenantResolver tenantResolver, IHttpContextAccessor accessor) : base(tenantResolver, accessor) { // DBContextBase handles multi-tenant database connections diff --git a/Oqtane.Server/Repository/Interfaces/ILanguageRepository.cs b/Oqtane.Server/Repository/Interfaces/ILanguageRepository.cs new file mode 100644 index 00000000..0ec10442 --- /dev/null +++ b/Oqtane.Server/Repository/Interfaces/ILanguageRepository.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public interface ILanguageRepository + { + IEnumerable GetLanguages(int siteId); + + Language AddLanguage(Language language); + + Language GetLanguage(int languageId); + + void DeleteLanguage(int languageId); + } +} diff --git a/Oqtane.Server/Repository/LanguageRepository.cs b/Oqtane.Server/Repository/LanguageRepository.cs new file mode 100644 index 00000000..2f59a047 --- /dev/null +++ b/Oqtane.Server/Repository/LanguageRepository.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Linq; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public class LanguageRepository : ILanguageRepository + { + private TenantDBContext _db; + + public LanguageRepository(TenantDBContext context) + { + _db = context; + } + + public IEnumerable GetLanguages(int siteId) => _db.Languages.Where(l => l.SiteId == siteId); + + public Language AddLanguage(Language language) + { + _db.Languages.Add(language); + _db.SaveChanges(); + + return language; + } + + public Language GetLanguage(int languageId) => _db.Languages.Find(languageId); + + public void DeleteLanguage(int languageId) + { + var language = _db.Languages.Find(languageId); + _db.Languages.Remove(language); + _db.SaveChanges(); + } + } +} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index b134a19e..b4e9569d 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -213,6 +213,7 @@ namespace Oqtane services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // load the external assemblies into the app domain, install services services.AddOqtane(_runtime, _supportedCultures); diff --git a/Oqtane.Shared/Models/Language.cs b/Oqtane.Shared/Models/Language.cs new file mode 100644 index 00000000..2433d0d0 --- /dev/null +++ b/Oqtane.Shared/Models/Language.cs @@ -0,0 +1,25 @@ +using System; + +namespace Oqtane.Models +{ + public class Language : IAuditable + { + public int LanguageId { get; set; } + + public int? SiteId { get; set; } + + public string Name { get; set; } + + public string Code { get; set; } + + public bool IsCurrent { get; set; } + + public string CreatedBy { get; set; } + + public DateTime CreatedOn { get; set; } + + public string ModifiedBy { get; set; } + + public DateTime ModifiedOn { get; set; } + } +} From 3059e8c763d87740f38262cf5f3896e0afffdb50 Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 10 Jan 2021 20:17:35 +0300 Subject: [PATCH 10/51] Add language service --- Oqtane.Client/Program.cs | 1 + .../Services/Interfaces/ILanguageService.cs | 17 +++++++++ Oqtane.Client/Services/LanguageService.cs | 38 +++++++++++++++++++ Oqtane.Server/Startup.cs | 1 + 4 files changed, 57 insertions(+) create mode 100644 Oqtane.Client/Services/Interfaces/ILanguageService.cs create mode 100644 Oqtane.Client/Services/LanguageService.cs diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index 4c2d1201..558b8290 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -67,6 +67,7 @@ namespace Oqtane.Client builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); await LoadClientAssemblies(httpClient); diff --git a/Oqtane.Client/Services/Interfaces/ILanguageService.cs b/Oqtane.Client/Services/Interfaces/ILanguageService.cs new file mode 100644 index 00000000..82fbcc78 --- /dev/null +++ b/Oqtane.Client/Services/Interfaces/ILanguageService.cs @@ -0,0 +1,17 @@ +using Oqtane.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Oqtane.Services +{ + public interface ILanguageService + { + Task> GetLanguagesAsync(int siteId); + + Task GetLanguageAsync(int languageId); + + Task AddLanguageAsync(Language language); + + Task DeleteLanguageAsync(int languageId); + } +} diff --git a/Oqtane.Client/Services/LanguageService.cs b/Oqtane.Client/Services/LanguageService.cs new file mode 100644 index 00000000..432a2312 --- /dev/null +++ b/Oqtane.Client/Services/LanguageService.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + public class LanguageService : ServiceBase, ILanguageService + { + + private readonly SiteState _siteState; + + public LanguageService(HttpClient http, SiteState siteState) : base(http) + { + _siteState = siteState; + } + + private string Apiurl => CreateApiUrl(_siteState.Alias, "Language"); + + public async Task> GetLanguagesAsync(int siteId) + { + var languages = await GetJsonAsync>($"{Apiurl}?siteid={siteId}"); + + return languages.OrderBy(l => l.Name).ToList(); + } + + public async Task GetLanguageAsync(int languageId) + => await GetJsonAsync($"{Apiurl}/{languageId}"); + + public async Task AddRoleAsync(Language language) + => await PostJsonAsync(Apiurl, language); + + public async Task DeleteLanguageAsync(int languageId) + => await DeleteAsync($"{Apiurl}/{languageId}"); + } +} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index b4e9569d..0353bc03 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -128,6 +128,7 @@ namespace Oqtane services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddSingleton(); From 9b4316d6cd66d7b09e66f23b3afabab7d716acfd Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 10 Jan 2021 21:32:11 +0300 Subject: [PATCH 11/51] Fix errors --- Oqtane.Client/Services/LanguageService.cs | 2 +- Oqtane.Server/Controllers/LanguageController.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Services/LanguageService.cs b/Oqtane.Client/Services/LanguageService.cs index 432a2312..da1e0342 100644 --- a/Oqtane.Client/Services/LanguageService.cs +++ b/Oqtane.Client/Services/LanguageService.cs @@ -29,7 +29,7 @@ namespace Oqtane.Services public async Task GetLanguageAsync(int languageId) => await GetJsonAsync($"{Apiurl}/{languageId}"); - public async Task AddRoleAsync(Language language) + public async Task AddLanguageAsync(Language language) => await PostJsonAsync(Apiurl, language); public async Task DeleteLanguageAsync(int languageId) diff --git a/Oqtane.Server/Controllers/LanguageController.cs b/Oqtane.Server/Controllers/LanguageController.cs index b2a38ee5..2b8f7cf0 100644 --- a/Oqtane.Server/Controllers/LanguageController.cs +++ b/Oqtane.Server/Controllers/LanguageController.cs @@ -17,8 +17,8 @@ namespace Oqtane.Controllers public LanguageController(ILanguageRepository language, ILogManager logger) { - _languages = roles; - _logger = language; + _languages = language; + _logger = logger; } [HttpGet] @@ -36,9 +36,9 @@ namespace Oqtane.Controllers if (ModelState.IsValid) { language = _languages.AddLanguage(language); - _logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", role); + _logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language); } - return role; + return language; } [HttpDelete("{id}")] From 21e09d95da17aaaf297b52e68179bf3451b4cd32 Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 10 Jan 2021 21:32:25 +0300 Subject: [PATCH 12/51] Add migration script --- Oqtane.Server/Scripts/Tenant.02.00.02.00.sql | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Oqtane.Server/Scripts/Tenant.02.00.02.00.sql diff --git a/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql b/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql new file mode 100644 index 00000000..8eb6eef3 --- /dev/null +++ b/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql @@ -0,0 +1,26 @@ +/* + +Version 2.0.0 Tenant migration script + +*/ + +CREATE TABLE [dbo].[Language]( + [LanguageId] [int] IDENTITY(1,1) NOT NULL, + [Name] [nvarchar](100) NOT NULL, + [Code] [nvarchar](10) NOT NULL, + [TenantId] [int], + [CreatedBy] [nvarchar](256) NOT NULL, + [CreatedOn] [datetime] NOT NULL, + [ModifiedBy] [nvarchar](256) NOT NULL, + [ModifiedOn] [datetime] NOT NULL, + CONSTRAINT [PK_Language] PRIMARY KEY CLUSTERED + ( + [LanguageId] ASC + ) +) +GO + +ALTER TABLE [dbo].[Language] WITH CHECK ADD CONSTRAINT [FK_Language_Tenant] FOREIGN KEY([TenantId]) +REFERENCES [dbo].[Tenant] ([TenantId]) +ON DELETE CASCADE +GO \ No newline at end of file From 91a844c9101b726a9bb73b75ea64fe6067417948 Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 10 Jan 2021 23:17:03 +0300 Subject: [PATCH 13/51] Add language management page template --- .../Modules/Admin/Languages/Index.razor | 54 +++++++++++++++++++ Oqtane.Server/Repository/SiteRepository.cs | 27 ++++++++++ Oqtane.Server/Scripts/Tenant.02.00.02.00.sql | 1 + 3 files changed, 82 insertions(+) create mode 100644 Oqtane.Client/Modules/Admin/Languages/Index.razor diff --git a/Oqtane.Client/Modules/Admin/Languages/Index.razor b/Oqtane.Client/Modules/Admin/Languages/Index.razor new file mode 100644 index 00000000..c45a8bc2 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/Languages/Index.razor @@ -0,0 +1,54 @@ +@namespace Oqtane.Modules.Admin.Languages +@inherits ModuleBase +@inject ILanguageService LanguageService +@inject IStringLocalizer Localizer + +@if (_languages == null) +{ +

@Localizer["Loading..."]

+} +else +{ + + + +
+   + @Localizer["Name"] + @Localizer["Code"] +
+ + + @context.Name + @context.Code + +
+} + +@code { + private List _languages; + + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + + protected override async Task OnParametersSetAsync() + { + _languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId); + } + + private async Task DeleteLanguage(Language language) + { + try + { + await LanguageService.DeleteLanguageAsync(language.LanguageId); + await logger.LogInformation("Language Deleted {Language}", language); + + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message); + + AddModuleMessage(Localizer["Error Deleting Language"], MessageType.Error); + } + } +} diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index d8df82b6..64791442 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -502,6 +502,33 @@ namespace Oqtane.Repository } }); pageTemplates.Add(new PageTemplate + { + Name = "Language Management", + Parent = "Admin", + Path = "admin/languages", + Icon = Icons.Text, + IsNavigation = false, + IsPersonalizable = false, + PagePermissions = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }.EncodePermissions(), + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = "Content", + ModulePermissions = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }.EncodePermissions(), + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate { Name = "Scheduled Jobs", Parent = "Admin", Path = "admin/jobs", Icon = Icons.Timer, IsNavigation = false, IsPersonalizable = false, PagePermissions = new List diff --git a/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql b/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql index 8eb6eef3..a52e8022 100644 --- a/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql +++ b/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql @@ -8,6 +8,7 @@ CREATE TABLE [dbo].[Language]( [LanguageId] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](100) NOT NULL, [Code] [nvarchar](10) NOT NULL, + [IsCurrent] [bit] NOT NULL, [TenantId] [int], [CreatedBy] [nvarchar](256) NOT NULL, [CreatedOn] [datetime] NOT NULL, From 8ab511fda773aad2ed4958485798ae891a7355ae Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 10 Jan 2021 23:17:35 +0300 Subject: [PATCH 14/51] Return empty list if languages list are null --- Oqtane.Client/Services/LanguageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Services/LanguageService.cs b/Oqtane.Client/Services/LanguageService.cs index da1e0342..5f5d29cb 100644 --- a/Oqtane.Client/Services/LanguageService.cs +++ b/Oqtane.Client/Services/LanguageService.cs @@ -23,7 +23,7 @@ namespace Oqtane.Services { var languages = await GetJsonAsync>($"{Apiurl}?siteid={siteId}"); - return languages.OrderBy(l => l.Name).ToList(); + return languages?.OrderBy(l => l.Name).ToList() ?? Enumerable.Empty().ToList(); } public async Task GetLanguageAsync(int languageId) From 70595eb90ab04a77789045cc00b8a455bf80df12 Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 10 Jan 2021 23:50:21 +0300 Subject: [PATCH 15/51] Fix Language table --- Oqtane.Server/Repository/Context/TenantDBContext.cs | 2 +- Oqtane.Server/Repository/LanguageRepository.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index 79270ffb..d6fd06c4 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -21,7 +21,7 @@ namespace Oqtane.Repository public virtual DbSet Folder { get; set; } public virtual DbSet File { get; set; } - public virtual DbSet Languages { get; set; } + public virtual DbSet Language { get; set; } public TenantDBContext(ITenantResolver tenantResolver, IHttpContextAccessor accessor) : base(tenantResolver, accessor) { diff --git a/Oqtane.Server/Repository/LanguageRepository.cs b/Oqtane.Server/Repository/LanguageRepository.cs index 2f59a047..bdfd1b3e 100644 --- a/Oqtane.Server/Repository/LanguageRepository.cs +++ b/Oqtane.Server/Repository/LanguageRepository.cs @@ -13,22 +13,22 @@ namespace Oqtane.Repository _db = context; } - public IEnumerable GetLanguages(int siteId) => _db.Languages.Where(l => l.SiteId == siteId); + public IEnumerable GetLanguages(int siteId) => _db.Language.Where(l => l.SiteId == siteId); public Language AddLanguage(Language language) { - _db.Languages.Add(language); + _db.Language.Add(language); _db.SaveChanges(); return language; } - public Language GetLanguage(int languageId) => _db.Languages.Find(languageId); + public Language GetLanguage(int languageId) => _db.Language.Find(languageId); public void DeleteLanguage(int languageId) { - var language = _db.Languages.Find(languageId); - _db.Languages.Remove(language); + var language = _db.Language.Find(languageId); + _db.Language.Remove(language); _db.SaveChanges(); } } From 128729d4a09d3f0f048ff93d30a75a0cd8206cfb Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 10 Jan 2021 23:50:34 +0300 Subject: [PATCH 16/51] TenantId -> SiteId --- Oqtane.Server/Scripts/Tenant.02.00.02.00.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql b/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql index a52e8022..8eb54714 100644 --- a/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql +++ b/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql @@ -9,7 +9,7 @@ CREATE TABLE [dbo].[Language]( [Name] [nvarchar](100) NOT NULL, [Code] [nvarchar](10) NOT NULL, [IsCurrent] [bit] NOT NULL, - [TenantId] [int], + [SiteId] [int], [CreatedBy] [nvarchar](256) NOT NULL, [CreatedOn] [datetime] NOT NULL, [ModifiedBy] [nvarchar](256) NOT NULL, @@ -21,7 +21,7 @@ CREATE TABLE [dbo].[Language]( ) GO -ALTER TABLE [dbo].[Language] WITH CHECK ADD CONSTRAINT [FK_Language_Tenant] FOREIGN KEY([TenantId]) +ALTER TABLE [dbo].[Language] WITH CHECK ADD CONSTRAINT [FK_Language_Tenant] FOREIGN KEY([SiteId]) REFERENCES [dbo].[Tenant] ([TenantId]) ON DELETE CASCADE GO \ No newline at end of file From 7d090e51a12d00f9b6573ba0adf9b754ecaa6b78 Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 10 Jan 2021 23:51:15 +0300 Subject: [PATCH 17/51] Add language page --- .../Modules/Admin/Languages/Add.razor | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 Oqtane.Client/Modules/Admin/Languages/Add.razor diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor new file mode 100644 index 00000000..baa287e5 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -0,0 +1,72 @@ +@namespace Oqtane.Modules.Admin.Languages +@inherits ModuleBase +@using System.Globalization +@inject NavigationManager NavigationManager +@inject ILanguageService LanguageService +@inject IStringLocalizer Localizer + + + + + + + + + + +
+ + + +
+ + + +
+ +@Localizer["Cancel"] + +@code { + private string _code = string.Empty; + private string _isCurrent = "False"; + + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + + private static IEnumerable GetCultures() + => CultureInfo.GetCultures(CultureTypes.AllCultures) + .Select(c => new Culture { Name = c.Name, DisplayName = c.DisplayName }); + + private async Task SaveLanguage() + { + var language = new Language + { + SiteId = PageState.Page.SiteId, + Name = CultureInfo.GetCultureInfo(_code).DisplayName, + Code = _code, + IsCurrent = (_isCurrent == null ? false : Boolean.Parse(_isCurrent)) + }; + + try + { + language = await LanguageService.AddLanguageAsync(language); + + await logger.LogInformation("Language Added {Language}", language); + + NavigationManager.NavigateTo(NavigateUrl()); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message); + + AddModuleMessage(Localizer["Error Adding Language"], MessageType.Error); + } + } +} From 3a8fc428a6e3a719f13aa66ee5e36ed62ae9da5b Mon Sep 17 00:00:00 2001 From: hishamco Date: Mon, 11 Jan 2021 00:04:43 +0300 Subject: [PATCH 18/51] Use TriaStateCheckBox for language IsCurrent --- Oqtane.Client/Modules/Admin/Languages/Index.razor | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Oqtane.Client/Modules/Admin/Languages/Index.razor b/Oqtane.Client/Modules/Admin/Languages/Index.razor index c45a8bc2..933903cc 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Index.razor @@ -16,11 +16,13 @@ else   @Localizer["Name"] @Localizer["Code"] + @Localizer["Is Current"] @context.Name @context.Code + } From 932c5590afee063eb1cded3c312335afe5b8ce2e Mon Sep 17 00:00:00 2001 From: hishamco Date: Mon, 11 Jan 2021 00:11:30 +0300 Subject: [PATCH 19/51] Make sure one language is set to current --- Oqtane.Server/Repository/LanguageRepository.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Oqtane.Server/Repository/LanguageRepository.cs b/Oqtane.Server/Repository/LanguageRepository.cs index bdfd1b3e..ade74f2d 100644 --- a/Oqtane.Server/Repository/LanguageRepository.cs +++ b/Oqtane.Server/Repository/LanguageRepository.cs @@ -17,6 +17,12 @@ namespace Oqtane.Repository public Language AddLanguage(Language language) { + if (language.IsCurrent) + { + // Ensure all other languages are not set to current + _db.Language.ToList().ForEach(l => l.IsCurrent = false); + } + _db.Language.Add(language); _db.SaveChanges(); From 1dcb14811ddfe54034fe911246665f5781922782 Mon Sep 17 00:00:00 2001 From: Pavel Vesely Date: Mon, 11 Jan 2021 13:11:55 +0100 Subject: [PATCH 20/51] Add missing ContentUrl method --- Oqtane.Client/Themes/ThemeBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Oqtane.Client/Themes/ThemeBase.cs b/Oqtane.Client/Themes/ThemeBase.cs index 7f57ec70..a2ca3c86 100644 --- a/Oqtane.Client/Themes/ThemeBase.cs +++ b/Oqtane.Client/Themes/ThemeBase.cs @@ -88,5 +88,10 @@ namespace Oqtane.Themes { return Utilities.ContentUrl(PageState.Alias, fileid); } + + public string ContentUrl(int fileid, bool asAttachment) + { + return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment); + } } } From f1a1a21d74777757c1beed754f8d5001057c17d8 Mon Sep 17 00:00:00 2001 From: Pavel Vesely Date: Mon, 11 Jan 2021 13:13:25 +0100 Subject: [PATCH 21/51] Introduce GetFolderPath and GetFilePath repository methods --- Oqtane.Server/Controllers/FileController.cs | 19 +++++++----------- Oqtane.Server/Repository/FileRepository.cs | 20 ++++++++++++++++++- Oqtane.Server/Repository/FolderRepository.cs | 19 +++++++++++++++++- .../Repository/Interfaces/IFileRepository.cs | 2 ++ .../Interfaces/IFolderRepository.cs | 2 ++ 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index a5f0ffdd..ce36dae2 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -142,13 +142,13 @@ namespace Oqtane.Controllers Models.File _file = _files.GetFile(id, false); if (_file.Name != file.Name || _file.FolderId != file.FolderId) { - string folderpath = GetFolderPath(file.Folder); + string folderpath = _folders.GetFolderPath(file.Folder); if (!Directory.Exists(folderpath)) { Directory.CreateDirectory(folderpath); } - System.IO.File.Move(Path.Combine(GetFolderPath(_file.Folder), _file.Name), Path.Combine(folderpath, file.Name)); + System.IO.File.Move(_files.GetFilePath(_file), Path.Combine(folderpath, file.Name)); } file.Extension = Path.GetExtension(file.Name).ToLower().Replace(".", ""); @@ -177,7 +177,7 @@ namespace Oqtane.Controllers { _files.DeleteFile(id); - string filepath = Path.Combine(GetFolderPath(file.Folder), file.Name); + string filepath = _files.GetFilePath(file); if (System.IO.File.Exists(filepath)) { System.IO.File.Delete(filepath); @@ -213,7 +213,7 @@ namespace Oqtane.Controllers return file; } - string folderPath = GetFolderPath(folder); + string folderPath = _folders.GetFolderPath(folder); CreateDirectory(folderPath); string filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1); @@ -280,7 +280,7 @@ namespace Oqtane.Controllers if (virtualFolder != null && _userPermissions.IsAuthorized(User, PermissionNames.Edit, virtualFolder.Permissions)) { - folderPath = GetFolderPath(virtualFolder); + folderPath = _folders.GetFolderPath(virtualFolder); } } else @@ -291,7 +291,7 @@ namespace Oqtane.Controllers } } - if (folderPath != "") + if (!String.IsNullOrEmpty(folderPath)) { CreateDirectory(folderPath); using (var stream = new FileStream(Path.Combine(folderPath, file.FileName), FileMode.Create)) @@ -472,7 +472,7 @@ namespace Oqtane.Controllers { if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions)) { - var filepath = Path.Combine(GetFolderPath(file.Folder), file.Name); + var filepath = _files.GetFilePath(file); if (System.IO.File.Exists(filepath)) { var result = asAttachment @@ -500,11 +500,6 @@ namespace Oqtane.Controllers return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null; } - private string GetFolderPath(Folder folder) - { - return Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", _tenants.GetTenant().TenantId.ToString(), "Sites", folder.SiteId.ToString(), folder.Path); - } - private string GetFolderPath(string folder) { return Utilities.PathCombine(_environment.WebRootPath, folder); diff --git a/Oqtane.Server/Repository/FileRepository.cs b/Oqtane.Server/Repository/FileRepository.cs index 0e5fa798..07866810 100644 --- a/Oqtane.Server/Repository/FileRepository.cs +++ b/Oqtane.Server/Repository/FileRepository.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using Microsoft.EntityFrameworkCore; using Oqtane.Extensions; using Oqtane.Models; using Oqtane.Shared; +using File = Oqtane.Models.File; namespace Oqtane.Repository { @@ -11,11 +13,13 @@ namespace Oqtane.Repository { private TenantDBContext _db; private readonly IPermissionRepository _permissions; + private readonly IFolderRepository _folderRepository; - public FileRepository(TenantDBContext context, IPermissionRepository permissions) + public FileRepository(TenantDBContext context, IPermissionRepository permissions, IFolderRepository folderRepository) { _db = context; _permissions = permissions; + _folderRepository = folderRepository; } public IEnumerable GetFiles(int folderId) @@ -74,5 +78,19 @@ namespace Oqtane.Repository _db.File.Remove(file); _db.SaveChanges(); } + + public string GetFilePath(int fileId) + { + var file = _db.File.Find(fileId); + return GetFilePath(file); + } + + public string GetFilePath(File file) + { + if (file == null) return null; + var folder = file.Folder ?? _db.Folder.Find(file.FolderId); + var filepath = Path.Combine(_folderRepository.GetFolderPath(folder), file.Name); + return filepath; + } } } diff --git a/Oqtane.Server/Repository/FolderRepository.cs b/Oqtane.Server/Repository/FolderRepository.cs index dbaed6c1..dc7125bb 100644 --- a/Oqtane.Server/Repository/FolderRepository.cs +++ b/Oqtane.Server/Repository/FolderRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Oqtane.Extensions; using Oqtane.Models; @@ -11,11 +12,15 @@ namespace Oqtane.Repository { private TenantDBContext _db; private readonly IPermissionRepository _permissions; + private readonly IWebHostEnvironment _environment; + private readonly ITenantResolver _tenants; - public FolderRepository(TenantDBContext context, IPermissionRepository permissions) + public FolderRepository(TenantDBContext context, IPermissionRepository permissions,IWebHostEnvironment environment, ITenantResolver tenants) { _db = context; _permissions = permissions; + _environment = environment; + _tenants = tenants; } public IEnumerable GetFolders(int siteId) @@ -85,5 +90,17 @@ namespace Oqtane.Repository _db.Folder.Remove(folder); _db.SaveChanges(); } + + public string GetFolderPath(int folderId) + { + Folder folder = _db.Folder.Find(folderId); + return GetFolderPath(folder); + } + + public string GetFolderPath(Folder folder) + { + return Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", _tenants.GetTenant().TenantId.ToString(), "Sites", folder.SiteId.ToString(), folder.Path); + } + } } diff --git a/Oqtane.Server/Repository/Interfaces/IFileRepository.cs b/Oqtane.Server/Repository/Interfaces/IFileRepository.cs index 7ef99025..adfe8f89 100644 --- a/Oqtane.Server/Repository/Interfaces/IFileRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IFileRepository.cs @@ -11,5 +11,7 @@ namespace Oqtane.Repository File GetFile(int fileId); File GetFile(int fileId, bool tracking); void DeleteFile(int fileId); + string GetFilePath(int fileId); + string GetFilePath(File file); } } diff --git a/Oqtane.Server/Repository/Interfaces/IFolderRepository.cs b/Oqtane.Server/Repository/Interfaces/IFolderRepository.cs index 52b67e5b..5ce7467f 100644 --- a/Oqtane.Server/Repository/Interfaces/IFolderRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IFolderRepository.cs @@ -12,5 +12,7 @@ namespace Oqtane.Repository Folder GetFolder(int folderId, bool tracking); Folder GetFolder(int siteId, string path); void DeleteFolder(int folderId); + string GetFolderPath(int folderId); + string GetFolderPath(Folder folder); } } From c5ae8c979b74efdee4c7ee90d215efc7e39becf3 Mon Sep 17 00:00:00 2001 From: hishamco Date: Wed, 13 Jan 2021 18:19:56 +0300 Subject: [PATCH 22/51] Cultures should come from supported cultures --- .../Modules/Admin/Languages/Add.razor | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor index baa287e5..efd5f22b 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -2,6 +2,7 @@ @inherits ModuleBase @using System.Globalization @inject NavigationManager NavigationManager +@inject ILocalizationService LocalizationService @inject ILanguageService LanguageService @inject IStringLocalizer Localizer @@ -11,12 +12,15 @@ - + @if (_supportedCultures?.Count() > 1) + { + + } @@ -31,7 +35,7 @@ - + @Localizer["Cancel"] @code { @@ -40,9 +44,12 @@ public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; - private static IEnumerable GetCultures() - => CultureInfo.GetCultures(CultureTypes.AllCultures) - .Select(c => new Culture { Name = c.Name, DisplayName = c.DisplayName }); + private IEnumerable _supportedCultures; + + protected override async Task OnParametersSetAsync() + { + _supportedCultures = await LocalizationService.GetCulturesAsync(); + } private async Task SaveLanguage() { From b3152ee3e5c162cfa0b511062532bf6d5afab438 Mon Sep 17 00:00:00 2001 From: hishamco Date: Wed, 13 Jan 2021 18:26:36 +0300 Subject: [PATCH 23/51] LanguageSwitcher should have the cultures from language management --- Oqtane.Client/Themes/Controls/LanguageSwitcher.razor | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor index 16391513..dd92bf15 100644 --- a/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor @@ -1,9 +1,9 @@ @namespace Oqtane.Themes.Controls @inherits ThemeControlBase @using System.Globalization -@using Microsoft.AspNetCore.Localization; +@using Microsoft.AspNetCore.Localization; @using Oqtane.Models -@inject ILocalizationService LocalizationService +@inject ILanguageService LanguageService @inject NavigationManager NavigationManager @if (_supportedCultures?.Count() > 1) @@ -26,7 +26,8 @@ protected override async Task OnParametersSetAsync() { - _supportedCultures = await LocalizationService.GetCulturesAsync(); + var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId); + _supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name }); } private async Task SetCultureAsync(string culture) From a2943d083beae73cf45156459d5b483346262cf1 Mon Sep 17 00:00:00 2001 From: hishamco Date: Wed, 13 Jan 2021 18:43:26 +0300 Subject: [PATCH 24/51] Set culture when added language set to current --- .../Modules/Admin/Languages/Add.razor | 18 ++++++++++++++++++ .../Themes/Controls/LanguageSwitcher.razor | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor index efd5f22b..02b8e0c2 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -1,6 +1,7 @@ @namespace Oqtane.Modules.Admin.Languages @inherits ModuleBase @using System.Globalization +@using Microsoft.AspNetCore.Localization @inject NavigationManager NavigationManager @inject ILocalizationService LocalizationService @inject ILanguageService LanguageService @@ -65,6 +66,11 @@ { language = await LanguageService.AddLanguageAsync(language); + if (language.IsCurrent) + { + await SetCultureAsync(language.Code); + } + await logger.LogInformation("Language Added {Language}", language); NavigationManager.NavigateTo(NavigateUrl()); @@ -76,4 +82,16 @@ AddModuleMessage(Localizer["Error Adding Language"], MessageType.Error); } } + + private async Task SetCultureAsync(string culture) + { + if (culture != CultureInfo.CurrentUICulture.Name) + { + var interop = new Interop(JSRuntime); + var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); + await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360); + + NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); + } + } } diff --git a/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor index dd92bf15..bd4ac6fc 100644 --- a/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor @@ -1,7 +1,7 @@ @namespace Oqtane.Themes.Controls @inherits ThemeControlBase @using System.Globalization -@using Microsoft.AspNetCore.Localization; +@using Microsoft.AspNetCore.Localization @using Oqtane.Models @inject ILanguageService LanguageService @inject NavigationManager NavigationManager From 54ff8eced16cd2e641b6db32c3f8ce365a536706 Mon Sep 17 00:00:00 2001 From: hishamco Date: Wed, 13 Jan 2021 23:41:08 +0300 Subject: [PATCH 25/51] Fix the relationship --- Oqtane.Server/Scripts/Tenant.02.00.02.00.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql b/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql index 8eb54714..0e6d04ab 100644 --- a/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql +++ b/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql @@ -21,7 +21,7 @@ CREATE TABLE [dbo].[Language]( ) GO -ALTER TABLE [dbo].[Language] WITH CHECK ADD CONSTRAINT [FK_Language_Tenant] FOREIGN KEY([SiteId]) -REFERENCES [dbo].[Tenant] ([TenantId]) +ALTER TABLE [dbo].[Language] WITH CHECK ADD CONSTRAINT [FK_Language_Site] FOREIGN KEY([SiteId]) +REFERENCES [dbo].[Site] ([SiteId]) ON DELETE CASCADE GO \ No newline at end of file From e938d4f8015a2aa039a67b1df1263fd86732b3c2 Mon Sep 17 00:00:00 2001 From: hishamco Date: Fri, 15 Jan 2021 00:28:59 +0300 Subject: [PATCH 26/51] Add Admins role --- Oqtane.Server/Controllers/LanguageController.cs | 6 ++++-- Oqtane.Server/Repository/SiteRepository.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Oqtane.Server/Controllers/LanguageController.cs b/Oqtane.Server/Controllers/LanguageController.cs index 2b8f7cf0..c63988bb 100644 --- a/Oqtane.Server/Controllers/LanguageController.cs +++ b/Oqtane.Server/Controllers/LanguageController.cs @@ -12,6 +12,8 @@ namespace Oqtane.Controllers [Route(ControllerRoutes.Default)] public class LanguageController : Controller { + private const string HostAdminRoles = RoleNames.Host + "," + RoleNames.Admin; + private readonly ILanguageRepository _languages; private readonly ILogManager _logger; @@ -30,7 +32,7 @@ namespace Oqtane.Controllers public Language Get(int id) => _languages.GetLanguage(id); [HttpPost] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Roles = HostAdminRoles)] public Language Post([FromBody] Language language) { if (ModelState.IsValid) @@ -42,7 +44,7 @@ namespace Oqtane.Controllers } [HttpDelete("{id}")] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Roles = HostAdminRoles)] public void Delete(int id) { _languages.DeleteLanguage(id); diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 64791442..774d0330 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -512,7 +512,9 @@ namespace Oqtane.Repository PagePermissions = new List { new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) + new Permission(PermissionNames.Edit, RoleNames.Host, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) }.EncodePermissions(), PageTemplateModules = new List { @@ -522,7 +524,9 @@ namespace Oqtane.Repository ModulePermissions = new List { new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) + new Permission(PermissionNames.Edit, RoleNames.Host, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) }.EncodePermissions(), Content = "" } From bc0ba9230397e3f092455480e7d2e4a80a680df4 Mon Sep 17 00:00:00 2001 From: hishamco Date: Fri, 15 Jan 2021 01:35:53 +0300 Subject: [PATCH 27/51] Revert the changes in the LanguageController --- Oqtane.Server/Controllers/LanguageController.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Oqtane.Server/Controllers/LanguageController.cs b/Oqtane.Server/Controllers/LanguageController.cs index c63988bb..2b8f7cf0 100644 --- a/Oqtane.Server/Controllers/LanguageController.cs +++ b/Oqtane.Server/Controllers/LanguageController.cs @@ -12,8 +12,6 @@ namespace Oqtane.Controllers [Route(ControllerRoutes.Default)] public class LanguageController : Controller { - private const string HostAdminRoles = RoleNames.Host + "," + RoleNames.Admin; - private readonly ILanguageRepository _languages; private readonly ILogManager _logger; @@ -32,7 +30,7 @@ namespace Oqtane.Controllers public Language Get(int id) => _languages.GetLanguage(id); [HttpPost] - [Authorize(Roles = HostAdminRoles)] + [Authorize(Roles = RoleNames.Admin)] public Language Post([FromBody] Language language) { if (ModelState.IsValid) @@ -44,7 +42,7 @@ namespace Oqtane.Controllers } [HttpDelete("{id}")] - [Authorize(Roles = HostAdminRoles)] + [Authorize(Roles = RoleNames.Admin)] public void Delete(int id) { _languages.DeleteLanguage(id); From a2029a3ca3ac498bc9e3cf011dc69ef5f1639297 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sun, 17 Jan 2021 11:46:09 -0500 Subject: [PATCH 28/51] auto registration of scheduled jobs --- .../Infrastructure/DatabaseManager.cs | 10 +--- .../Infrastructure/Jobs/HostedServiceBase.cs | 56 +++++++++++++++---- .../Infrastructure/Jobs/NotificationJob.cs | 8 ++- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 4f598322..35f93353 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.IO; @@ -244,14 +244,6 @@ namespace Oqtane.Infrastructure db.Tenant.Add(tenant); db.SaveChanges(); _cache.Remove("tenants"); - - if (install.TenantName == TenantNames.Master) - { - var job = new Job { Name = "Notification Job", JobType = "Oqtane.Infrastructure.NotificationJob, Oqtane.Server", Frequency = "m", Interval = 1, StartDate = null, EndDate = null, IsEnabled = false, IsStarted = false, IsExecuting = false, NextExecution = null, RetentionHistory = 10, CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow }; - db.Job.Add(job); - db.SaveChanges(); - _cache.Remove("jobs"); - } } else { diff --git a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs index 5fb49934..96ab55a0 100644 --- a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs +++ b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -25,6 +25,15 @@ namespace Oqtane.Infrastructure // abstract method must be overridden public abstract string ExecuteJob(IServiceProvider provider); + // public properties which can be overridden and are used during auto registration of job + public string Name { get; set; } = ""; + public string Frequency { get; set; } = "d"; // day + public int Interval { get; set; } = 1; + public DateTime? StartDate { get; set; } = null; + public DateTime? EndDate { get; set; } = null; + public int RetentionHistory { get; set; } = 10; + public bool IsEnabled { get; set; } = false; + protected async Task ExecuteAsync(CancellationToken stoppingToken) { await Task.Yield(); // required so that this method does not block startup @@ -153,27 +162,52 @@ namespace Oqtane.Infrastructure // set IsExecuting to false in case this job was forcefully terminated previously using (var scope = _serviceScopeFactory.CreateScope()) { - string jobType = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); + string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); IJobRepository jobs = scope.ServiceProvider.GetRequiredService(); - Job job = jobs.GetJobs().Where(item => item.JobType == jobType).FirstOrDefault(); + Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault(); if (job != null) { job.IsStarted = true; job.IsExecuting = false; jobs.UpdateJob(job); } + else + { + // auto registration + job = new Job { JobType = jobTypeName }; + // optional properties + var jobType = Type.GetType(jobTypeName); + var jobObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, jobType) as HostedServiceBase; + if (jobObject.Name != "") + { + job.Name = jobObject.Name; + } + else + { + job.Name = Utilities.GetTypeName(job.JobType); + } + job.Frequency = jobObject.Frequency; + job.Interval = jobObject.Interval; + job.StartDate = jobObject.StartDate; + job.EndDate = jobObject.EndDate; + job.RetentionHistory = jobObject.RetentionHistory; + job.IsEnabled = jobObject.IsEnabled; + job.IsStarted = true; + job.IsExecuting = false; + jobs.AddJob(job); + } + } + + _executingTask = ExecuteAsync(_cancellationTokenSource.Token); + + if (_executingTask.IsCompleted) + { + return _executingTask; } } catch { - // can occur during the initial installation as there is no DBContext - } - - _executingTask = ExecuteAsync(_cancellationTokenSource.Token); - - if (_executingTask.IsCompleted) - { - return _executingTask; + // can occur during the initial installation because this method is called during startup and the database has not yet been created } return Task.CompletedTask; diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index deafe30a..bc1a2d31 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -14,7 +14,13 @@ namespace Oqtane.Infrastructure { // JobType = "Oqtane.Infrastructure.NotificationJob, Oqtane.Server" - public NotificationJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) {} + public NotificationJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) + { + Name = "Notification Job"; + Frequency = "m"; // minute + Interval = 1; + IsEnabled = false; + } public override string ExecuteJob(IServiceProvider provider) { From 8be9fd6eb2ae9d7dc0d1d436043983934c995636 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 18 Jan 2021 08:59:07 -0500 Subject: [PATCH 29/51] set SiteState in HostedServiceBase for scheduled jobs --- .../Infrastructure/Jobs/HostedServiceBase.cs | 10 +- .../Infrastructure/Jobs/NotificationJob.cs | 162 ++++++++---------- 2 files changed, 83 insertions(+), 89 deletions(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs index 96ab55a0..36fbefb6 100644 --- a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs +++ b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs @@ -91,7 +91,15 @@ namespace Oqtane.Infrastructure // execute the job try { - log.Notes = ExecuteJob(scope.ServiceProvider); + var notes = ""; + var tenants = scope.ServiceProvider.GetRequiredService(); + var siteState = scope.ServiceProvider.GetRequiredService(); + foreach (var tenant in tenants.GetTenants()) + { + siteState.Alias = new Alias { TenantId = tenant.TenantId }; + notes += ExecuteJob(scope.ServiceProvider); + } + log.Notes = notes; log.Succeeded = true; } catch (Exception ex) diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index bc1a2d31..92d1d336 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -26,103 +26,89 @@ namespace Oqtane.Infrastructure { string log = ""; - // iterate through tenants in this installation - List tenants = new List(); - var aliasRepository = provider.GetRequiredService(); - List aliases = aliasRepository.GetAliases().ToList(); - foreach (Alias alias in aliases) + // get services + var siteRepository = provider.GetRequiredService(); + var settingRepository = provider.GetRequiredService(); + var notificationRepository = provider.GetRequiredService(); + + // iterate through sites for this tenant + List sites = siteRepository.GetSites().ToList(); + foreach (Site site in sites) { - if (tenants.Contains(alias.TenantId)) continue; - tenants.Add(alias.TenantId); + log += "Processing Notifications For Site: " + site.Name + "
"; - // use the SiteState to set the Alias explicitly so the tenant can be resolved - var siteState = provider.GetRequiredService(); - siteState.Alias = alias; - - // get services which require tenant resolution - var siteRepository = provider.GetRequiredService(); - var settingRepository = provider.GetRequiredService(); - var notificationRepository = provider.GetRequiredService(); - - // iterate through sites for this tenant - List sites = siteRepository.GetSites().ToList(); - foreach (Site site in sites) + // get site settings + List sitesettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList(); + Dictionary settings = GetSettings(sitesettings); + if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && + settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" && + settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" && + settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "") { - log += "Processing Notifications For Site: " + site.Name + "
"; - - // get site settings - List sitesettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList(); - Dictionary settings = GetSettings(sitesettings); - if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && - settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" && - settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" && - settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "") + // construct SMTP Client + var client = new SmtpClient() { - // construct SMTP Client - var client = new SmtpClient() - { - DeliveryMethod = SmtpDeliveryMethod.Network, - UseDefaultCredentials = false, - Host = settings["SMTPHost"], - Port = int.Parse(settings["SMTPPort"]), - EnableSsl = bool.Parse(settings["SMTPSSL"]) - }; - if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "") - { - client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]); - } + DeliveryMethod = SmtpDeliveryMethod.Network, + UseDefaultCredentials = false, + Host = settings["SMTPHost"], + Port = int.Parse(settings["SMTPPort"]), + EnableSsl = bool.Parse(settings["SMTPSSL"]) + }; + if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "") + { + client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]); + } - // iterate through notifications - int sent = 0; - List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); - foreach (Notification notification in notifications) + // iterate through notifications + int sent = 0; + List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); + foreach (Notification notification in notifications) + { + MailMessage mailMessage = new MailMessage(); + mailMessage.From = new MailAddress(settings["SMTPSender"], site.Name); + mailMessage.Subject = notification.Subject; + if (notification.FromUserId != null) { - MailMessage mailMessage = new MailMessage(); - mailMessage.From = new MailAddress(settings["SMTPSender"], site.Name); - mailMessage.Subject = notification.Subject; - if (notification.FromUserId != null) - { - mailMessage.Body = "From: " + notification.FromDisplayName + "<" + notification.FromEmail + ">" + "\n"; - } - else - { - mailMessage.Body = "From: " + site.Name + "\n"; - } - mailMessage.Body += "Sent: " + notification.CreatedOn + "\n"; - if (notification.ToUserId != null) - { - mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName)); - mailMessage.Body += "To: " + notification.ToDisplayName + "<" + notification.ToEmail + ">" + "\n"; - } - else - { - mailMessage.To.Add(new MailAddress(notification.ToEmail)); - mailMessage.Body += "To: " + notification.ToEmail + "\n"; - } - mailMessage.Body += "Subject: " + notification.Subject + "\n\n"; - mailMessage.Body += notification.Body; + mailMessage.Body = "From: " + notification.FromDisplayName + "<" + notification.FromEmail + ">" + "\n"; + } + else + { + mailMessage.Body = "From: " + site.Name + "\n"; + } + mailMessage.Body += "Sent: " + notification.CreatedOn + "\n"; + if (notification.ToUserId != null) + { + mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName)); + mailMessage.Body += "To: " + notification.ToDisplayName + "<" + notification.ToEmail + ">" + "\n"; + } + else + { + mailMessage.To.Add(new MailAddress(notification.ToEmail)); + mailMessage.Body += "To: " + notification.ToEmail + "\n"; + } + mailMessage.Body += "Subject: " + notification.Subject + "\n\n"; + mailMessage.Body += notification.Body; - // send mail - try - { - client.Send(mailMessage); - sent = sent++; - notification.IsDelivered = true; - notification.DeliveredOn = DateTime.UtcNow; - notificationRepository.UpdateNotification(notification); - } - catch (Exception ex) - { - // error - log += ex.Message + "
"; - } + // send mail + try + { + client.Send(mailMessage); + sent = sent++; + notification.IsDelivered = true; + notification.DeliveredOn = DateTime.UtcNow; + notificationRepository.UpdateNotification(notification); + } + catch (Exception ex) + { + // error + log += ex.Message + "
"; } - log += "Notifications Delivered: " + sent + "
"; - } - else - { - log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "
"; } + log += "Notifications Delivered: " + sent + "
"; + } + else + { + log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "
"; } } From b664bc2dbb18a2ab0db365ea8c5a9ad70c28d0e5 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 18 Jan 2021 10:19:42 -0500 Subject: [PATCH 30/51] remove Add Job component and make Type read-only in Edit --- Oqtane.Client/Modules/Admin/Jobs/Add.razor | 141 ------------------- Oqtane.Client/Modules/Admin/Jobs/Edit.razor | 4 +- Oqtane.Client/Modules/Admin/Jobs/Index.razor | 1 - 3 files changed, 2 insertions(+), 144 deletions(-) delete mode 100644 Oqtane.Client/Modules/Admin/Jobs/Add.razor diff --git a/Oqtane.Client/Modules/Admin/Jobs/Add.razor b/Oqtane.Client/Modules/Admin/Jobs/Add.razor deleted file mode 100644 index 8535d2c0..00000000 --- a/Oqtane.Client/Modules/Admin/Jobs/Add.razor +++ /dev/null @@ -1,141 +0,0 @@ -@namespace Oqtane.Modules.Admin.Jobs -@inherits ModuleBase -@inject NavigationManager NavigationManager -@inject IJobService JobService -@inject IStringLocalizer Localizer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - -
- - - -
- - - - -
- - - -
- - - -
- - - -
- -@Localizer["Cancel"] - -@code { - private string _name = string.Empty; - private string _jobType = string.Empty; - private string _isEnabled = "True"; - private string _interval = string.Empty; - private string _frequency = string.Empty; - private string _startDate = string.Empty; - private string _endDate = string.Empty; - private string _retentionHistory = "10"; - - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - - private async Task SaveJob() - { - if (_name != string.Empty && !string.IsNullOrEmpty(_jobType) && _frequency != string.Empty && _interval != string.Empty && _retentionHistory != string.Empty) - { - var job = new Job(); - job.Name = _name; - job.JobType = _jobType; - job.IsEnabled = Boolean.Parse(_isEnabled); - job.Frequency = _frequency; - job.Interval = int.Parse(_interval); - - if (_startDate == string.Empty) - { - job.StartDate = null; - } - else - { - job.StartDate = DateTime.Parse(_startDate); - } - - if (_endDate == string.Empty) - { - job.EndDate = null; - } - else - { - job.EndDate = DateTime.Parse(_endDate); - } - - job.RetentionHistory = int.Parse(_retentionHistory); - job.IsStarted = false; - job.IsExecuting = false; - job.NextExecution = null; - - try - { - job = await JobService.AddJobAsync(job); - await logger.LogInformation("Job Added {Job}", job); - NavigationManager.NavigateTo(NavigateUrl()); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Adding Job {Job} {Error}", job, ex.Message); - AddModuleMessage(Localizer["Error Adding Job"], MessageType.Error); - } - } - else - { - AddModuleMessage(Localizer["You Must Provide The Job Name, Type, Frequency, and Retention"], MessageType.Warning); - } - } - -} diff --git a/Oqtane.Client/Modules/Admin/Jobs/Edit.razor b/Oqtane.Client/Modules/Admin/Jobs/Edit.razor index 65043de8..9a436b6d 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Edit.razor @@ -15,10 +15,10 @@ - + - + diff --git a/Oqtane.Client/Modules/Admin/Jobs/Index.razor b/Oqtane.Client/Modules/Admin/Jobs/Index.razor index 7a435f77..5aefdcbe 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Index.razor @@ -9,7 +9,6 @@ } else { -
From 82a118b603fe1b9394be0cf2b253cc7987b358fe Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 18 Jan 2021 14:39:56 -0500 Subject: [PATCH 31/51] notification improvements --- .../Modules/Admin/UserProfile/Add.razor | 20 +--- .../Modules/Admin/UserProfile/View.razor | 20 +--- Oqtane.Server/Controllers/UserController.cs | 30 +----- .../Infrastructure/Jobs/NotificationJob.cs | 96 ++++++++++++------- .../Repository/NotificationRepository.cs | 4 +- Oqtane.Shared/Models/Notification.cs | 46 ++++++++- 6 files changed, 122 insertions(+), 94 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Add.razor b/Oqtane.Client/Modules/Admin/UserProfile/Add.razor index d3212726..dcc96ff4 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Add.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Add.razor @@ -48,26 +48,12 @@ private async Task Send() { - var notification = new Notification(); try { var user = await UserService.GetUserAsync(username, PageState.Site.SiteId); if (user != null) - { - notification.SiteId = PageState.Site.SiteId; - notification.FromUserId = PageState.User.UserId; - notification.FromDisplayName = PageState.User.DisplayName; - notification.FromEmail = PageState.User.Email; - notification.ToUserId = user.UserId; - notification.ToDisplayName = user.DisplayName; - notification.ToEmail = user.Email; - notification.Subject = subject; - notification.Body = body; - notification.ParentId = null; - notification.CreatedOn = DateTime.UtcNow; - notification.IsDelivered = false; - notification.DeliveredOn = null; - notification.SendOn = DateTime.UtcNow; + { + var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, null); notification = await NotificationService.AddNotificationAsync(notification); await logger.LogInformation("Notification Created {Notification}", notification); NavigationManager.NavigateTo(NavigateUrl()); @@ -79,7 +65,7 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Adding Notification {Notification} {Error}", notification, ex.Message); + await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message); AddModuleMessage(Localizer["Error Adding Notification"], MessageType.Error); } } diff --git a/Oqtane.Client/Modules/Admin/UserProfile/View.razor b/Oqtane.Client/Modules/Admin/UserProfile/View.razor index fcd7340c..ff9f3937 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/View.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/View.razor @@ -176,26 +176,12 @@ private async Task Send() { - var notification = new Notification(); try { var user = await UserService.GetUserAsync(username, PageState.Site.SiteId); if (user != null) - { - notification.SiteId = PageState.Site.SiteId; - notification.FromUserId = PageState.User.UserId; - notification.FromDisplayName = PageState.User.DisplayName; - notification.FromEmail = PageState.User.Email; - notification.ToUserId = user.UserId; - notification.ToDisplayName = user.DisplayName; - notification.ToEmail = user.Email; - notification.Subject = subject; - notification.Body = body; - notification.ParentId = notificationid; - notification.CreatedOn = DateTime.UtcNow; - notification.IsDelivered = false; - notification.DeliveredOn = null; - notification.SendOn = DateTime.UtcNow; + { + var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid); notification = await NotificationService.AddNotificationAsync(notification); await logger.LogInformation("Notification Created {Notification}", notification); NavigationManager.NavigateTo(NavigateUrl()); @@ -207,7 +193,7 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Adding Notification {Notification} {Error}", notification, ex.Message); + await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message); AddModuleMessage(Localizer["Error Adding Notification"], MessageType.Error); } } diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 5a97087a..9819994b 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; @@ -146,20 +146,10 @@ namespace Oqtane.Controllers newUser = _users.AddUser(user); if (!verified) { - Notification notification = new Notification(); - notification.SiteId = user.SiteId; - notification.FromUserId = null; - notification.ToUserId = newUser.UserId; - notification.ToEmail = newUser.Email; - notification.Subject = "User Account Verification"; string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); string url = HttpContext.Request.Scheme + "://" + _tenants.GetAlias().Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); - notification.Body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; - notification.ParentId = null; - notification.CreatedOn = DateTime.UtcNow; - notification.IsDelivered = false; - notification.DeliveredOn = null; - notification.SendOn = DateTime.UtcNow; + string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; + var notification = new Notification(user.SiteId, null, newUser, "User Account Verification", body, null); _notifications.AddNotification(notification); } @@ -379,20 +369,10 @@ namespace Oqtane.Controllers IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); if (identityuser != null) { - Notification notification = new Notification(); - notification.SiteId = user.SiteId; - notification.FromUserId = null; - notification.ToUserId = user.UserId; - notification.ToEmail = ""; - notification.Subject = "User Password Reset"; string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); string url = HttpContext.Request.Scheme + "://" + _tenants.GetAlias().Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); - notification.Body = "Dear " + user.DisplayName + ",\n\nPlease Click The Link Displayed Below To Reset Your Password:\n\n" + url + "\n\nThank You!"; - notification.ParentId = null; - notification.CreatedOn = DateTime.UtcNow; - notification.IsDelivered = false; - notification.DeliveredOn = null; - notification.SendOn = DateTime.UtcNow; + string body = "Dear " + user.DisplayName + ",\n\nPlease Click The Link Displayed Below To Reset Your Password:\n\n" + url + "\n\nThank You!"; + var notification = new Notification(user.SiteId, null, user, "User Password Reset", body, null); _notifications.AddNotification(notification); _logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username); } diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 92d1d336..7818d9d3 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -22,16 +22,18 @@ namespace Oqtane.Infrastructure IsEnabled = false; } + // job is executed for each tenant in installation public override string ExecuteJob(IServiceProvider provider) { string log = ""; // get services var siteRepository = provider.GetRequiredService(); + var userRepository = provider.GetRequiredService(); var settingRepository = provider.GetRequiredService(); var notificationRepository = provider.GetRequiredService(); - // iterate through sites for this tenant + // iterate through sites for current tenant List sites = siteRepository.GetSites().ToList(); foreach (Site site in sites) { @@ -59,49 +61,79 @@ namespace Oqtane.Infrastructure client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]); } - // iterate through notifications + // iterate through undelivered notifications int sent = 0; List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); foreach (Notification notification in notifications) { - MailMessage mailMessage = new MailMessage(); - mailMessage.From = new MailAddress(settings["SMTPSender"], site.Name); - mailMessage.Subject = notification.Subject; - if (notification.FromUserId != null) + // get sender and receiver information if not provided + if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null) { - mailMessage.Body = "From: " + notification.FromDisplayName + "<" + notification.FromEmail + ">" + "\n"; + var user = userRepository.GetUser(notification.FromUserId.Value); + if (user != null) + { + notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail; + notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName; + } } - else + if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null) { - mailMessage.Body = "From: " + site.Name + "\n"; + var user = userRepository.GetUser(notification.ToUserId.Value); + if (user != null) + { + notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail; + notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName; + } } - mailMessage.Body += "Sent: " + notification.CreatedOn + "\n"; - if (notification.ToUserId != null) + + // validate recipient + if (string.IsNullOrEmpty(notification.ToEmail)) { - mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName)); - mailMessage.Body += "To: " + notification.ToDisplayName + "<" + notification.ToEmail + ">" + "\n"; - } - else - { - mailMessage.To.Add(new MailAddress(notification.ToEmail)); - mailMessage.Body += "To: " + notification.ToEmail + "\n"; - } - mailMessage.Body += "Subject: " + notification.Subject + "\n\n"; - mailMessage.Body += notification.Body; - - // send mail - try - { - client.Send(mailMessage); - sent = sent++; - notification.IsDelivered = true; - notification.DeliveredOn = DateTime.UtcNow; + log += "Recipient Missing For NotificationId: " + notification.NotificationId + "
"; + notification.IsDeleted = true; notificationRepository.UpdateNotification(notification); } - catch (Exception ex) + else { - // error - log += ex.Message + "
"; + MailMessage mailMessage = new MailMessage(); + mailMessage.From = new MailAddress(settings["SMTPSender"], site.Name); + mailMessage.Subject = notification.Subject; + if (!string.IsNullOrEmpty(notification.FromEmail) && !string.IsNullOrEmpty(notification.FromDisplayName)) + { + mailMessage.Body = "From: " + notification.FromDisplayName + "<" + notification.FromEmail + ">" + "\n"; + } + else + { + mailMessage.Body = "From: " + site.Name + "\n"; + } + mailMessage.Body += "Sent: " + notification.CreatedOn + "\n"; + if (!string.IsNullOrEmpty(notification.ToEmail) && !string.IsNullOrEmpty(notification.ToDisplayName)) + { + mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName)); + mailMessage.Body += "To: " + notification.ToDisplayName + "<" + notification.ToEmail + ">" + "\n"; + } + else + { + mailMessage.To.Add(new MailAddress(notification.ToEmail)); + mailMessage.Body += "To: " + notification.ToEmail + "\n"; + } + mailMessage.Body += "Subject: " + notification.Subject + "\n\n"; + mailMessage.Body += notification.Body; + + // send mail + try + { + client.Send(mailMessage); + sent = sent++; + notification.IsDelivered = true; + notification.DeliveredOn = DateTime.UtcNow; + notificationRepository.UpdateNotification(notification); + } + catch (Exception ex) + { + // error + log += ex.Message + "
"; + } } } log += "Notifications Delivered: " + sent + "
"; diff --git a/Oqtane.Server/Repository/NotificationRepository.cs b/Oqtane.Server/Repository/NotificationRepository.cs index 43025c3a..38ec8559 100644 --- a/Oqtane.Server/Repository/NotificationRepository.cs +++ b/Oqtane.Server/Repository/NotificationRepository.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using Oqtane.Models; @@ -20,7 +20,7 @@ namespace Oqtane.Repository { return _db.Notification .Where(item => item.SiteId == siteId) - .Where(item => item.IsDelivered == false) + .Where(item => item.IsDelivered == false && item.IsDeleted == false) .Where(item => item.SendOn == null || item.SendOn < System.DateTime.UtcNow) .ToList(); } diff --git a/Oqtane.Shared/Models/Notification.cs b/Oqtane.Shared/Models/Notification.cs index 7ccdbd9d..6795c954 100644 --- a/Oqtane.Shared/Models/Notification.cs +++ b/Oqtane.Shared/Models/Notification.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations.Schema; namespace Oqtane.Models @@ -23,6 +23,50 @@ namespace Oqtane.Models public DateTime? DeletedOn { get; set; } public bool IsDeleted { get; set; } public DateTime? SendOn { get; set; } + + public Notification() {} + + public Notification(int siteId, User from, User to, string subject, string body, int? parentId) + { + SiteId = siteId; + if (from != null) + { + FromUserId = from.UserId; + FromDisplayName = from.DisplayName; + FromEmail = from.Email; + } + if (to != null) + { + ToUserId = to.UserId; + ToDisplayName = to.DisplayName; + ToEmail = to.Email; + } + Subject = subject; + Body = body; + ParentId = parentId; + CreatedOn = DateTime.UtcNow; + IsDelivered = false; + DeliveredOn = null; + SendOn = DateTime.UtcNow; + } + + public Notification(int siteId, string fromDisplayName, string fromEmail, string toDisplayName, string toEmail, string subject, string body) + { + SiteId = siteId; + FromUserId = null; + FromDisplayName = fromDisplayName; + FromEmail = fromEmail; + ToUserId = null; + ToDisplayName = toDisplayName; + ToEmail = toEmail; + Subject = subject; + Body = body; + ParentId = null; + CreatedOn = DateTime.UtcNow; + IsDelivered = false; + DeliveredOn = null; + SendOn = DateTime.UtcNow; + } } } From c0ed7c79345443f004c46ea0940704ac20f63e55 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 21 Jan 2021 17:09:34 -0500 Subject: [PATCH 32/51] Localization fixes - table definition, SQL script naming, SQL script not marked as Embedded Resource, changed column name from IsCurrrent to IsDefault to reflect intent, set default language for site in _Host --- .../Modules/Admin/Languages/Add.razor | 80 +++++++++++-------- .../Modules/Admin/Languages/Index.razor | 6 +- Oqtane.Server/Controllers/AliasController.cs | 21 +---- Oqtane.Server/Oqtane.Server.csproj | 1 + Oqtane.Server/Pages/_Host.cshtml.cs | 35 ++++++++ Oqtane.Server/Repository/AliasRepository.cs | 23 +++++- .../Repository/Context/DBContextBase.cs | 16 ++-- .../Repository/Context/MasterDBContext.cs | 18 ++++- .../Repository/Interfaces/IAliasRepository.cs | 3 +- .../Repository/LanguageRepository.cs | 4 +- Oqtane.Server/Repository/TenantResolver.cs | 54 ++++++++----- ...02.00.02.00.sql => Tenant.02.00.01.02.sql} | 4 +- Oqtane.Server/Startup.cs | 5 +- Oqtane.Shared/Models/Language.cs | 2 +- 14 files changed, 178 insertions(+), 94 deletions(-) rename Oqtane.Server/Scripts/{Tenant.02.00.02.00.sql => Tenant.02.00.01.02.sql} (90%) diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor index 02b8e0c2..7efbcf85 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -7,41 +7,48 @@ @inject ILanguageService LanguageService @inject IStringLocalizer Localizer - - - - - - - - - -
- - - @if (_supportedCultures?.Count() > 1) - { - - } -
- - - -
- -@Localizer["Cancel"] +@if (_supportedCultures == null) +{ +

@Localizer["Loading..."]

+} +else +{ + @if (_supportedCultures?.Count() > 1) + { + + + + + + + + + +
+ + + +
+ + + +
+ + } + @Localizer["Cancel"] +} @code { private string _code = string.Empty; - private string _isCurrent = "False"; + private string _isDefault = "False"; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; @@ -50,6 +57,10 @@ protected override async Task OnParametersSetAsync() { _supportedCultures = await LocalizationService.GetCulturesAsync(); + if (_supportedCultures.Count() <= 1) + { + AddModuleMessage(Localizer["The Only Supported Culture That Has Been Defined Is English"], MessageType.Warning); + } } private async Task SaveLanguage() @@ -59,14 +70,14 @@ SiteId = PageState.Page.SiteId, Name = CultureInfo.GetCultureInfo(_code).DisplayName, Code = _code, - IsCurrent = (_isCurrent == null ? false : Boolean.Parse(_isCurrent)) + IsDefault = (_isDefault == null ? false : Boolean.Parse(_isDefault)) }; try { language = await LanguageService.AddLanguageAsync(language); - if (language.IsCurrent) + if (language.IsDefault) { await SetCultureAsync(language.Code); } @@ -78,7 +89,6 @@ catch (Exception ex) { await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message); - AddModuleMessage(Localizer["Error Adding Language"], MessageType.Error); } } diff --git a/Oqtane.Client/Modules/Admin/Languages/Index.razor b/Oqtane.Client/Modules/Admin/Languages/Index.razor index 933903cc..748c875b 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Index.razor @@ -16,13 +16,13 @@ else   @Localizer["Name"] @Localizer["Code"] - @Localizer["Is Current"] + @Localizer["Default?"] - + @context.Name @context.Code - + } diff --git a/Oqtane.Server/Controllers/AliasController.cs b/Oqtane.Server/Controllers/AliasController.cs index 8956bac7..4d8dac8d 100644 --- a/Oqtane.Server/Controllers/AliasController.cs +++ b/Oqtane.Server/Controllers/AliasController.cs @@ -50,29 +50,13 @@ namespace Oqtane.Controllers [HttpGet("name/{**name}")] public Alias Get(string name, string sync) { - List aliases = _aliases.GetAliases().ToList(); // cached Alias alias = null; + if (_accessor.HttpContext != null) { name = (name == "~") ? "" : name; name = _accessor.HttpContext.Request.Host.Value + "/" + WebUtility.UrlDecode(name); - var segments = name.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - - // iterate segments in reverse order - for (int i = segments.Length; i > 0; i--) - { - name = string.Join("/", segments, 0, i); - alias = aliases.Find(item => item.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); - if (alias != null) - { - break; // found a matching alias - } - } - } - if (alias == null && aliases.Any()) - { - // use first alias if name does not exist - alias = aliases.FirstOrDefault(); + alias = _aliases.GetAlias(name); } // get sync events @@ -81,6 +65,7 @@ namespace Oqtane.Controllers alias.SyncDate = DateTime.UtcNow; alias.SyncEvents = _syncManager.GetSyncEvents(alias.TenantId, DateTime.ParseExact(sync, "yyyyMMddHHmmssfff", CultureInfo.InvariantCulture)); } + return alias; } diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 8176804d..1ac18c29 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -35,6 +35,7 @@ + diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index c50e5b1f..662beb18 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -7,11 +7,28 @@ using Oqtane.Themes; using System; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Http.Extensions; +using Oqtane.Repository; +using Microsoft.AspNetCore.Localization; +using Microsoft.Extensions.Configuration; namespace Oqtane.Pages { public class HostModel : PageModel { + private IConfiguration _configuration; + private readonly SiteState _state; + private readonly IAliasRepository _aliases; + private readonly ILanguageRepository _languages; + + public HostModel(IConfiguration configuration, SiteState state, IAliasRepository aliases, ILanguageRepository languages) + { + _configuration = configuration; + _state = state; + _aliases = aliases; + _languages = languages; + } + public string HeadResources = ""; public string BodyResources = ""; @@ -24,6 +41,24 @@ namespace Oqtane.Pages ProcessModuleControls(assembly); ProcessThemeControls(assembly); } + + // if framework is installed + if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) + { + Uri uri = new Uri(Request.GetDisplayUrl()); + var alias = _aliases.GetAlias(uri.Authority + "/" + uri.LocalPath.Substring(1)); + _state.Alias = alias; + + // set default language for site + var language = _languages.GetLanguages(alias.SiteId).Where(item => item.IsDefault).FirstOrDefault(); + if (language != null) + { + HttpContext.Response.Cookies.Append( + CookieRequestCultureProvider.DefaultCookieName, + CookieRequestCultureProvider.MakeCookieValue( + new RequestCulture(language.Code))); + } + } } private void ProcessHostResources(Assembly assembly) diff --git a/Oqtane.Server/Repository/AliasRepository.cs b/Oqtane.Server/Repository/AliasRepository.cs index ccb26681..f25c4c03 100644 --- a/Oqtane.Server/Repository/AliasRepository.cs +++ b/Oqtane.Server/Repository/AliasRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; @@ -48,6 +48,27 @@ namespace Oqtane.Repository return _db.Alias.Find(aliasId); } + public Alias GetAlias(string name) + { + Alias alias = null; + + List aliases = GetAliases().ToList(); + var segments = name.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + // iterate segments in reverse order + for (int i = segments.Length; i > 0; i--) + { + name = string.Join("/", segments, 0, i); + alias = aliases.Find(item => item.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + if (alias != null) + { + break; // found a matching alias + } + } + + return alias; + } + public void DeleteAlias(int aliasId) { Alias alias = _db.Alias.Find(aliasId); diff --git a/Oqtane.Server/Repository/Context/DBContextBase.cs b/Oqtane.Server/Repository/Context/DBContextBase.cs index 247006bd..522bf0d9 100644 --- a/Oqtane.Server/Repository/Context/DBContextBase.cs +++ b/Oqtane.Server/Repository/Context/DBContextBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; @@ -10,20 +10,24 @@ namespace Oqtane.Repository { public class DBContextBase : IdentityUserContext { - private Tenant _tenant; + private ITenantResolver _tenantResolver; private IHttpContextAccessor _accessor; public DBContextBase(ITenantResolver tenantResolver, IHttpContextAccessor accessor) { - _tenant = tenantResolver.GetTenant(); + _tenantResolver = tenantResolver; _accessor = accessor; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseSqlServer(_tenant.DBConnectionString - .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()) - ); + var tenant = _tenantResolver.GetTenant(); + if (tenant != null) + { + optionsBuilder.UseSqlServer(tenant.DBConnectionString + .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()) + ); + } base.OnConfiguring(optionsBuilder); } diff --git a/Oqtane.Server/Repository/Context/MasterDBContext.cs b/Oqtane.Server/Repository/Context/MasterDBContext.cs index 65312819..976874cf 100644 --- a/Oqtane.Server/Repository/Context/MasterDBContext.cs +++ b/Oqtane.Server/Repository/Context/MasterDBContext.cs @@ -1,18 +1,32 @@ -using System; +using System; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Oqtane.Models; +using Microsoft.Extensions.Configuration; namespace Oqtane.Repository { public class MasterDBContext : DbContext { private IHttpContextAccessor _accessor; + private IConfiguration _configuration; - public MasterDBContext(DbContextOptions options, IHttpContextAccessor accessor) : base(options) + public MasterDBContext(DbContextOptions options, IHttpContextAccessor accessor, IConfiguration configuration) : base(options) { _accessor = accessor; + _configuration = configuration; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) + { + optionsBuilder.UseSqlServer(_configuration.GetConnectionString("DefaultConnection") + .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()) + ); + } + base.OnConfiguring(optionsBuilder); } public virtual DbSet Alias { get; set; } diff --git a/Oqtane.Server/Repository/Interfaces/IAliasRepository.cs b/Oqtane.Server/Repository/Interfaces/IAliasRepository.cs index 4f258307..5422ab07 100644 --- a/Oqtane.Server/Repository/Interfaces/IAliasRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IAliasRepository.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Oqtane.Models; namespace Oqtane.Repository @@ -9,6 +9,7 @@ namespace Oqtane.Repository Alias AddAlias(Alias alias); Alias UpdateAlias(Alias alias); Alias GetAlias(int aliasId); + Alias GetAlias(string name); void DeleteAlias(int aliasId); } } diff --git a/Oqtane.Server/Repository/LanguageRepository.cs b/Oqtane.Server/Repository/LanguageRepository.cs index ade74f2d..0ee5105b 100644 --- a/Oqtane.Server/Repository/LanguageRepository.cs +++ b/Oqtane.Server/Repository/LanguageRepository.cs @@ -17,10 +17,10 @@ namespace Oqtane.Repository public Language AddLanguage(Language language) { - if (language.IsCurrent) + if (language.IsDefault) { // Ensure all other languages are not set to current - _db.Language.ToList().ForEach(l => l.IsCurrent = false); + _db.Language.ToList().ForEach(l => l.IsDefault = false); } _db.Language.Add(language); diff --git a/Oqtane.Server/Repository/TenantResolver.cs b/Oqtane.Server/Repository/TenantResolver.cs index cd273b3a..815a8c55 100644 --- a/Oqtane.Server/Repository/TenantResolver.cs +++ b/Oqtane.Server/Repository/TenantResolver.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; @@ -9,24 +9,49 @@ namespace Oqtane.Repository { public class TenantResolver : ITenantResolver { - private readonly Alias _alias; - private readonly Tenant _tenant; + private readonly IHttpContextAccessor _accessor; + private readonly IAliasRepository _aliasRepository; + private readonly ITenantRepository _tenantRepository; + private readonly SiteState _siteState; + + private Alias _alias; + private Tenant _tenant; public TenantResolver(IHttpContextAccessor accessor, IAliasRepository aliasRepository, ITenantRepository tenantRepository, SiteState siteState) { - int aliasId = -1; + _accessor = accessor; + _aliasRepository = aliasRepository; + _tenantRepository = tenantRepository; + _siteState = siteState; + } - if (siteState != null && siteState.Alias != null) + public Alias GetAlias() + { + if (_alias == null) ResolveTenant(); + return _alias; + } + + public Tenant GetTenant() + { + if (_tenant == null) ResolveTenant(); + return _tenant; + } + + private void ResolveTenant() + { + if (_siteState != null && _siteState.Alias != null) { // background processes can pass in an alias using the SiteState service - _alias = siteState.Alias; + _alias = _siteState.Alias; } else { + int aliasId = -1; + // get aliasid identifier based on request - if (accessor.HttpContext != null) + if (_accessor.HttpContext != null) { - string[] segments = accessor.HttpContext.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + string[] segments = _accessor.HttpContext.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (segments.Length > 1 && (segments[1] == "api" || segments[1] == "pages") && segments[0] != "~") { aliasId = int.Parse(segments[0]); @@ -34,7 +59,7 @@ namespace Oqtane.Repository } // get the alias - IEnumerable aliases = aliasRepository.GetAliases().ToList(); // cached + IEnumerable aliases = _aliasRepository.GetAliases().ToList(); // cached if (aliasId != -1) { _alias = aliases.FirstOrDefault(item => item.AliasId == aliasId); @@ -44,19 +69,10 @@ namespace Oqtane.Repository if (_alias != null) { // get the tenant - IEnumerable tenants = tenantRepository.GetTenants(); // cached + IEnumerable tenants = _tenantRepository.GetTenants(); // cached _tenant = tenants.FirstOrDefault(item => item.TenantId == _alias.TenantId); } - } - public Alias GetAlias() - { - return _alias; - } - - public Tenant GetTenant() - { - return _tenant; } } } diff --git a/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql b/Oqtane.Server/Scripts/Tenant.02.00.01.02.sql similarity index 90% rename from Oqtane.Server/Scripts/Tenant.02.00.02.00.sql rename to Oqtane.Server/Scripts/Tenant.02.00.01.02.sql index 0e6d04ab..3f3f4976 100644 --- a/Oqtane.Server/Scripts/Tenant.02.00.02.00.sql +++ b/Oqtane.Server/Scripts/Tenant.02.00.01.02.sql @@ -8,8 +8,8 @@ CREATE TABLE [dbo].[Language]( [LanguageId] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](100) NOT NULL, [Code] [nvarchar](10) NOT NULL, - [IsCurrent] [bit] NOT NULL, - [SiteId] [int], + [IsDefault] [bit] NOT NULL, + [SiteId] [int] NOT NULL, [CreatedBy] [nvarchar](256) NOT NULL, [CreatedOn] [datetime] NOT NULL, [ModifiedBy] [nvarchar](256) NOT NULL, diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 0353bc03..babe9245 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -132,10 +132,7 @@ namespace Oqtane services.AddSingleton(); - services.AddDbContext(options => - options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection") - .Replace("|DataDirectory|", AppContext.GetData("DataDirectory")?.ToString()) - )); + services.AddDbContext(options => { }); services.AddDbContext(options => { }); services.AddIdentityCore(options => { }) diff --git a/Oqtane.Shared/Models/Language.cs b/Oqtane.Shared/Models/Language.cs index 2433d0d0..e0afdd66 100644 --- a/Oqtane.Shared/Models/Language.cs +++ b/Oqtane.Shared/Models/Language.cs @@ -12,7 +12,7 @@ namespace Oqtane.Models public string Code { get; set; } - public bool IsCurrent { get; set; } + public bool IsDefault { get; set; } public string CreatedBy { get; set; } From f637c9137cd98b2e2a0a3b00ead0292aceb4de0b Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Fri, 22 Jan 2021 14:19:43 -0500 Subject: [PATCH 33/51] added HTML5 date picker to input controls --- Oqtane.Client/Modules/Admin/Roles/Users.razor | 47 ++++--------------- 1 file changed, 8 insertions(+), 39 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Roles/Users.razor b/Oqtane.Client/Modules/Admin/Roles/Users.razor index 3fb2bb95..b9dcd431 100644 --- a/Oqtane.Client/Modules/Admin/Roles/Users.razor +++ b/Oqtane.Client/Modules/Admin/Roles/Users.razor @@ -38,7 +38,7 @@ else - + @@ -46,7 +46,7 @@ else - + @@ -75,8 +75,8 @@ else private string name = string.Empty; private List users; private int userid = -1; - private string effectivedate = string.Empty; - private string expirydate = string.Empty; + private DateTime? effectivedate = null; + private DateTime? expirydate = null; private List userroles; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; @@ -125,23 +125,8 @@ else var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault(); if (userrole != null) { - if (string.IsNullOrEmpty(effectivedate)) - { - userrole.EffectiveDate = null; - } - else - { - userrole.EffectiveDate = DateTime.Parse(effectivedate); - } - - if (string.IsNullOrEmpty(expirydate)) - { - userrole.ExpiryDate = null; - } - else - { - userrole.ExpiryDate = DateTime.Parse(expirydate); - } + userrole.EffectiveDate = effectivedate; + userrole.ExpiryDate = expirydate; await UserRoleService.UpdateUserRoleAsync(userrole); } else @@ -149,24 +134,8 @@ else userrole = new UserRole(); userrole.UserId = userid; userrole.RoleId = roleid; - - if (string.IsNullOrEmpty(effectivedate)) - { - userrole.EffectiveDate = null; - } - else - { - userrole.EffectiveDate = DateTime.Parse(effectivedate); - } - - if (string.IsNullOrEmpty(expirydate)) - { - userrole.ExpiryDate = null; - } - else - { - userrole.ExpiryDate = DateTime.Parse(expirydate); - } + userrole.EffectiveDate = effectivedate; + userrole.ExpiryDate = expirydate; await UserRoleService.AddUserRoleAsync(userrole); } From 5a660f2634fc5147863ef5242beb28a7f45a693a Mon Sep 17 00:00:00 2001 From: hishamco Date: Sat, 23 Jan 2021 23:48:10 +0300 Subject: [PATCH 34/51] Reset IsDefault per site Id for new language --- Oqtane.Server/Repository/LanguageRepository.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Repository/LanguageRepository.cs b/Oqtane.Server/Repository/LanguageRepository.cs index 0ee5105b..7f92086b 100644 --- a/Oqtane.Server/Repository/LanguageRepository.cs +++ b/Oqtane.Server/Repository/LanguageRepository.cs @@ -20,7 +20,10 @@ namespace Oqtane.Repository if (language.IsDefault) { // Ensure all other languages are not set to current - _db.Language.ToList().ForEach(l => l.IsDefault = false); + _db.Language + .Where(l => l.SiteId == language.SiteId) + .ToList() + .ForEach(l => l.IsDefault = false); } _db.Language.Add(language); From 90ca6aafe91b5eb29e23c640432fead3042b3b46 Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 24 Jan 2021 01:06:50 +0300 Subject: [PATCH 35/51] Set default language if the culture not supported --- Oqtane.Server/Pages/_Host.cshtml.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 662beb18..d93ead04 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -5,6 +5,7 @@ using Oqtane.Modules; using Oqtane.Models; using Oqtane.Themes; using System; +using System.Globalization; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Http.Extensions; @@ -45,18 +46,20 @@ namespace Oqtane.Pages // if framework is installed if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) { - Uri uri = new Uri(Request.GetDisplayUrl()); + var uri = new Uri(Request.GetDisplayUrl()); var alias = _aliases.GetAlias(uri.Authority + "/" + uri.LocalPath.Substring(1)); _state.Alias = alias; - // set default language for site - var language = _languages.GetLanguages(alias.SiteId).Where(item => item.IsDefault).FirstOrDefault(); - if (language != null) + // set default language for site if the culture is not supported + var languages = _languages.GetLanguages(alias.SiteId); + if (languages.All(l => l.Code != CultureInfo.CurrentUICulture.Name)) { + var defaultLanguage = languages.Where(l => l.IsDefault).SingleOrDefault() ?? languages.First(); + HttpContext.Response.Cookies.Append( CookieRequestCultureProvider.DefaultCookieName, CookieRequestCultureProvider.MakeCookieValue( - new RequestCulture(language.Code))); + new RequestCulture(defaultLanguage.Code))); } } } From 6fdbbeb8cee032e6a8581f448d5f57147ad041a8 Mon Sep 17 00:00:00 2001 From: Jayson Furr Date: Sat, 23 Jan 2021 18:24:07 -0600 Subject: [PATCH 36/51] Fixes to horizontal menu logic. Now supports two levels of menu items. --- .../Themes/Controls/MenuHorizontal.razor | 31 +------ .../Themes/Controls/MenuItemsBase.cs | 27 +++++++ .../Themes/Controls/MenuItemsHorizontal.razor | 80 +++++++++++++++++++ .../Controls/MenuItemsHorizontal.razor.cs | 6 ++ 4 files changed, 115 insertions(+), 29 deletions(-) create mode 100644 Oqtane.Client/Themes/Controls/MenuItemsBase.cs create mode 100644 Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor create mode 100644 Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor.cs diff --git a/Oqtane.Client/Themes/Controls/MenuHorizontal.razor b/Oqtane.Client/Themes/Controls/MenuHorizontal.razor index 575a513b..c4c45f16 100644 --- a/Oqtane.Client/Themes/Controls/MenuHorizontal.razor +++ b/Oqtane.Client/Themes/Controls/MenuHorizontal.razor @@ -1,4 +1,5 @@ @namespace Oqtane.Themes.Controls + @inherits MenuBase @if (MenuPages.Any()) @@ -10,35 +11,7 @@
} diff --git a/Oqtane.Client/Themes/Controls/MenuItemsBase.cs b/Oqtane.Client/Themes/Controls/MenuItemsBase.cs new file mode 100644 index 00000000..bfdd3cd4 --- /dev/null +++ b/Oqtane.Client/Themes/Controls/MenuItemsBase.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; + +using Microsoft.AspNetCore.Components; + +using Oqtane.Models; +using Oqtane.UI; + +namespace Oqtane.Themes.Controls +{ + public abstract class MenuItemsBase : MenuBase + { + [Parameter()] + public Page ParentPage { get; set; } + + [Parameter()] + public IEnumerable Pages { get; set; } + + protected IEnumerable GetChildPages() + { + return Pages + .Where(e => e.ParentId == ParentPage?.PageId) + .OrderBy(e => e.Order) + .AsEnumerable(); + } + } +} diff --git a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor new file mode 100644 index 00000000..3a54c855 --- /dev/null +++ b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor @@ -0,0 +1,80 @@ +@namespace Oqtane.Themes.Controls + +@inherits MenuItemsBase + +@if (ParentPage != null) +{ + +} +else +{ + +} \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor.cs b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor.cs new file mode 100644 index 00000000..30226bfb --- /dev/null +++ b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor.cs @@ -0,0 +1,6 @@ +namespace Oqtane.Themes.Controls +{ + public partial class MenuItemsHorizontal : MenuItemsBase + { + } +} From f60a4af6d2d2a0a1396d1652909c38dbfa00099b Mon Sep 17 00:00:00 2001 From: Jayson Furr Date: Sat, 23 Jan 2021 21:14:44 -0600 Subject: [PATCH 37/51] Fixes to horizontal menu logic. Now supports multiple levels of menu items. Added FontIcon component to reduce duplicate code. --- Oqtane.Client/Themes/Controls/FontIcon.razor | 4 ++ .../Themes/Controls/FontIcon.razor.cs | 10 +++ .../Themes/Controls/MenuItemsHorizontal.razor | 20 +++--- .../Themes/Controls/MenuItemsVertical.razor | 62 +++++++++++++++++++ .../Controls/MenuItemsVertical.razor.cs | 6 ++ .../Themes/Controls/MenuVertical.razor | 20 +----- 6 files changed, 92 insertions(+), 30 deletions(-) create mode 100644 Oqtane.Client/Themes/Controls/FontIcon.razor create mode 100644 Oqtane.Client/Themes/Controls/FontIcon.razor.cs create mode 100644 Oqtane.Client/Themes/Controls/MenuItemsVertical.razor create mode 100644 Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs diff --git a/Oqtane.Client/Themes/Controls/FontIcon.razor b/Oqtane.Client/Themes/Controls/FontIcon.razor new file mode 100644 index 00000000..04cd682a --- /dev/null +++ b/Oqtane.Client/Themes/Controls/FontIcon.razor @@ -0,0 +1,4 @@ +@if (!string.IsNullOrWhiteSpace(Value)) +{ + +} \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/FontIcon.razor.cs b/Oqtane.Client/Themes/Controls/FontIcon.razor.cs new file mode 100644 index 00000000..07ecda89 --- /dev/null +++ b/Oqtane.Client/Themes/Controls/FontIcon.razor.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Components; + +namespace Oqtane.Themes.Controls +{ + public partial class FontIcon : ComponentBase + { + [Parameter()] + public string Value { get; set; } + } +} diff --git a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor index 3a54c855..1ba03d4f 100644 --- a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor +++ b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor @@ -10,12 +10,14 @@ if (childPage.PageId == PageState.Page.PageId) { - @childPage.Name(current) + + @childPage.Name (current) } else { + @childPage.Name } @@ -33,11 +35,8 @@ else { } @@ -45,10 +44,7 @@ else { @@ -60,7 +56,8 @@ else { @@ -69,6 +66,7 @@ else { + } + else + { + + } + if (Pages.Any(e => e.ParentId == childPage.PageId)) + { + + } + } +} +else +{ + +} \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs b/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs new file mode 100644 index 00000000..85ba00b8 --- /dev/null +++ b/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs @@ -0,0 +1,6 @@ +namespace Oqtane.Themes.Controls +{ + public partial class MenuItemsVertical : MenuItemsBase + { + } +} diff --git a/Oqtane.Client/Themes/Controls/MenuVertical.razor b/Oqtane.Client/Themes/Controls/MenuVertical.razor index 79b20589..a552ea11 100644 --- a/Oqtane.Client/Themes/Controls/MenuVertical.razor +++ b/Oqtane.Client/Themes/Controls/MenuVertical.razor @@ -10,25 +10,7 @@ } From 5a02ce612481a1bc33fdc9e48ee35e02990fbe5a Mon Sep 17 00:00:00 2001 From: Jayson Furr Date: Sat, 23 Jan 2021 21:14:44 -0600 Subject: [PATCH 38/51] Fixes to vertical menu logic. Now supports multiple levels of menu items. Added FontIcon component to reduce duplicate code. --- Oqtane.Client/Themes/Controls/FontIcon.razor | 4 ++ .../Themes/Controls/FontIcon.razor.cs | 10 +++ .../Themes/Controls/MenuItemsHorizontal.razor | 20 +++--- .../Themes/Controls/MenuItemsVertical.razor | 62 +++++++++++++++++++ .../Controls/MenuItemsVertical.razor.cs | 6 ++ .../Themes/Controls/MenuVertical.razor | 20 +----- 6 files changed, 92 insertions(+), 30 deletions(-) create mode 100644 Oqtane.Client/Themes/Controls/FontIcon.razor create mode 100644 Oqtane.Client/Themes/Controls/FontIcon.razor.cs create mode 100644 Oqtane.Client/Themes/Controls/MenuItemsVertical.razor create mode 100644 Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs diff --git a/Oqtane.Client/Themes/Controls/FontIcon.razor b/Oqtane.Client/Themes/Controls/FontIcon.razor new file mode 100644 index 00000000..04cd682a --- /dev/null +++ b/Oqtane.Client/Themes/Controls/FontIcon.razor @@ -0,0 +1,4 @@ +@if (!string.IsNullOrWhiteSpace(Value)) +{ + +} \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/FontIcon.razor.cs b/Oqtane.Client/Themes/Controls/FontIcon.razor.cs new file mode 100644 index 00000000..07ecda89 --- /dev/null +++ b/Oqtane.Client/Themes/Controls/FontIcon.razor.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Components; + +namespace Oqtane.Themes.Controls +{ + public partial class FontIcon : ComponentBase + { + [Parameter()] + public string Value { get; set; } + } +} diff --git a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor index 3a54c855..1ba03d4f 100644 --- a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor +++ b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor @@ -10,12 +10,14 @@ if (childPage.PageId == PageState.Page.PageId) { - @childPage.Name(current) + + @childPage.Name (current) } else { + @childPage.Name } @@ -33,11 +35,8 @@ else { } @@ -45,10 +44,7 @@ else { @@ -60,7 +56,8 @@ else { @@ -69,6 +66,7 @@ else { + } + else + { + + } + if (Pages.Any(e => e.ParentId == childPage.PageId)) + { + + } + } +} +else +{ + +} \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs b/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs new file mode 100644 index 00000000..85ba00b8 --- /dev/null +++ b/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs @@ -0,0 +1,6 @@ +namespace Oqtane.Themes.Controls +{ + public partial class MenuItemsVertical : MenuItemsBase + { + } +} diff --git a/Oqtane.Client/Themes/Controls/MenuVertical.razor b/Oqtane.Client/Themes/Controls/MenuVertical.razor index 79b20589..a552ea11 100644 --- a/Oqtane.Client/Themes/Controls/MenuVertical.razor +++ b/Oqtane.Client/Themes/Controls/MenuVertical.razor @@ -10,25 +10,7 @@ } From 531cba715e3d3ca654565edd9a5baf5a36d9a09d Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 4 Feb 2021 08:54:59 -0500 Subject: [PATCH 39/51] performance and user experience improvements --- .../Admin/ModuleDefinitions/Index.razor | 5 +- Oqtane.Client/Modules/Admin/Site/Index.razor | 241 ++++++++++-------- Oqtane.Client/Modules/Admin/Sites/Add.razor | 18 ++ Oqtane.Client/Modules/Admin/Sites/Edit.razor | 60 ++++- .../Modules/Admin/Tenants/Edit.razor | 86 ------- .../Modules/Admin/Tenants/Index.razor | 68 ----- .../Modules/Admin/Themes/Index.razor | 5 +- Oqtane.Client/Modules/Controls/Pager.razor | 104 ++++++-- Oqtane.Client/Themes/Controls/FontIcon.razor | 8 + .../Themes/Controls/FontIcon.razor.cs | 10 - .../Themes/Controls/MenuItemsHorizontal.razor | 3 +- .../Controls/MenuItemsHorizontal.razor.cs | 6 - .../Themes/Controls/MenuItemsVertical.razor | 1 - .../Controls/MenuItemsVertical.razor.cs | 6 - Oqtane.Client/UI/ContainerBuilder.razor | 4 +- .../Controllers/ModuleDefinitionController.cs | 72 +++--- Oqtane.Server/Controllers/ThemeController.cs | 30 ++- .../Infrastructure/InstallationManager.cs | 28 +- .../Infrastructure/Jobs/HostedServiceBase.cs | 2 +- Oqtane.Server/Oqtane.Server.csproj | 1 + Oqtane.Server/Pages/_Host.cshtml.cs | 11 +- .../Interfaces/IModuleDefinitionRepository.cs | 4 +- .../Repository/Interfaces/IThemeRepository.cs | 3 +- .../Repository/ModuleDefinitionRepository.cs | 114 +++++---- Oqtane.Server/Repository/SiteRepository.cs | 26 -- Oqtane.Server/Repository/ThemeRepository.cs | 33 ++- Oqtane.Server/Scripts/Tenant.02.00.01.02.sql | 2 +- Oqtane.Server/Scripts/Tenant.02.00.01.03.sql | 17 ++ Oqtane.Server/Startup.cs | 4 +- Oqtane.Shared/Models/Site.cs | 3 +- Oqtane.Shared/Shared/InstallConfig.cs | 3 +- 31 files changed, 494 insertions(+), 484 deletions(-) delete mode 100644 Oqtane.Client/Modules/Admin/Tenants/Edit.razor delete mode 100644 Oqtane.Client/Modules/Admin/Tenants/Index.razor delete mode 100644 Oqtane.Client/Themes/Controls/FontIcon.razor.cs delete mode 100644 Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor.cs delete mode 100644 Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs create mode 100644 Oqtane.Server/Scripts/Tenant.02.00.01.03.sql diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor index a3f0b71e..538bf700 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor @@ -47,7 +47,7 @@ else public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - protected override async Task OnInitializedAsync() + protected override async Task OnParametersSetAsync() { try { @@ -100,7 +100,8 @@ else try { await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId); - AddModuleMessage(Localizer["Module Deleted Successfully. You Must Restart Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success); + AddModuleMessage(Localizer["Module Deleted Successfully"], MessageType.Success); + StateHasChanged(); } catch (Exception ex) { diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 63e58889..acd0105b 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -10,122 +10,137 @@ @if (_initialized) { - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + +
- - - -
- - - -
- - - -
- - - -
- - - -
- - - + + + + + + + + + + + + + + + + + + + + + + + + + @if (_layouts.Count > 0) + { + + + - @if (_layouts.Count > 0) - { - - - - - } - - - - - - - - - - - - -
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + +
- - - -
- - - -
- - - -
- - - -
+ } +
+ + + +
+ + + +
+ + + +
+ + + +
@@ -244,6 +259,7 @@ private string _themetype = "-"; private string _layouttype = "-"; private string _containertype = "-"; + private string _admincontainertype = "-"; private string _allowregistration; private string _smtphost = string.Empty; private string _smtpport = string.Empty; @@ -298,6 +314,7 @@ _layouttype = site.DefaultLayoutType; _containers = ThemeService.GetContainerControls(_themeList, _themetype); _containertype = site.DefaultContainerType; + _admincontainertype = site.AdminContainerType; _allowregistration = site.AllowRegistration.ToString(); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); @@ -365,6 +382,7 @@ } _layouttype = "-"; _containertype = "-"; + _admincontainertype = ""; StateHasChanged(); } catch (Exception ex) @@ -405,6 +423,7 @@ site.DefaultThemeType = _themetype; site.DefaultLayoutType = (_layouttype == "-" ? string.Empty : _layouttype); site.DefaultContainerType = _containertype; + site.AdminContainerType = _admincontainertype; site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration)); site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index 2945f23a..a4b430e9 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -78,6 +78,21 @@ else + + + + - - - - + + + + + + + + + + + + +
+ + + +
@@ -225,6 +240,7 @@ else private string _themetype = "-"; private string _layouttype = "-"; private string _containertype = "-"; + private string _admincontainertype = ""; private string _sitetemplatetype = "-"; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; @@ -278,6 +294,7 @@ else } _layouttype = "-"; _containertype = "-"; + _admincontainertype = ""; StateHasChanged(); } catch (Exception ex) @@ -378,6 +395,7 @@ else config.DefaultTheme = _themetype; config.DefaultLayout = _layouttype; config.DefaultContainer = _containertype; + config.DefaultAdminContainer = _admincontainertype; config.SiteTemplate = _sitetemplatetype; ShowProgressIndicator(); diff --git a/Oqtane.Client/Modules/Admin/Sites/Edit.razor b/Oqtane.Client/Modules/Admin/Sites/Edit.razor index 3d01cf2e..b1afc85c 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Edit.razor @@ -18,14 +18,6 @@
- - - -
@@ -86,6 +78,21 @@
+ + + +
@@ -97,6 +104,23 @@
+ + + +
+ + + +

@@ -114,13 +138,12 @@ private List _containers = new List(); private Alias _alias; private string _name = string.Empty; - private List _tenantList; - private string _tenant = string.Empty; private List _aliasList; private string _urls = string.Empty; private string _themetype; private string _layouttype; - private string _containertype; + private string _containertype = "-"; + private string _admincontainertype = "-"; private string _createdby; private DateTime _createdon; private string _modifiedby; @@ -128,6 +151,8 @@ private string _deletedby; private DateTime? _deletedon; private string _isdeleted; + private string _tenant = string.Empty; + private string _connectionstring = string.Empty; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; @@ -144,8 +169,6 @@ if (site != null) { _name = site.Name; - _tenantList = await TenantService.GetTenantsAsync(); - _tenant = _tenantList.Find(item => item.TenantId == site.TenantId).Name; foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList()) { @@ -158,6 +181,7 @@ _layouttype = site.DefaultLayoutType; _containers = ThemeService.GetContainerControls(_themeList, _themetype); _containertype = site.DefaultContainerType; + _admincontainertype = site.AdminContainerType; _createdby = site.CreatedBy; _createdon = site.CreatedOn; _modifiedby = site.ModifiedBy; @@ -166,6 +190,14 @@ _deletedon = site.DeletedOn; _isdeleted = site.IsDeleted.ToString(); + List tenants = await TenantService.GetTenantsAsync(); + Tenant tenant = tenants.Find(item => item.TenantId == site.TenantId); + if (tenant != null) + { + _tenant = tenant.Name; + _connectionstring = tenant.DBConnectionString; + } + _initialized = true; } } @@ -193,6 +225,7 @@ } _layouttype = "-"; _containertype = "-"; + _admincontainertype = ""; StateHasChanged(); } catch (Exception ex) @@ -228,6 +261,7 @@ site.DefaultThemeType = _themetype; site.DefaultLayoutType = _layouttype ?? string.Empty; site.DefaultContainerType = _containertype; + site.AdminContainerType = _admincontainertype; site.IsDeleted = (_isdeleted == null || Boolean.Parse(_isdeleted)); site = await SiteService.UpdateSiteAsync(site); diff --git a/Oqtane.Client/Modules/Admin/Tenants/Edit.razor b/Oqtane.Client/Modules/Admin/Tenants/Edit.razor deleted file mode 100644 index 247b338a..00000000 --- a/Oqtane.Client/Modules/Admin/Tenants/Edit.razor +++ /dev/null @@ -1,86 +0,0 @@ -@namespace Oqtane.Modules.Admin.Tenants -@inherits ModuleBase -@inject NavigationManager NavigationManager -@inject ITenantService TenantService -@inject IStringLocalizer Localizer - - - - - - - - - - - -
- - - @if (name == TenantNames.Master) - { - - } - else - { - - } -
- - - -
- -@Localizer["Cancel"] - -@code { - private int tenantid; - private string name = string.Empty; - private string connectionstring = string.Empty; - private string schema = string.Empty; - - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - - protected override async Task OnInitializedAsync() - { - try - { - tenantid = Int32.Parse(PageState.QueryString["id"]); - var tenant = await TenantService.GetTenantAsync(tenantid); - if (tenant != null) - { - name = tenant.Name; - connectionstring = tenant.DBConnectionString; - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading Tenant {TenantId} {Error}", tenantid, ex.Message); - AddModuleMessage(Localizer["Error Loading Tenant"], MessageType.Error); - } - } - - private async Task SaveTenant() - { - try - { - connectionstring = connectionstring.Replace("\\\\", "\\"); - var tenant = await TenantService.GetTenantAsync(tenantid); - if (tenant != null) - { - tenant.Name = name; - tenant.DBConnectionString = connectionstring; - - await TenantService.UpdateTenantAsync(tenant); - await logger.LogInformation("Tenant Saved {TenantId}", tenantid); - - NavigationManager.NavigateTo(NavigateUrl()); - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Saving Tenant {TenantId} {Error}", tenantid, ex.Message); - AddModuleMessage(Localizer["Error Saving Tenant"], MessageType.Error); - } - } -} diff --git a/Oqtane.Client/Modules/Admin/Tenants/Index.razor b/Oqtane.Client/Modules/Admin/Tenants/Index.razor deleted file mode 100644 index 2aa70ecb..00000000 --- a/Oqtane.Client/Modules/Admin/Tenants/Index.razor +++ /dev/null @@ -1,68 +0,0 @@ -@namespace Oqtane.Modules.Admin.Tenants -@inherits ModuleBase -@inject ITenantService TenantService -@inject IAliasService AliasService -@inject IStringLocalizer Localizer - -@if (tenants == null) -{ -

@Localizer["Loading..."]

-} -else -{ - -
-   -   - @Localizer["Name"] -
- - - - @context.Name - -
- -} - -@code { - private List tenants; - - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - - protected override async Task OnParametersSetAsync() - { - tenants = await TenantService.GetTenantsAsync(); - } - - private async Task DeleteTenant(Tenant Tenant) - { - try - { - string message = string.Empty; - var aliases = await AliasService.GetAliasesAsync(); - foreach (var alias in aliases) - { - if (alias.TenantId == Tenant.TenantId) - { - message += ", " + alias.Name; - } - } - if (string.IsNullOrEmpty(message)) - { - await TenantService.DeleteTenantAsync(Tenant.TenantId); - await logger.LogInformation("Tenant Deleted {Tenant}", Tenant); - StateHasChanged(); - } - else - { - AddModuleMessage(Localizer["Tenant Cannot Be Deleted Until The Following Sites Are Deleted: {0}", message.Substring(2)], MessageType.Warning); - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Deleting Tenant {Tenant} {Error}", Tenant, ex.Message); - AddModuleMessage(Localizer["Error Deleting Tenant"], MessageType.Error); - } - } -} \ No newline at end of file diff --git a/Oqtane.Client/Modules/Admin/Themes/Index.razor b/Oqtane.Client/Modules/Admin/Themes/Index.razor index 816bd06f..9e8677f7 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Index.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Index.razor @@ -49,7 +49,7 @@ else public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - protected override async Task OnInitializedAsync() + protected override async Task OnParametersSetAsync() { try { @@ -101,7 +101,8 @@ else try { await ThemeService.DeleteThemeAsync(Theme.ThemeName); - AddModuleMessage(Localizer["Theme Deleted Successfully. You Must Restart Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success); + AddModuleMessage(Localizer["Theme Deleted Successfully"], MessageType.Success); + StateHasChanged(); } catch (Exception ex) { diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index 5f3ae713..64f32728 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -3,6 +3,43 @@ @typeparam TableItem

+ @if (Toolbar == "Top") + { +

+ @if (_endPage > 1) + { + + } + @if (_page > _maxPages) + { + + } + @if (_endPage > 1) + { + + @for (int i = _startPage; i <= _endPage; i++) + { + var pager = i; + + } + + } + @if (_endPage < _pages) + { + + } + @if (_endPage > 1) + { + + } + @if (_endPage > 1) + { + Page @_page of @_pages + } +
+ } @if (Format == "Table") { @@ -35,32 +72,43 @@ } } -
- @if (_page > _maxPages) - { - - } - @if (_endPage > 1) - { - - @for (int i = _startPage; i <= _endPage; i++) + @if (Toolbar == "Bottom") + { +
+ @if (_endPage > 1) { - var pager = i; - + } - - } - @if (_endPage < _pages) - { - - } - @if (_endPage > 1) - { - Page @_page of @_pages - } -
+ @if (_page > _maxPages) + { + + } + @if (_endPage > 1) + { + + @for (int i = _startPage; i <= _endPage; i++) + { + var pager = i; + + } + + } + @if (_endPage < _pages) + { + + } + @if (_endPage > 1) + { + + } + @if (_endPage > 1) + { + Page @_page of @_pages + } +
+ }

@code { @@ -74,6 +122,9 @@ [Parameter] public string Format { get; set; } + [Parameter] + public string Toolbar { get; set; } + [Parameter] public RenderFragment Header { get; set; } @@ -104,6 +155,11 @@ Format = "Table"; } + if (string.IsNullOrEmpty(Toolbar)) + { + Toolbar = "Top"; + } + if (string.IsNullOrEmpty(Class)) { if (Format == "Table") diff --git a/Oqtane.Client/Themes/Controls/FontIcon.razor b/Oqtane.Client/Themes/Controls/FontIcon.razor index 04cd682a..04aad17e 100644 --- a/Oqtane.Client/Themes/Controls/FontIcon.razor +++ b/Oqtane.Client/Themes/Controls/FontIcon.razor @@ -1,4 +1,12 @@ +@namespace Oqtane.Themes.Controls +@inherits ThemeControlBase + @if (!string.IsNullOrWhiteSpace(Value)) { +} + +@code { + [Parameter()] + public string Value { get; set; } } \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/FontIcon.razor.cs b/Oqtane.Client/Themes/Controls/FontIcon.razor.cs deleted file mode 100644 index 07ecda89..00000000 --- a/Oqtane.Client/Themes/Controls/FontIcon.razor.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.AspNetCore.Components; - -namespace Oqtane.Themes.Controls -{ - public partial class FontIcon : ComponentBase - { - [Parameter()] - public string Value { get; set; } - } -} diff --git a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor index 1ba03d4f..840fbf4e 100644 --- a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor +++ b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor @@ -1,5 +1,4 @@ @namespace Oqtane.Themes.Controls - @inherits MenuItemsBase @if (ParentPage != null) @@ -75,4 +74,4 @@ else } } -} \ No newline at end of file +} diff --git a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor.cs b/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor.cs deleted file mode 100644 index 30226bfb..00000000 --- a/Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Oqtane.Themes.Controls -{ - public partial class MenuItemsHorizontal : MenuItemsBase - { - } -} diff --git a/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor b/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor index ab6c92d8..a27f9a7a 100644 --- a/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor +++ b/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor @@ -1,5 +1,4 @@ @namespace Oqtane.Themes.Controls - @inherits MenuItemsBase @if (ParentPage != null) diff --git a/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs b/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs deleted file mode 100644 index 85ba00b8..00000000 --- a/Oqtane.Client/Themes/Controls/MenuItemsVertical.razor.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Oqtane.Themes.Controls -{ - public partial class MenuItemsVertical : MenuItemsBase - { - } -} diff --git a/Oqtane.Client/UI/ContainerBuilder.razor b/Oqtane.Client/UI/ContainerBuilder.razor index 5a164309..41f8268a 100644 --- a/Oqtane.Client/UI/ContainerBuilder.razor +++ b/Oqtane.Client/UI/ContainerBuilder.razor @@ -1,4 +1,4 @@ -@namespace Oqtane.UI +@namespace Oqtane.UI @DynamicComponent @@ -21,7 +21,7 @@ string container = _moduleState.ContainerType; if (PageState.ModuleId != -1 && _moduleState.UseAdminContainer) { - container = Constants.DefaultAdminContainer; + container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer; } DynamicComponent = builder => diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 299ad601..dc371214 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -101,13 +101,12 @@ namespace Oqtane.Controllers public void Delete(int id, int siteid) { ModuleDefinition moduledefinition = _moduleDefinitions.GetModuleDefinition(id, siteid); - if (moduledefinition != null ) + if (moduledefinition != null && Utilities.GetAssemblyName(moduledefinition.ServerManagerType) != "Oqtane.Server") { - if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType) && Utilities.GetAssemblyName(moduledefinition.ServerManagerType) != "Oqtane.Server") + // execute uninstall logic or scripts + if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType)) { Type moduletype = Type.GetType(moduledefinition.ServerManagerType); - - // execute uninstall logic foreach (Tenant tenant in _tenants.GetTenants()) { try @@ -128,34 +127,45 @@ namespace Oqtane.Controllers _logger.Log(LogLevel.Error, this, LogFunction.Delete, "Error Uninstalling {ModuleDefinitionName} For Tenant {Tenant} {Error}", moduledefinition.ModuleDefinitionName, tenant.Name, ex.Message); } } - - // use assets.json to clean up file resources - string assetfilepath = Path.Combine(_environment.WebRootPath, "Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName), "assets.json"); - if (System.IO.File.Exists(assetfilepath)) - { - List assets = JsonSerializer.Deserialize>(System.IO.File.ReadAllText(assetfilepath)); - foreach(string asset in assets) - { - if (System.IO.File.Exists(asset)) - { - System.IO.File.Delete(asset); - } - } - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assets Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName); - } - - // clean up module static resource folder - string folder = Path.Combine(_environment.WebRootPath, Path.Combine("Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName))); - if (Directory.Exists(folder)) - { - Directory.Delete(folder, true); - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Resources Folder Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName); - } - - // remove module definition - _moduleDefinitions.DeleteModuleDefinition(id, siteid); - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name); } + + // remove module assets + string assetpath = Path.Combine(_environment.WebRootPath, "Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName)); + if (System.IO.File.Exists(Path.Combine(assetpath, "assets.json"))) + { + // use assets.json to clean up file resources + List assets = JsonSerializer.Deserialize>(System.IO.File.ReadAllText(Path.Combine(assetpath, "assets.json"))); + foreach(string asset in assets) + { + // legacy support for assets that were stored as absolute paths + string filepath = asset.StartsWith("\\") ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset; + if (System.IO.File.Exists(filepath)) + { + System.IO.File.Delete(filepath); + } + } + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assets Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName); + } + else + { + // attempt to delete assemblies based on naming convention + foreach(string asset in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + "*.*")) + { + System.IO.File.Delete(asset); + } + _logger.Log(LogLevel.Warning, this, LogFunction.Delete, "Module Assets Removed For {ModuleDefinitionName}. Please Note That Some Assets May Have Been Missed Due To A Missing Asset Manifest. An Asset Manifest Is Only Created If A Module Is Installed From A Nuget Package.", moduledefinition.Name); + } + + // clean up module static resource folder + if (Directory.Exists(assetpath)) + { + Directory.Delete(assetpath, true); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Folder Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName); + } + + // remove module definition + _moduleDefinitions.DeleteModuleDefinition(id); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name); } } diff --git a/Oqtane.Server/Controllers/ThemeController.cs b/Oqtane.Server/Controllers/ThemeController.cs index 07ed6152..615080e9 100644 --- a/Oqtane.Server/Controllers/ThemeController.cs +++ b/Oqtane.Server/Controllers/ThemeController.cs @@ -57,28 +57,44 @@ namespace Oqtane.Controllers Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault(); if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != "Oqtane.Client") { - // use assets.json to clean up file resources - string assetfilepath = Path.Combine(_environment.WebRootPath, "Themes", Utilities.GetTypeName(theme.ThemeName), "assets.json"); - if (System.IO.File.Exists(assetfilepath)) + // remove theme assets + string assetpath = Path.Combine(_environment.WebRootPath, "Themes", Utilities.GetTypeName(theme.ThemeName)); + if (System.IO.File.Exists(Path.Combine(assetpath, "assets.json"))) { - List assets = JsonSerializer.Deserialize>(System.IO.File.ReadAllText(assetfilepath)); + // use assets.json to clean up file resources + List assets = JsonSerializer.Deserialize>(System.IO.File.ReadAllText(Path.Combine(assetpath, "assets.json"))); foreach (string asset in assets) { - if (System.IO.File.Exists(asset)) + // legacy support for assets that were stored as absolute paths + string filepath = (asset.StartsWith("\\")) ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset; + if (System.IO.File.Exists(filepath)) { - System.IO.File.Delete(asset); + System.IO.File.Delete(filepath); } } _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Assets Removed For {ThemeName}", theme.ThemeName); } + else + { + // attempt to delete assemblies based on naming convention + foreach (string asset in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(theme.ThemeName) + "*.*")) + { + System.IO.File.Delete(asset); + } + _logger.Log(LogLevel.Warning, this, LogFunction.Delete, "Theme Assets Removed For {ThemeName}. Please Note That Some Assets May Have Been Missed Due To A Missing Asset Manifest. An Asset Manifest Is Only Created If A Theme Is Installed From A Nuget Package.", theme.ThemeName); + } // clean up theme static resource folder string folder = Path.Combine(_environment.WebRootPath, "Themes" , Utilities.GetTypeName(theme.ThemeName)); if (Directory.Exists(folder)) { Directory.Delete(folder, true); - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Resource Folder Removed For {ThemeName}", theme.ThemeName); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Static Resource Folder Removed For {ThemeName}", theme.ThemeName); } + + // remove theme + _themes.DeleteTheme(theme.ThemeName); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Removed For {ThemeName}", theme.ThemeName); } } diff --git a/Oqtane.Server/Infrastructure/InstallationManager.cs b/Oqtane.Server/Infrastructure/InstallationManager.cs index 60910484..f14e8ae1 100644 --- a/Oqtane.Server/Infrastructure/InstallationManager.cs +++ b/Oqtane.Server/Infrastructure/InstallationManager.cs @@ -28,13 +28,13 @@ namespace Oqtane.Infrastructure public void InstallPackages(string folders) { - if (!InstallPackages(folders, _environment.WebRootPath)) + if (!InstallPackages(folders, _environment.WebRootPath, _environment.ContentRootPath)) { // error installing packages } } - public static bool InstallPackages(string folders, string webRootPath) + public static bool InstallPackages(string folders, string webRootPath, string contentRootPath) { bool install = false; string binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); @@ -79,6 +79,7 @@ namespace Oqtane.Infrastructure if (frameworkversion == "" || Version.Parse(Constants.Version).CompareTo(Version.Parse(frameworkversion)) >= 0) { List assets = new List(); + bool manifest = false; // module and theme packages must be in form of name.1.0.0.nupkg string name = Path.GetFileNameWithoutExtension(packagename); @@ -91,36 +92,41 @@ namespace Oqtane.Infrastructure string foldername = Path.GetDirectoryName(entry.FullName).Split(Path.DirectorySeparatorChar)[0]; string filename = Path.GetFileName(entry.FullName); + if (!manifest && filename == "assets.json") + { + manifest = true; + } + switch (foldername) { case "lib": filename = Path.Combine(binFolder, filename); ExtractFile(entry, filename); - assets.Add(filename); + assets.Add(filename.Replace(contentRootPath, "")); break; case "wwwroot": filename = Path.Combine(webRootPath, Utilities.PathCombine(entry.FullName.Replace("wwwroot/", "").Split('/'))); ExtractFile(entry, filename); - assets.Add(filename); + assets.Add(filename.Replace(contentRootPath, "")); break; case "runtimes": var destSubFolder = Path.GetDirectoryName(entry.FullName); filename = Path.Combine(binFolder, destSubFolder, filename); ExtractFile(entry, filename); - assets.Add(filename); + assets.Add(filename.Replace(contentRootPath, "")); break; } } - // save list of assets - if (assets.Count != 0) + // save dynamic list of assets + if (!manifest && assets.Count != 0) { - string assetfilepath = Path.Combine(webRootPath, folder, name, "assets.json"); - if (File.Exists(assetfilepath)) + string manifestpath = Path.Combine(webRootPath, folder, name, "assets.json"); + if (File.Exists(manifestpath)) { - File.Delete(assetfilepath); + File.Delete(manifestpath); } - File.WriteAllText(assetfilepath, JsonSerializer.Serialize(assets)); + File.WriteAllText(manifestpath, JsonSerializer.Serialize(assets)); } } } diff --git a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs index 36fbefb6..449f7ae3 100644 --- a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs +++ b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs @@ -181,7 +181,7 @@ namespace Oqtane.Infrastructure } else { - // auto registration + // auto registration - does not run on initial installation but will run after restart job = new Job { JobType = jobTypeName }; // optional properties var jobType = Type.GetType(jobTypeName); diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 1ac18c29..15ca0a15 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -36,6 +36,7 @@ + diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 662beb18..ee7c43ce 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -42,8 +42,8 @@ namespace Oqtane.Pages ProcessThemeControls(assembly); } - // if framework is installed - if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) + // if culture not specified and framework is installed + if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null && !string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) { Uri uri = new Uri(Request.GetDisplayUrl()); var alias = _aliases.GetAlias(uri.Authority + "/" + uri.LocalPath.Substring(1)); @@ -58,6 +58,13 @@ namespace Oqtane.Pages CookieRequestCultureProvider.MakeCookieValue( new RequestCulture(language.Code))); } + else + { + HttpContext.Response.Cookies.Append( + CookieRequestCultureProvider.DefaultCookieName, + CookieRequestCultureProvider.MakeCookieValue( + new RequestCulture(_configuration.GetSection("Localization").GetValue("DefaultCulture", Constants.DefaultCulture)))); + } } } diff --git a/Oqtane.Server/Repository/Interfaces/IModuleDefinitionRepository.cs b/Oqtane.Server/Repository/Interfaces/IModuleDefinitionRepository.cs index dd516461..48de4c8a 100644 --- a/Oqtane.Server/Repository/Interfaces/IModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IModuleDefinitionRepository.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Oqtane.Models; namespace Oqtane.Repository @@ -9,6 +9,6 @@ namespace Oqtane.Repository IEnumerable GetModuleDefinitions(int sideId); ModuleDefinition GetModuleDefinition(int moduleDefinitionId, int sideId); void UpdateModuleDefinition(ModuleDefinition moduleDefinition); - void DeleteModuleDefinition(int moduleDefinitionId, int siteId); + void DeleteModuleDefinition(int moduleDefinitionId); } } diff --git a/Oqtane.Server/Repository/Interfaces/IThemeRepository.cs b/Oqtane.Server/Repository/Interfaces/IThemeRepository.cs index 90afa3bd..61dfc677 100644 --- a/Oqtane.Server/Repository/Interfaces/IThemeRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IThemeRepository.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Oqtane.Models; namespace Oqtane.Repository @@ -6,5 +6,6 @@ namespace Oqtane.Repository public interface IThemeRepository { IEnumerable GetThemes(); + void DeleteTheme(string ThemeName); } } diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index b0d21ff2..5a510c4c 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore; @@ -16,7 +17,6 @@ namespace Oqtane.Repository private MasterDBContext _db; private readonly IMemoryCache _cache; private readonly IPermissionRepository _permissions; - private List _moduleDefinitions; // lazy load public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions) { @@ -46,44 +46,71 @@ namespace Oqtane.Repository _db.Entry(moduleDefinition).State = EntityState.Modified; _db.SaveChanges(); _permissions.UpdatePermissions(moduleDefinition.SiteId, EntityNames.ModuleDefinition, moduleDefinition.ModuleDefinitionId, moduleDefinition.Permissions); - _cache.Remove("moduledefinitions:" + moduleDefinition.SiteId.ToString()); } - public void DeleteModuleDefinition(int moduleDefinitionId, int siteId) + public void DeleteModuleDefinition(int moduleDefinitionId) { ModuleDefinition moduleDefinition = _db.ModuleDefinition.Find(moduleDefinitionId); - _permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduleDefinitionId); _db.ModuleDefinition.Remove(moduleDefinition); _db.SaveChanges(); + _cache.Remove("moduledefinitions"); } public List LoadModuleDefinitions(int siteId) { - // get module definitions for site - List moduleDefinitions = _cache.GetOrCreate("moduledefinitions:" + siteId.ToString(), entry => + // get module definitions + List moduleDefinitions; + if (siteId != -1) { - entry.SlidingExpiration = TimeSpan.FromMinutes(30); - return LoadSiteModuleDefinitions(siteId); - }); + moduleDefinitions = _cache.GetOrCreate("moduledefinitions", entry => + { + entry.SlidingExpiration = TimeSpan.FromMinutes(30); + return LoadModuleDefinitions(); + }); + + // get all module definition permissions for site + List permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList(); + + // populate module definition permissions + foreach (ModuleDefinition moduledefinition in moduleDefinitions) + { + moduledefinition.SiteId = siteId; + if (permissions.Count == 0) + { + _permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, moduledefinition.Permissions); + } + else + { + if (permissions.Where(item => item.EntityId == moduledefinition.ModuleDefinitionId).Any()) + { + moduledefinition.Permissions = permissions.Where(item => item.EntityId == moduledefinition.ModuleDefinitionId).EncodePermissions(); + } + else + { + _permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, moduledefinition.Permissions); + } + } + } + + // clean up any orphaned permissions + var ids = new HashSet(moduleDefinitions.Select(item => item.ModuleDefinitionId)); + foreach (var permission in permissions.Where(item => !ids.Contains(item.EntityId))) + { + _permissions.DeletePermission(permission.PermissionId); + } + } + else + { + moduleDefinitions = LoadModuleDefinitions(); + } + return moduleDefinitions; } - private List LoadSiteModuleDefinitions(int siteId) + private List LoadModuleDefinitions() { - if (_moduleDefinitions == null) - { - // get module assemblies - _moduleDefinitions = LoadModuleDefinitionsFromAssemblies(); - } - - List moduleDefinitions = _moduleDefinitions; - - List permissions = new List(); - if (siteId != -1) - { - // get module definition permissions for site - permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList(); - } + // get module assemblies + List moduleDefinitions = LoadModuleDefinitionsFromAssemblies(); // get module definitions in database List moduledefs = _db.ModuleDefinition.ToList(); @@ -95,13 +122,9 @@ namespace Oqtane.Repository if (moduledef == null) { // new module definition - moduledef = new ModuleDefinition {ModuleDefinitionName = moduledefinition.ModuleDefinitionName}; + moduledef = new ModuleDefinition { ModuleDefinitionName = moduledefinition.ModuleDefinitionName }; _db.ModuleDefinition.Add(moduledef); _db.SaveChanges(); - if (siteId != -1) - { - _permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions); - } } else { @@ -126,31 +149,11 @@ namespace Oqtane.Repository moduledefinition.Version = moduledef.Version; } - if (siteId != -1) - { - if (permissions.Count == 0) - { - _permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions); - } - else - { - if (permissions.Where(item => item.EntityId == moduledef.ModuleDefinitionId).Any()) - { - moduledefinition.Permissions = permissions.Where(item => item.EntityId == moduledef.ModuleDefinitionId).EncodePermissions(); - } - else - { - _permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions); - } - } - } - // remove module definition from list as it is already synced moduledefs.Remove(moduledef); } moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId; - moduledefinition.SiteId = siteId; moduledefinition.CreatedBy = moduledef.CreatedBy; moduledefinition.CreatedOn = moduledef.CreatedOn; moduledefinition.ModifiedBy = moduledef.ModifiedBy; @@ -160,11 +163,6 @@ namespace Oqtane.Repository // any remaining module definitions are orphans foreach (ModuleDefinition moduledefinition in moduledefs) { - if (siteId != -1) - { - _permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId); - } - _db.ModuleDefinition.Remove(moduledefinition); // delete _db.SaveChanges(); } @@ -175,11 +173,15 @@ namespace Oqtane.Repository private List LoadModuleDefinitionsFromAssemblies() { List moduleDefinitions = new List(); + // iterate through Oqtane module assemblies var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); foreach (Assembly assembly in assemblies) { - moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly); + if (System.IO.File.Exists(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(assembly.FullName) + ".dll"))) + { + moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly); + } } return moduleDefinitions; diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 774d0330..ba23d218 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -408,32 +408,6 @@ namespace Oqtane.Repository Content = "" } } - }); pageTemplates.Add(new PageTemplate - { - Name = "Tenant Management", - Parent = "Admin", - Path = "admin/tenants", - Icon = Icons.List, - IsNavigation = false, - IsPersonalizable = false, - PagePermissions = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }.EncodePermissions(), - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Tenants.Index).ToModuleDefinitionName(), Title = "Tenant Management", Pane = "Content", - ModulePermissions = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }.EncodePermissions(), - Content = "" - } - } }); pageTemplates.Add(new PageTemplate { diff --git a/Oqtane.Server/Repository/ThemeRepository.cs b/Oqtane.Server/Repository/ThemeRepository.cs index 24b57598..f85b7cd7 100644 --- a/Oqtane.Server/Repository/ThemeRepository.cs +++ b/Oqtane.Server/Repository/ThemeRepository.cs @@ -1,7 +1,9 @@ -using System; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; +using Microsoft.Extensions.Caching.Memory; using Oqtane.Models; using Oqtane.Shared; using Oqtane.Themes; @@ -10,7 +12,12 @@ namespace Oqtane.Repository { public class ThemeRepository : IThemeRepository { - private List _themes; // lazy load + private readonly IMemoryCache _cache; + + public ThemeRepository(IMemoryCache cache) + { + _cache = cache; + } public IEnumerable GetThemes() { @@ -19,12 +26,14 @@ namespace Oqtane.Repository private List LoadThemes() { - if (_themes == null) + // get module definitions + List themes = _cache.GetOrCreate("themes", entry => { - // get themes - _themes = LoadThemesFromAssemblies(); - } - return _themes; + entry.SlidingExpiration = TimeSpan.FromMinutes(30); + return LoadThemesFromAssemblies(); + }); + + return themes; } private List LoadThemesFromAssemblies() @@ -35,7 +44,10 @@ namespace Oqtane.Repository var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); foreach (Assembly assembly in assemblies) { - themes = LoadThemesFromAssembly(themes, assembly); + if (System.IO.File.Exists(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(assembly.FullName) + ".dll"))) + { + themes = LoadThemesFromAssembly(themes, assembly); + } } return themes; @@ -143,5 +155,10 @@ namespace Oqtane.Repository } return themes; } + + public void DeleteTheme(string ThemeName) + { + _cache.Remove("themes"); + } } } diff --git a/Oqtane.Server/Scripts/Tenant.02.00.01.02.sql b/Oqtane.Server/Scripts/Tenant.02.00.01.02.sql index 3f3f4976..6f12d62e 100644 --- a/Oqtane.Server/Scripts/Tenant.02.00.01.02.sql +++ b/Oqtane.Server/Scripts/Tenant.02.00.01.02.sql @@ -1,6 +1,6 @@ /* -Version 2.0.0 Tenant migration script +Version 2.0.1 Tenant migration script */ diff --git a/Oqtane.Server/Scripts/Tenant.02.00.01.03.sql b/Oqtane.Server/Scripts/Tenant.02.00.01.03.sql new file mode 100644 index 00000000..4b1d2876 --- /dev/null +++ b/Oqtane.Server/Scripts/Tenant.02.00.01.03.sql @@ -0,0 +1,17 @@ +/* + +Version 2.0.1 Tenant migration script + +*/ + +DELETE FROM [dbo].[Page] +WHERE Path = 'admin/tenants'; +GO + +ALTER TABLE [dbo].[Site] ADD + [AdminContainerType] [nvarchar](200) NULL +GO + +UPDATE [dbo].[Site] SET AdminContainerType = '' +GO + diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index babe9245..ba2a7d89 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -26,7 +26,6 @@ namespace Oqtane { public class Startup { - private string _webRoot; private Runtime _runtime; private bool _useSwagger; private IWebHostEnvironment _env; @@ -48,7 +47,6 @@ namespace Oqtane //add possibility to switch off swagger on production. _useSwagger = Configuration.GetSection("UseSwagger").Value != "false"; - _webRoot = env.WebRootPath; AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data")); _env = env; @@ -181,7 +179,7 @@ namespace Oqtane services.AddSingleton(); // install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain ) - InstallationManager.InstallPackages("Modules,Themes", _webRoot); + InstallationManager.InstallPackages("Modules,Themes", _env.WebRootPath, _env.ContentRootPath); // register transient scoped core services services.AddTransient(); diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index 4b6b7f86..49f3524e 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations.Schema; namespace Oqtane.Models @@ -13,6 +13,7 @@ namespace Oqtane.Models public string DefaultThemeType { get; set; } public string DefaultLayoutType { get; set; } public string DefaultContainerType { get; set; } + public string AdminContainerType { get; set; } public bool PwaIsEnabled { get; set; } public int? PwaAppIconFileId { get; set; } public int? PwaSplashIconFileId { get; set; } diff --git a/Oqtane.Shared/Shared/InstallConfig.cs b/Oqtane.Shared/Shared/InstallConfig.cs index 2ab74c74..7658e97d 100644 --- a/Oqtane.Shared/Shared/InstallConfig.cs +++ b/Oqtane.Shared/Shared/InstallConfig.cs @@ -1,4 +1,4 @@ -namespace Oqtane.Shared +namespace Oqtane.Shared { public class InstallConfig { @@ -14,5 +14,6 @@ public string DefaultTheme { get; set; } public string DefaultLayout { get; set; } public string DefaultContainer { get; set; } + public string DefaultAdminContainer { get; set; } } } From 988639b6033f6892c3636b310dd171d02e96b700 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 4 Feb 2021 09:36:19 -0500 Subject: [PATCH 40/51] module creator owner and module name cannot be the same --- Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor index 79d5eb42..e0826c86 100644 --- a/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor @@ -113,7 +113,7 @@ else { try { - if (IsValid(_owner) && IsValid(_module) && _template != "-") + if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-") { var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference }; moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); @@ -126,7 +126,7 @@ else } else { - AddModuleMessage(Localizer["You Must Provide A Valid Owner Name, Module Name, And Template"], MessageType.Warning); + AddModuleMessage(Localizer["You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template"], MessageType.Warning); } } catch (Exception ex) From c3e7fa67f3ca70ba96b5f5f8de9583130f1b1ee3 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Fri, 5 Feb 2021 09:37:10 -0500 Subject: [PATCH 41/51] Performance improvement - set IsFixed="true" on ModuleState CascadingValues so that Blazor will not monitor them for changes --- Oqtane.Client/Modules/Controls/TabStrip.razor | 2 +- Oqtane.Client/UI/ContainerBuilder.razor | 2 +- Oqtane.Client/UI/ModuleInstance.razor | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/TabStrip.razor b/Oqtane.Client/Modules/Controls/TabStrip.razor index be82b9ff..d5186930 100644 --- a/Oqtane.Client/Modules/Controls/TabStrip.razor +++ b/Oqtane.Client/Modules/Controls/TabStrip.razor @@ -1,7 +1,7 @@ @namespace Oqtane.Modules.Controls @inherits ModuleControlBase - +