From 531cba715e3d3ca654565edd9a5baf5a36d9a09d Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 4 Feb 2021 08:54:59 -0500 Subject: [PATCH] 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; } } }