diff --git a/Oqtane.Client/Modules/Admin/Settings/Add.razor b/Oqtane.Client/Modules/Admin/Settings/Add.razor new file mode 100644 index 00000000..db7c0c58 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/Settings/Add.razor @@ -0,0 +1,226 @@ +@namespace Oqtane.Modules.Admin.Settings +@inherits ModuleBase +@inject NavigationManager NavigationManager +@inject ISettingService SettingService +@inject IStringLocalizer Localizer +@inject IStringLocalizer SharedLocalizer + +
+
+
+ +
+
+ @if (_entityNameElement == "input") + { + + } + else + { + + } + +
+
+
+
+ +
+
+ @if (_entityIdElement == "input") + { + + } + else + { + + } + +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+

+ + @SharedLocalizer["Cancel"] +
+
+ +@code { + private ElementReference form; + private bool validated = false; + + private string _entityName = "-"; + private List _entityNames = new List(); + private string _entityNameElement = "select"; + private string _entityNameTitle = ""; + private string _entityId = "-"; + private List _entityIds = new List(); + private string _entityIdElement = "select"; + private string _entityIdTitle = ""; + private string _settingName = ""; + private string _settingValue = ""; + private string _isPrivate = "True"; + + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + + protected override async Task OnInitializedAsync() + { + try + { + _entityNameTitle = Localizer["Input"]; + _entityIdTitle = Localizer["Input"]; + + // default entity names + _entityNames.Add(EntityNames.Host); + _entityNames.Add(EntityNames.Job); + _entityNames.Add(EntityNames.ModuleDefinition); + _entityNames.Add(EntityNames.Theme); + _entityNames.Add(EntityNames.Tenant); + _entityNames.Add(EntityNames.Site); + _entityNames.Add(EntityNames.Role); + _entityNames.Add(EntityNames.Page); + _entityNames.Add(EntityNames.Module); + _entityNames.Add(EntityNames.Folder); + _entityNames.Add(EntityNames.User); + _entityNames.Add(EntityNames.Visitor); + + // custom entity names + var entityNames = await SettingService.GetEntityNamesAsync(); + foreach (var entityName in entityNames) + { + if (!_entityNames.Contains(entityName)) + { + _entityNames.Add(entityName); + } + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Setting {Error}", ex.Message); + AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error); + } + } + + private void EntityNameClicked() + { + if (_entityNameElement == "select") + { + _entityName = ""; + _entityNameElement = "input"; + _entityNameTitle = Localizer["Select"]; + _entityId = ""; + _entityIdElement = "input"; + _entityIdTitle = Localizer["Select"]; + } + else + { + _entityName = "-"; + _entityNameElement = "select"; + _entityNameTitle = Localizer["Input"]; + } + } + + private void EntityIdClicked() + { + if (_entityIdElement == "select") + { + _entityId = ""; + _entityIdElement = "input"; + _entityIdTitle = Localizer["Select"]; + } + else + { + _entityId = "-"; + _entityIdElement = "select"; + _entityIdTitle = Localizer["Input"]; + } + } + + private async void EntityNameChanged(ChangeEventArgs e) + { + try + { + _entityName = e.Value.ToString(); + _entityId = "-"; + _entityIdElement = "select"; + _entityIdTitle = Localizer["Input"]; + if (_entityName != "-") + { + _entityIds = await SettingService.GetEntityIdsAsync(_entityName); + } + else + { + _entityIds = new List(); + } + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error On EntityNameChanged"); + } + } + + private async Task SaveSetting() + { + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(form)) + { + var setting = new Setting(); + setting.EntityName = _entityName; + setting.EntityId = int.Parse(_entityId); + setting.SettingName = _settingName; + setting.SettingValue = _settingValue; + setting.IsPrivate = (bool.Parse(_isPrivate)); + + try + { + setting = await SettingService.AddSettingAsync(setting); + await logger.LogInformation("Setting Saved {Setting}", setting); + NavigationManager.NavigateTo(PageState.ReturnUrl); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message); + AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error); + } + } + else + { + AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); + } + } +} diff --git a/Oqtane.Client/Modules/Admin/Settings/Edit.razor b/Oqtane.Client/Modules/Admin/Settings/Edit.razor new file mode 100644 index 00000000..682355d6 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/Settings/Edit.razor @@ -0,0 +1,122 @@ +@namespace Oqtane.Modules.Admin.Settings +@inherits ModuleBase +@inject NavigationManager NavigationManager +@inject ISettingService SettingService +@inject IStringLocalizer Localizer +@inject IStringLocalizer SharedLocalizer + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+

+ + @SharedLocalizer["Cancel"] +

+ +
+
+ +@code { + private ElementReference form; + private bool validated = false; + + private int _settingId; + private string _entityName; + private string _entityId; + private string _settingName; + private string _settingValue; + private string _isPrivate; + private string _createdby; + private DateTime _createdon; + private string _modifiedby; + private DateTime _modifiedon; + + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + + protected override async Task OnInitializedAsync() + { + _settingId = int.Parse(PageState.QueryString["id"]); + _entityName = PageState.QueryString["entity"]; + + try + { + var setting = await SettingService.GetSettingAsync(_entityName, _settingId); + if (setting != null) + { + _entityId = setting.EntityId.ToString(); + _settingName = setting.SettingName; + _settingValue = setting.SettingValue; + _isPrivate = setting.IsPrivate.ToString(); + _createdby = setting.CreatedBy; + _createdon = setting.CreatedOn; + _modifiedby = setting.ModifiedBy; + _modifiedon = setting.ModifiedOn; + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Setting {SettingId} {Error}", _settingId, ex.Message); + AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error); + } + } + + private async Task SaveSetting() + { + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(form)) + { + var setting = await SettingService.GetSettingAsync(_entityName, _settingId); + setting.SettingValue = _settingValue; + setting.IsPrivate = (_isPrivate != null && Boolean.Parse(_isPrivate)); + + try + { + setting = await SettingService.UpdateSettingAsync(setting); + await logger.LogInformation("Setting Saved {Setting}", setting); + NavigationManager.NavigateTo(PageState.ReturnUrl); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message); + AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error); + } + } + else + { + AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); + } + } +} diff --git a/Oqtane.Client/Modules/Admin/Settings/Index.razor b/Oqtane.Client/Modules/Admin/Settings/Index.razor new file mode 100644 index 00000000..001c95c1 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/Settings/Index.razor @@ -0,0 +1,145 @@ +@namespace Oqtane.Modules.Admin.Settings +@inherits ModuleBase +@inject ISettingService SettingService +@inject IStringLocalizer Localizer +@inject IStringLocalizer SharedLocalizer + +
+
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+   +   + @Localizer["Name"] + @Localizer["Value"] +
+ + + + @context.SettingName + @context.SettingValue + +
+ +@code { + private string _entityName = "-"; + private List _entityNames = new List(); + private string _entityId = "-"; + private List _entityIds = new List(); + private List _settings = new List(); + + public override string UrlParametersTemplate => "/{entityname}/{entityid}"; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + + protected override async Task OnParametersSetAsync() + { + _entityNames = await SettingService.GetEntityNamesAsync(); + + if (UrlParameters.ContainsKey("entityname")) + { + _entityName = UrlParameters["entityname"]; + await GetEntityIds(); + } + if (UrlParameters.ContainsKey("entityid")) + { + _entityId = UrlParameters["entityid"]; + await GetSettings(); + } + } + + private async Task GetEntityIds() + { + if (_entityName != "-") + { + _entityIds = await SettingService.GetEntityIdsAsync(_entityName); + } + else + { + _entityIds = new List(); + } + } + + private async Task GetSettings() + { + if (_entityName != "-" && _entityId != "-") + { + _settings = await SettingService.GetSettingsAsync(_entityName, int.Parse(_entityId), ""); + _settings = _settings.OrderBy(item => item.SettingName).ToList(); + } + else + { + _settings = new List(); + } + } + + private async void EntityNameChanged(ChangeEventArgs e) + { + try + { + _entityName = e.Value.ToString(); + _entityId = "-"; + await GetEntityIds(); + await GetSettings(); + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error On EntityNameChanged"); + } + } + + private async void EntityIdChanged(ChangeEventArgs e) + { + try + { + _entityId = e.Value.ToString(); + await GetSettings(); + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error On EntityIdChanged"); + } + } + + private async Task DeleteSetting(Setting setting) + { + try + { + await SettingService.DeleteSettingAsync(setting.EntityName, setting.EntityId, setting.SettingName); + await logger.LogInformation("Setting Deleted {Setting}", setting); + await GetSettings(); + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Deleting Setting {Setting} {Error}", setting, ex.Message); + AddModuleMessage(Localizer["Error.DeleteSetting"], MessageType.Error); + } + } +} diff --git a/Oqtane.Client/Resources/Modules/Admin/Settings/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Settings/Add.resx new file mode 100644 index 00000000..183c6f82 --- /dev/null +++ b/Oqtane.Client/Resources/Modules/Admin/Settings/Add.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name: + + + Setting Name + + + Select an existing Id or input a new Id. For Entities which are global such as master data, use the Id value '-1'. + + + Select an existing Entity or input a custom Entity. Custom Entities with a prefix of 'Master:' will be stored in the master database. + + + Id: + + + Entity: + + + Error Saving Setting + + + Please Provide All Required Information + + + Value: + + + Setting Value + + + Private? + + + Indicates if this setting is private ie. if it should only be maintained on the server and not sent to the client + + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Settings/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Settings/Edit.resx new file mode 100644 index 00000000..9309ff6a --- /dev/null +++ b/Oqtane.Client/Resources/Modules/Admin/Settings/Edit.resx @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name: + + + Setting Name + + + Entity Id + + + Entity Name + + + Id: + + + Entity: + + + Error Loading Setting + + + Error Saving Setting + + + Please Provide All Required Information + + + Value: + + + Setting Value + + + Private? + + + Indicates if this setting is private ie. if it should only be maintained on the server and not sent to the client + + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Settings/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Settings/Index.resx new file mode 100644 index 00000000..ed7c7cc7 --- /dev/null +++ b/Oqtane.Client/Resources/Modules/Admin/Settings/Index.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Are You Sure You Wish To Delete The {0} Setting? + + + Error Deleting Setting + + + Add Setting + + + Delete Setting + + + Delete + + + Edit + + + Select Entity + + + Select Id + + + Name + + + Value + + \ No newline at end of file diff --git a/Oqtane.Client/Services/SettingService.cs b/Oqtane.Client/Services/SettingService.cs index 4203f9c0..6694e2b6 100644 --- a/Oqtane.Client/Services/SettingService.cs +++ b/Oqtane.Client/Services/SettingService.cs @@ -240,6 +240,19 @@ namespace Oqtane.Services /// Task DeleteSettingAsync(string entityName, int settingId); + /// + /// Gets list of unique entity names + /// + /// + Task> GetEntityNamesAsync(); + + /// + /// Gets a list of unique entity IDs for the given entity name + /// + /// + /// + Task> GetEntityIdsAsync(string entityName); + /// /// Gets the value of the given settingName (key) from the given key-value dictionary /// @@ -494,6 +507,15 @@ namespace Oqtane.Services await DeleteAsync($"{Apiurl}/{settingId}/{entityName}"); } + public async Task> GetEntityNamesAsync() + { + return await GetJsonAsync>($"{Apiurl}/entitynames"); + } + + public async Task> GetEntityIdsAsync(string entityName) + { + return await GetJsonAsync>($"{Apiurl}/entityids?entityname={entityName}"); + } public string GetSetting(Dictionary settings, string settingName, string defaultValue) { diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 1c42ab7d..17c5c673 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -248,6 +248,22 @@ namespace Oqtane.Controllers } } + // GET: api//entitynames + [HttpGet("entitynames")] + [Authorize(Roles = RoleNames.Host)] + public IEnumerable GetEntityNames() + { + return _settings.GetEntityNames(); + } + + // GET: api//entityids?entityname=x + [HttpGet("entityids")] + [Authorize(Roles = RoleNames.Host)] + public IEnumerable GetEntityIds(string entityName) + { + return _settings.GetEntityIds(entityName); + } + // DELETE api//clear [HttpDelete("clear")] [Authorize(Roles = RoleNames.Admin)] @@ -297,6 +313,7 @@ namespace Oqtane.Controllers } break; case EntityNames.Site: + case EntityNames.Role: if (permissionName == PermissionNames.Edit) { authorized = User.IsInRole(RoleNames.Admin); @@ -326,8 +343,14 @@ namespace Oqtane.Controllers authorized = true; if (permissionName == PermissionNames.Edit) { - authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName) || - _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin); + if (entityId == -1) + { + authorized = User.IsInRole(entityName.ToLower().StartsWith("master:") ? RoleNames.Host : RoleNames.Admin); + } + else + { + authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName); + } } break; } @@ -347,6 +370,7 @@ namespace Oqtane.Controllers filter = !User.IsInRole(RoleNames.Host); break; case EntityNames.Site: + case EntityNames.Role: filter = !User.IsInRole(RoleNames.Admin); break; case EntityNames.Page: @@ -365,7 +389,7 @@ namespace Oqtane.Controllers } break; default: // custom entity - filter = !User.IsInRole(RoleNames.Admin) && !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit); + filter = !User.IsInRole(entityName.ToLower().StartsWith("master:") ? RoleNames.Host : RoleNames.Admin) && !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit); break; } return filter; diff --git a/Oqtane.Server/Repository/SettingRepository.cs b/Oqtane.Server/Repository/SettingRepository.cs index 13c48819..7305e3bc 100644 --- a/Oqtane.Server/Repository/SettingRepository.cs +++ b/Oqtane.Server/Repository/SettingRepository.cs @@ -19,6 +19,8 @@ namespace Oqtane.Repository Setting GetSetting(string entityName, int entityId, string settingName); void DeleteSetting(string entityName, int settingId); void DeleteSettings(string entityName, int entityId); + IEnumerable GetEntityNames(); + IEnumerable GetEntityIds(string entityName); string GetSettingValue(IEnumerable settings, string settingName, string defaultValue); string GetSettingValue(string entityName, int entityId, string settingName, string defaultValue); } @@ -190,6 +192,18 @@ namespace Oqtane.Repository ManageCache(entityName); } + public IEnumerable GetEntityNames() + { + using var db = _tenantContextFactory.CreateDbContext(); + return db.Setting.Select(item => item.EntityName).Distinct().OrderBy(item => item).ToList(); + } + public IEnumerable GetEntityIds(string entityName) + { + using var db = _tenantContextFactory.CreateDbContext(); + return db.Setting.Where(item => item.EntityName == entityName) + .Select(item => item.EntityId).Distinct().OrderBy(item => item).ToList(); + } + public string GetSettingValue(IEnumerable settings, string settingName, string defaultValue) { var setting = settings.FirstOrDefault(item => item.SettingName == settingName); @@ -218,7 +232,9 @@ namespace Oqtane.Repository private bool IsMaster(string EntityName) { - return (EntityName == EntityNames.ModuleDefinition || EntityName == EntityNames.Host); + return EntityName == EntityNames.Host || EntityName == EntityNames.Job || + EntityName == EntityNames.ModuleDefinition || EntityName == EntityNames.Theme || + EntityName.ToLower().StartsWith("master:"); } private void ManageCache(string EntityName)