added a Setting Management UI

This commit is contained in:
sbwalker
2025-09-05 17:20:31 -04:00
parent 3e12910fbd
commit 94391875d5
9 changed files with 1024 additions and 4 deletions

View File

@ -0,0 +1,226 @@
@namespace Oqtane.Modules.Admin.Settings
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ISettingService SettingService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
<div class="col-sm-9">
<div class="input-group">
@if (_entityNameElement == "input")
{
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" required />
}
else
{
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
<option value="-">&lt;@Localizer["Select Entity"]&gt;</option>
@foreach (var entityName in _entityNames)
{
<option value="@entityName">@entityName</option>
}
</select>
}
<button type="button" class="btn btn-secondary" @onclick="@EntityNameClicked" tabindex="-1">@_entityNameTitle</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
<div class="col-sm-9">
<div class="input-group">
@if (_entityIdElement == "input")
{
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" required />
}
else
{
<select class="form-select custom-select" @bind="@_entityId">
<option value="-">&lt;@Localizer["Select Id"]&gt;</option>
@foreach (var entityId in _entityIds)
{
<option value="@entityId">@entityId</option>
}
</select>
}
<button type="button" class="btn btn-secondary" @onclick="@EntityIdClicked" tabindex="-1">@_entityIdTitle</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
<div class="col-sm-9">
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
<div class="col-sm-9">
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
<div class="col-sm-9">
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
</div>
</form>
@code {
private ElementReference form;
private bool validated = false;
private string _entityName = "-";
private List<string> _entityNames = new List<string>();
private string _entityNameElement = "select";
private string _entityNameTitle = "";
private string _entityId = "-";
private List<int> _entityIds = new List<int>();
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<int>();
}
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);
}
}
}

View File

@ -0,0 +1,122 @@
@namespace Oqtane.Modules.Admin.Settings
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ISettingService SettingService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
<div class="col-sm-9">
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
<div class="col-sm-9">
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
<div class="col-sm-9">
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
<div class="col-sm-9">
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
<div class="col-sm-9">
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
<br /><br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</div>
</form>
@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);
}
}
}

View File

@ -0,0 +1,145 @@
@namespace Oqtane.Modules.Admin.Settings
@inherits ModuleBase
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-2">
<ActionLink Action="Add" Text="Add Setting" Security="SecurityAccessLevel.Host" ResourceKey="AddSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
</div>
<div class="col-sm-5">
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
<option value="-">&lt;@Localizer["Select Entity"]&gt;</option>
@foreach (var entityName in _entityNames)
{
<option value="@entityName">@entityName</option>
}
</select>
</div>
<div class="col-sm-5">
<select class="form-select custom-select" value="@_entityId" @onchange="(e => EntityIdChanged(e))">
<option value="-">&lt;@Localizer["Select Id"]&gt;</option>
@foreach (var entityId in _entityIds)
{
<option value="@entityId">@entityId</option>
}
</select>
</div>
</div>
</div>
<br />
<Pager Items="@_settings" SearchProperties="SettingName,SettingValue">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@Localizer["Value"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"entity={context.EntityName}&id={context.SettingId}")" Security="SecurityAccessLevel.Host" ResourceKey="EditSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" /></td>
<td><ActionDialog Header="Delete Setting" Message="@string.Format(Localizer["Confirm.DeleteSetting"], context.SettingName)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSetting(context))" ResourceKey="DeleteSetting" /></td>
<td>@context.SettingName</td>
<td>@context.SettingValue</td>
</Row>
</Pager>
@code {
private string _entityName = "-";
private List<string> _entityNames = new List<string>();
private string _entityId = "-";
private List<int> _entityIds = new List<int>();
private List<Setting> _settings = new List<Setting>();
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<int>();
}
}
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<Setting>();
}
}
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);
}
}
}

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="SettingName.Text" xml:space="preserve">
<value>Name:</value>
</data>
<data name="SettingName.HelpText" xml:space="preserve">
<value>Setting Name</value>
</data>
<data name="EntityId.HelpText" xml:space="preserve">
<value>Select an existing Id or input a new Id. For Entities which are global such as master data, use the Id value '-1'.</value>
</data>
<data name="EntityName.HelpText" xml:space="preserve">
<value>Select an existing Entity or input a custom Entity. Custom Entities with a prefix of 'Master:' will be stored in the master database.</value>
</data>
<data name="EntityId.Text" xml:space="preserve">
<value>Id:</value>
</data>
<data name="EntityName.Text" xml:space="preserve">
<value>Entity:</value>
</data>
<data name="Error.SaveSetting" xml:space="preserve">
<value>Error Saving Setting</value>
</data>
<data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value>
</data>
<data name="SettingValue.Text" xml:space="preserve">
<value>Value:</value>
</data>
<data name="SettingValue.HelpText" xml:space="preserve">
<value>Setting Value</value>
</data>
<data name="IsPrivate.Text" xml:space="preserve">
<value>Private?</value>
</data>
<data name="IsPrivate.HelpText" xml:space="preserve">
<value>Indicates if this setting is private ie. if it should only be maintained on the server and not sent to the client</value>
</data>
</root>

View File

@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="SettingName.Text" xml:space="preserve">
<value>Name:</value>
</data>
<data name="SettingName.HelpText" xml:space="preserve">
<value>Setting Name</value>
</data>
<data name="EntityId.HelpText" xml:space="preserve">
<value>Entity Id</value>
</data>
<data name="EntityName.HelpText" xml:space="preserve">
<value>Entity Name</value>
</data>
<data name="EntityId.Text" xml:space="preserve">
<value>Id:</value>
</data>
<data name="EntityName.Text" xml:space="preserve">
<value>Entity:</value>
</data>
<data name="Error.LoadSetting" xml:space="preserve">
<value>Error Loading Setting</value>
</data>
<data name="Error.SaveSetting" xml:space="preserve">
<value>Error Saving Setting</value>
</data>
<data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value>
</data>
<data name="SettingValue.Text" xml:space="preserve">
<value>Value:</value>
</data>
<data name="SettingValue.HelpText" xml:space="preserve">
<value>Setting Value</value>
</data>
<data name="IsPrivate.Text" xml:space="preserve">
<value>Private?</value>
</data>
<data name="IsPrivate.HelpText" xml:space="preserve">
<value>Indicates if this setting is private ie. if it should only be maintained on the server and not sent to the client</value>
</data>
</root>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Confirm.DeleteSetting" xml:space="preserve">
<value>Are You Sure You Wish To Delete The {0} Setting?</value>
</data>
<data name="Error.DeleteSetting" xml:space="preserve">
<value>Error Deleting Setting</value>
</data>
<data name="AddSetting.Text" xml:space="preserve">
<value>Add Setting</value>
</data>
<data name="DeleteSetting.Header" xml:space="preserve">
<value>Delete Setting</value>
</data>
<data name="DeleteSetting.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="EditSetting.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Select Entity" xml:space="preserve">
<value>Select Entity</value>
</data>
<data name="Select Id" xml:space="preserve">
<value>Select Id</value>
</data>
<data name="Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Value" xml:space="preserve">
<value>Value</value>
</data>
</root>

View File

@ -240,6 +240,19 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task DeleteSettingAsync(string entityName, int settingId); Task DeleteSettingAsync(string entityName, int settingId);
/// <summary>
/// Gets list of unique entity names
/// </summary>
/// <returns></returns>
Task<List<string>> GetEntityNamesAsync();
/// <summary>
/// Gets a list of unique entity IDs for the given entity name
/// </summary>
/// <param name="entityName"></param>
/// <returns></returns>
Task<List<int>> GetEntityIdsAsync(string entityName);
/// <summary> /// <summary>
/// Gets the value of the given settingName (key) from the given key-value dictionary /// Gets the value of the given settingName (key) from the given key-value dictionary
/// </summary> /// </summary>
@ -494,6 +507,15 @@ namespace Oqtane.Services
await DeleteAsync($"{Apiurl}/{settingId}/{entityName}"); await DeleteAsync($"{Apiurl}/{settingId}/{entityName}");
} }
public async Task<List<string>> GetEntityNamesAsync()
{
return await GetJsonAsync<List<string>>($"{Apiurl}/entitynames");
}
public async Task<List<int>> GetEntityIdsAsync(string entityName)
{
return await GetJsonAsync<List<int>>($"{Apiurl}/entityids?entityname={entityName}");
}
public string GetSetting(Dictionary<string, string> settings, string settingName, string defaultValue) public string GetSetting(Dictionary<string, string> settings, string settingName, string defaultValue)
{ {

View File

@ -248,6 +248,22 @@ namespace Oqtane.Controllers
} }
} }
// GET: api/<controller>/entitynames
[HttpGet("entitynames")]
[Authorize(Roles = RoleNames.Host)]
public IEnumerable<string> GetEntityNames()
{
return _settings.GetEntityNames();
}
// GET: api/<controller>/entityids?entityname=x
[HttpGet("entityids")]
[Authorize(Roles = RoleNames.Host)]
public IEnumerable<int> GetEntityIds(string entityName)
{
return _settings.GetEntityIds(entityName);
}
// DELETE api/<controller>/clear // DELETE api/<controller>/clear
[HttpDelete("clear")] [HttpDelete("clear")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Roles = RoleNames.Admin)]
@ -297,6 +313,7 @@ namespace Oqtane.Controllers
} }
break; break;
case EntityNames.Site: case EntityNames.Site:
case EntityNames.Role:
if (permissionName == PermissionNames.Edit) if (permissionName == PermissionNames.Edit)
{ {
authorized = User.IsInRole(RoleNames.Admin); authorized = User.IsInRole(RoleNames.Admin);
@ -326,8 +343,14 @@ namespace Oqtane.Controllers
authorized = true; authorized = true;
if (permissionName == PermissionNames.Edit) if (permissionName == PermissionNames.Edit)
{ {
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName) || if (entityId == -1)
_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin); {
authorized = User.IsInRole(entityName.ToLower().StartsWith("master:") ? RoleNames.Host : RoleNames.Admin);
}
else
{
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName);
}
} }
break; break;
} }
@ -347,6 +370,7 @@ namespace Oqtane.Controllers
filter = !User.IsInRole(RoleNames.Host); filter = !User.IsInRole(RoleNames.Host);
break; break;
case EntityNames.Site: case EntityNames.Site:
case EntityNames.Role:
filter = !User.IsInRole(RoleNames.Admin); filter = !User.IsInRole(RoleNames.Admin);
break; break;
case EntityNames.Page: case EntityNames.Page:
@ -365,7 +389,7 @@ namespace Oqtane.Controllers
} }
break; break;
default: // custom entity 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; break;
} }
return filter; return filter;

View File

@ -19,6 +19,8 @@ namespace Oqtane.Repository
Setting GetSetting(string entityName, int entityId, string settingName); Setting GetSetting(string entityName, int entityId, string settingName);
void DeleteSetting(string entityName, int settingId); void DeleteSetting(string entityName, int settingId);
void DeleteSettings(string entityName, int entityId); void DeleteSettings(string entityName, int entityId);
IEnumerable<string> GetEntityNames();
IEnumerable<int> GetEntityIds(string entityName);
string GetSettingValue(IEnumerable<Setting> settings, string settingName, string defaultValue); string GetSettingValue(IEnumerable<Setting> settings, string settingName, string defaultValue);
string GetSettingValue(string entityName, int entityId, string settingName, string defaultValue); string GetSettingValue(string entityName, int entityId, string settingName, string defaultValue);
} }
@ -190,6 +192,18 @@ namespace Oqtane.Repository
ManageCache(entityName); ManageCache(entityName);
} }
public IEnumerable<string> GetEntityNames()
{
using var db = _tenantContextFactory.CreateDbContext();
return db.Setting.Select(item => item.EntityName).Distinct().OrderBy(item => item).ToList();
}
public IEnumerable<int> 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<Setting> settings, string settingName, string defaultValue) public string GetSettingValue(IEnumerable<Setting> settings, string settingName, string defaultValue)
{ {
var setting = settings.FirstOrDefault(item => item.SettingName == settingName); var setting = settings.FirstOrDefault(item => item.SettingName == settingName);
@ -218,7 +232,9 @@ namespace Oqtane.Repository
private bool IsMaster(string EntityName) 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) private void ManageCache(string EntityName)