Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
@@ -7,13 +7,80 @@
|
||||
"Blazor",
|
||||
"Oqtane"
|
||||
],
|
||||
"name": "Oqtane Application Template",
|
||||
"shortName": "oqtane-app",
|
||||
"defaultName": "MyCompany.MyProject",
|
||||
"identity": "Oqtane.Application.Template",
|
||||
"tags": {
|
||||
"language": "C#",
|
||||
"type": "project"
|
||||
"type": "solution",
|
||||
"editorTreatAs":"solution"
|
||||
},
|
||||
"identity": "Oqtane.Application.Template",
|
||||
"name": "Oqtane Application Template For Blazor",
|
||||
"shortName": "oqtane-app",
|
||||
"sourceName": "Oqtane.Application",
|
||||
"preferNameDirectory": true
|
||||
"preferNameDirectory": true,
|
||||
"guids": [
|
||||
"04B05448-788F-433D-92C0-FED35122D45A",
|
||||
"AA8E58A1-CD09-4208-BF66-A8BB341FD669",
|
||||
"18D73F73-D7BE-4388-85BA-FBD9AC96FCA2"
|
||||
],
|
||||
"symbols": {
|
||||
"Framework": {
|
||||
"type": "parameter",
|
||||
"description": "The target framework for the project",
|
||||
"datatype": "choice",
|
||||
"choices": [
|
||||
{
|
||||
"choice": "net9.0",
|
||||
"description": "Target net9.0"
|
||||
}
|
||||
],
|
||||
"replaces": "net9.0",
|
||||
"defaultValue": "net9.0"
|
||||
},
|
||||
"HttpPort": {
|
||||
"type": "parameter",
|
||||
"datatype": "integer",
|
||||
"description": "Port number to use for the HTTP endpoint in launchSettings.json."
|
||||
},
|
||||
"HttpPortGenerated": {
|
||||
"type": "generated",
|
||||
"generator": "port"
|
||||
},
|
||||
"HttpPortReplacer": {
|
||||
"type": "generated",
|
||||
"generator": "coalesce",
|
||||
"parameters": {
|
||||
"sourceVariableName": "HttpPort",
|
||||
"fallbackVariableName": "HttpPortGenerated"
|
||||
},
|
||||
"replaces": "44358"
|
||||
},
|
||||
"HttpsPort": {
|
||||
"type": "parameter",
|
||||
"datatype": "integer",
|
||||
"description": "Port number to use for the HTTPS endpoint in launchSettings.json."
|
||||
},
|
||||
"HttpsPortGenerated": {
|
||||
"type": "generated",
|
||||
"generator": "port",
|
||||
"parameters": {
|
||||
"low": 44300,
|
||||
"high": 44399
|
||||
}
|
||||
},
|
||||
"HttpsPortReplacer": {
|
||||
"type": "generated",
|
||||
"generator": "coalesce",
|
||||
"parameters": {
|
||||
"sourceVariableName": "HttpsPort",
|
||||
"fallbackVariableName": "HttpsPortGenerated"
|
||||
},
|
||||
"replaces": "44359"
|
||||
}
|
||||
},
|
||||
"primaryOutputs": [
|
||||
{
|
||||
"path": "Oqtane.Application.sln"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,23 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
|
||||
<IsPackable>true</IsPackable>
|
||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
|
||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Client" Version="6.2.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Client" Version="6.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,7 +9,7 @@ cd MyCompany.MyProject
|
||||
dotnet build
|
||||
cd Server
|
||||
dotnet run
|
||||
browse to http://localhost:5001
|
||||
browse to Url
|
||||
```
|
||||
|
||||
When using this approach you do not need to have a local copy of the oqtane.framework source code - you simply utilize Oqtane as a standard application dependency.
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName>
|
||||
<IsPackable>true</IsPackable>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
||||
<CompressionEnabled>false</CompressionEnabled>
|
||||
<StaticWebAssetsFingerprintContent>false</StaticWebAssetsFingerprintContent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
||||
<CompressionEnabled>false</CompressionEnabled>
|
||||
<StaticWebAssetsFingerprintContent>false</StaticWebAssetsFingerprintContent>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Client\Oqtane.Application.Client.csproj" />
|
||||
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Server" Version="6.2.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Client\Oqtane.Application.Client.csproj" />
|
||||
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Server" Version="6.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5000",
|
||||
"applicationUrl": "http://localhost:44358",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"applicationUrl": "https://localhost:44359;http://localhost:44358",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Shared" Version="6.2.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Shared" Version="6.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
@if (_initialized)
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
|
||||
<TabPanel Name="Module" ResourceKey="Module" Heading="Module">
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -236,11 +236,10 @@
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
private List<Page> _pagesWithModules;
|
||||
|
||||
#pragma warning disable 649
|
||||
private PermissionGrid _permissionGrid;
|
||||
#pragma warning restore 649
|
||||
|
||||
private List<Page> _pagesWithModules;
|
||||
|
||||
private List<Package> _packages;
|
||||
private List<Language> _languages;
|
||||
|
||||
@@ -269,8 +269,16 @@
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
|
||||
{
|
||||
_themetype = PageState.Site.DefaultThemeType;
|
||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
var themes = new List<Theme>();
|
||||
foreach (var theme in PageState.Site.Themes)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||
{
|
||||
themes.Add(theme);
|
||||
}
|
||||
}
|
||||
_themes = ThemeService.GetThemeControls(themes);
|
||||
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||
_containertype = PageState.Site.DefaultContainerType;
|
||||
_children = new List<Page>();
|
||||
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||
|
||||
@@ -443,8 +443,16 @@
|
||||
{
|
||||
_themetype = PageState.Site.DefaultThemeType;
|
||||
}
|
||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
var themes = new List<Theme>();
|
||||
foreach (var theme in PageState.Site.Themes)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||
{
|
||||
themes.Add(theme);
|
||||
}
|
||||
}
|
||||
_themes = ThemeService.GetThemeControls(themes);
|
||||
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||
_containertype = _page.DefaultContainerType;
|
||||
if (string.IsNullOrEmpty(_containertype))
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IProfileService ProfileService
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@@ -56,9 +57,25 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from custom Settings (ie. 'EntityName:Countries')." ResourceKey="Options">Options: </Label>
|
||||
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from Settings." ResourceKey="Options">Options: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
||||
<div class="input-group">
|
||||
@if (_optiontype == "Settings")
|
||||
{
|
||||
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="entityName" class="form-select" @bind="@_options">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var entityname in _entitynames)
|
||||
{
|
||||
<option value="@entityname">@entityname</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleOptionType">@Localizer[_optiontype]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -95,7 +112,7 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<br />
|
||||
<br />
|
||||
@@ -116,6 +133,8 @@
|
||||
private string _rows = "1";
|
||||
private string _defaultvalue = string.Empty;
|
||||
private string _options = string.Empty;
|
||||
private string _optiontype = "Settings";
|
||||
private List<string> _entitynames;
|
||||
private string _validation = string.Empty;
|
||||
private string _autocomplete = string.Empty;
|
||||
private string _isrequired = "False";
|
||||
@@ -133,6 +152,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_entitynames = await SettingService.GetEntityNamesAsync();
|
||||
|
||||
if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
_profileid = Int32.Parse(PageState.QueryString["id"]);
|
||||
@@ -148,6 +169,11 @@
|
||||
_rows = profile.Rows.ToString();
|
||||
_defaultvalue = profile.DefaultValue;
|
||||
_options = profile.Options;
|
||||
if (_options.StartsWith("EntityName:"))
|
||||
{
|
||||
_optiontype = "Options";
|
||||
_options = _options.Substring(11);
|
||||
}
|
||||
_validation = profile.Validation;
|
||||
_autocomplete = profile.Autocomplete;
|
||||
_isrequired = profile.IsRequired.ToString();
|
||||
@@ -166,6 +192,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleOptionType()
|
||||
{
|
||||
if (_optiontype == "Options")
|
||||
{
|
||||
_optiontype = "Settings";
|
||||
}
|
||||
else
|
||||
{
|
||||
_optiontype = "Options";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveProfile()
|
||||
{
|
||||
validated = true;
|
||||
@@ -193,7 +231,14 @@
|
||||
profile.MaxLength = int.Parse(_maxlength);
|
||||
profile.Rows = int.Parse(_rows);
|
||||
profile.DefaultValue = _defaultvalue;
|
||||
profile.Options = _options;
|
||||
if (_optiontype == "Options" && !string.IsNullOrEmpty(_options))
|
||||
{
|
||||
profile.Options = "EntityName:" + _options;
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.Options = _options;
|
||||
}
|
||||
profile.Validation = _validation;
|
||||
profile.Autocomplete = _autocomplete;
|
||||
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
||||
|
||||
@@ -592,9 +592,17 @@
|
||||
{
|
||||
_faviconfileid = site.FaviconFileId.Value;
|
||||
}
|
||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||
var themes = new List<Theme>();
|
||||
foreach (var theme in PageState.Site.Themes)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||
{
|
||||
themes.Add(theme);
|
||||
}
|
||||
}
|
||||
_themes = ThemeService.GetThemeControls(themes);
|
||||
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
|
||||
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
|
||||
_cookieconsent = SettingService.GetSetting(settings, "CookieConsent", string.Empty);
|
||||
|
||||
@@ -216,7 +216,7 @@ else
|
||||
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||
}
|
||||
_urls = PageState.Alias.Name;
|
||||
_themeList = await ThemeService.GetThemesAsync();
|
||||
_themeList = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||
_themes = ThemeService.GetThemeControls(_themeList);
|
||||
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
|
||||
{
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_themes = await ThemeService.GetThemesAsync();
|
||||
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||
await LoadPackages();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@@ -9,84 +9,98 @@
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
<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="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" />
|
||||
<TabStrip>
|
||||
<TabPanel Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||
<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="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="themename" class="form-control" @bind="@_themeName" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
|
||||
{
|
||||
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<PermissionGrid EntityName="@EntityNames.Theme" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="themename" class="form-control" @bind="@_themeName" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
|
||||
{
|
||||
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
@@ -103,11 +117,14 @@
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
private string _license = "";
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
|
||||
private PermissionGrid _permissionGrid;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -126,6 +143,7 @@
|
||||
_url = theme.Url;
|
||||
_contact = theme.Contact;
|
||||
_license = theme.License;
|
||||
_permissions = theme.PermissionList;
|
||||
_createdby = theme.CreatedBy;
|
||||
_createdon = theme.CreatedOn;
|
||||
_modifiedby = theme.ModifiedBy;
|
||||
@@ -152,6 +170,7 @@
|
||||
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
|
||||
theme.Name = _name;
|
||||
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
||||
theme.PermissionList = _permissionGrid.GetPermissionList();
|
||||
await ThemeService.UpdateThemeAsync(theme);
|
||||
await logger.LogInformation("Theme Saved {Theme}", theme);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
|
||||
@@ -78,7 +78,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
_themes = await ThemeService.GetThemesAsync();
|
||||
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||
_packages = await PackageService.GetPackageUpdatesAsync("theme");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -161,7 +161,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
await ThemeService.DeleteThemeAsync(Theme.ThemeName);
|
||||
await ThemeService.DeleteThemeAsync(Theme.ThemeId, PageState.Site.SiteId);
|
||||
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||
}
|
||||
|
||||
@@ -345,11 +345,11 @@
|
||||
try
|
||||
{
|
||||
FolderId = int.Parse((string)e.Value);
|
||||
await OnSelectFolder.InvokeAsync(FolderId);
|
||||
FileId = -1;
|
||||
GetFolderPermission();
|
||||
await SetImage();
|
||||
await GetFiles();
|
||||
await OnSelectFolder.InvokeAsync(FolderId);
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -364,11 +364,11 @@
|
||||
{
|
||||
_message = string.Empty;
|
||||
FileId = int.Parse((string)e.Value);
|
||||
await SetImage();
|
||||
#pragma warning disable CS0618
|
||||
await OnSelect.InvokeAsync(FileId);
|
||||
#pragma warning restore CS0618
|
||||
await OnSelectFile.InvokeAsync(FileId);
|
||||
await SetImage();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
@@ -460,13 +460,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
await SetImage();
|
||||
|
||||
await OnUpload.InvokeAsync(FileId);
|
||||
#pragma warning disable CS0618
|
||||
await OnSelect.InvokeAsync(FileId);
|
||||
#pragma warning restore CS0618
|
||||
await OnSelectFile.InvokeAsync(FileId);
|
||||
|
||||
await SetImage();
|
||||
await GetFiles();
|
||||
StateHasChanged();
|
||||
}
|
||||
@@ -518,12 +519,13 @@
|
||||
}
|
||||
|
||||
FileId = -1;
|
||||
await SetImage();
|
||||
|
||||
#pragma warning disable CS0618
|
||||
await OnSelect.InvokeAsync(FileId);
|
||||
#pragma warning restore CS0618
|
||||
await OnSelectFile.InvokeAsync(FileId);
|
||||
|
||||
await SetImage();
|
||||
await GetFiles();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// This is just a placeholder file
|
||||
// It is necessary for the documentation to successfully build this project.
|
||||
// Reason is that docfx will run the .net compiler and find references
|
||||
// to this class in the project.
|
||||
// But since the real class is just a .razor file, ATM docfx will fail.
|
||||
//
|
||||
// Note added 2025-09-23 by @tvatavuk.
|
||||
// We hope that as .net and docfx improve, the razor-compiler will work in that scenario
|
||||
// as well, and this file can be removed.
|
||||
|
||||
namespace Oqtane.Modules.Controls;
|
||||
public partial class RadzenTextEditor;
|
||||
@@ -26,7 +26,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
||||
<PackageReference Include="Radzen.Blazor" Version="7.3.4" />
|
||||
<PackageReference Include="Radzen.Blazor" Version="7.3.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -183,8 +183,8 @@
|
||||
<data name="Runtimes.Text" xml:space="preserve">
|
||||
<value>Runtimes: </value>
|
||||
</data>
|
||||
<data name="Definition.Heading" xml:space="preserve">
|
||||
<value>Definition</value>
|
||||
<data name="Module.Heading" xml:space="preserve">
|
||||
<value>Module</value>
|
||||
</data>
|
||||
<data name="Information.Heading" xml:space="preserve">
|
||||
<value>Information</value>
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
<value>The default value for this profile item</value>
|
||||
</data>
|
||||
<data name="Options.HelpText" xml:space="preserve">
|
||||
<value>A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from custom Settings (ie. 'EntityName:Countries').</value>
|
||||
<value>A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from Settings.</value>
|
||||
</data>
|
||||
<data name="Required.HelpText" xml:space="preserve">
|
||||
<value>Should a user be required to provide a value for this profile item?</value>
|
||||
@@ -201,4 +201,10 @@
|
||||
<data name="Autocomplete.Text" xml:space="preserve">
|
||||
<value>Autocomplete: </value>
|
||||
</data>
|
||||
<data name="Options" xml:space="preserve">
|
||||
<value>Options</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -180,4 +180,10 @@
|
||||
<data name="View License" xml:space="preserve">
|
||||
<value>View License</value>
|
||||
</data>
|
||||
<data name="Theme.Heading" xml:space="preserve">
|
||||
<value>Themex</value>
|
||||
</data>
|
||||
<data name="Permissions.Heading" xml:space="preserve">
|
||||
<value>Permissionsx</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -101,7 +101,6 @@ namespace Oqtane.Services
|
||||
/// Unzips the contents of a zip file
|
||||
/// </summary>
|
||||
/// <param name="fileId">Reference to the <see cref="File"/></param>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
Task UnzipFileAsync(int fileId);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,9 @@ namespace Oqtane.Services
|
||||
/// <summary>
|
||||
/// Returns a list of available themes
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<Theme>> GetThemesAsync();
|
||||
Task<List<Theme>> GetThemesAsync(int siteId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a specific theme
|
||||
@@ -69,9 +70,10 @@ namespace Oqtane.Services
|
||||
/// <summary>
|
||||
/// Deletes a theme
|
||||
/// </summary>
|
||||
/// <param name="themeName"></param>
|
||||
/// <param name="themeId"></param>
|
||||
/// <param name="siteId"></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteThemeAsync(string themeName);
|
||||
Task DeleteThemeAsync(int themeId, int siteId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new theme
|
||||
@@ -103,9 +105,9 @@ namespace Oqtane.Services
|
||||
|
||||
private string ApiUrl => CreateApiUrl("Theme");
|
||||
|
||||
public async Task<List<Theme>> GetThemesAsync()
|
||||
public async Task<List<Theme>> GetThemesAsync(int siteId)
|
||||
{
|
||||
List<Theme> themes = await GetJsonAsync<List<Theme>>(ApiUrl);
|
||||
List<Theme> themes = await GetJsonAsync<List<Theme>>($"{ApiUrl}?siteid={siteId}");
|
||||
return themes.OrderBy(item => item.Name).ToList();
|
||||
}
|
||||
public async Task<Theme> GetThemeAsync(int themeId, int siteId)
|
||||
@@ -139,9 +141,9 @@ namespace Oqtane.Services
|
||||
await PutJsonAsync($"{ApiUrl}/{theme.ThemeId}", theme);
|
||||
}
|
||||
|
||||
public async Task DeleteThemeAsync(string themeName)
|
||||
public async Task DeleteThemeAsync(int themeId, int siteId)
|
||||
{
|
||||
await DeleteAsync($"{ApiUrl}/{themeName}");
|
||||
await DeleteAsync($"{ApiUrl}/{themeId}?siteid={siteId}");
|
||||
}
|
||||
|
||||
public async Task<Theme> CreateThemeAsync(Theme theme)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.Http" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.Localization" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Radzen.Blazor" version="7.3.4" exclude="Build,Analyzers" />
|
||||
<dependency id="Radzen.Blazor" version="7.3.5" exclude="Build,Analyzers" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<dependency id="Microsoft.Extensions.Localization" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Authentication.OpenIdConnect" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="SixLabors.ImageSharp" version="3.1.11" exclude="Build,Analyzers" />
|
||||
<dependency id="HtmlAgilityPack" version="1.12.2" exclude="Build,Analyzers" />
|
||||
<dependency id="HtmlAgilityPack" version="1.12.3" exclude="Build,Analyzers" />
|
||||
<dependency id="Swashbuckle.AspNetCore" version="9.0.4" exclude="Build,Analyzers" />
|
||||
<dependency id="MailKit" version="4.13.0" exclude="Build,Analyzers" />
|
||||
<dependency id="MySql.Data" version="9.4.0" exclude="Build,Analyzers" />
|
||||
|
||||
@@ -252,7 +252,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized ModuleDefinition Delete Attempt {ModuleDefinitionId}", id);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized ModuleDefinition Delete Attempt {ModuleDefinitionId} {SiteId}", id, siteid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ namespace Oqtane.Controllers
|
||||
|
||||
// GET: api/<controller>/entitynames
|
||||
[HttpGet("entitynames")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public IEnumerable<string> GetEntityNames()
|
||||
{
|
||||
return _settings.GetEntityNames();
|
||||
@@ -316,7 +316,7 @@ namespace Oqtane.Controllers
|
||||
|
||||
// GET: api/<controller>/entityids?entityname=x
|
||||
[HttpGet("entityids")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public IEnumerable<int> GetEntityIds(string entityName)
|
||||
{
|
||||
return _settings.GetEntityIds(entityName);
|
||||
|
||||
@@ -14,6 +14,9 @@ using System.Text.Json;
|
||||
using System.Net;
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection.Metadata;
|
||||
using Oqtane.Security;
|
||||
using System.Security.Policy;
|
||||
|
||||
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
||||
|
||||
@@ -26,30 +29,50 @@ namespace Oqtane.Controllers
|
||||
private readonly IInstallationManager _installationManager;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
private readonly ITenantManager _tenantManager;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IServiceProvider serviceProvider)
|
||||
public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, IServiceProvider serviceProvider)
|
||||
{
|
||||
_themes = themes;
|
||||
_installationManager = installationManager;
|
||||
_environment = environment;
|
||||
_tenantManager = tenantManager;
|
||||
_userPermissions = userPermissions;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
// GET: api/<controller>
|
||||
// GET: api/<controller>?siteid=x
|
||||
[HttpGet]
|
||||
[Authorize(Roles = RoleNames.Registered)]
|
||||
public IEnumerable<Theme> Get()
|
||||
public IEnumerable<Theme> Get(string siteid)
|
||||
{
|
||||
return _themes.GetThemes();
|
||||
}
|
||||
int SiteId;
|
||||
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
|
||||
{
|
||||
List<Theme> themes = new List<Theme>();
|
||||
foreach (Theme theme in _themes.GetThemes(SiteId))
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, PermissionNames.Utilize, theme.PermissionList))
|
||||
{
|
||||
themes.Add(theme);
|
||||
}
|
||||
}
|
||||
return themes;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Get Attempt {SiteId}", siteid);
|
||||
HttpContext.Response.StatusCode = (int) HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// GET api/<controller>/5?siteid=x
|
||||
[HttpGet("{id}")]
|
||||
@@ -58,7 +81,24 @@ namespace Oqtane.Controllers
|
||||
int SiteId;
|
||||
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
|
||||
{
|
||||
return _themes.GetTheme(id, SiteId);
|
||||
Theme theme = _themes.GetTheme(id, SiteId);
|
||||
if (theme != null && _userPermissions.IsAuthorized(User, PermissionNames.Utilize, theme.PermissionList))
|
||||
{
|
||||
return theme;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (theme != null)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Get Attempt {ThemeId} {SiteId}", id, siteid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -86,14 +126,13 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/xxx
|
||||
// DELETE api/<controller>/5?siteid=x
|
||||
[HttpDelete("{themename}")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public void Delete(string themename)
|
||||
public void Delete(int id, int siteid)
|
||||
{
|
||||
List<Theme> themes = _themes.GetThemes().ToList();
|
||||
Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault();
|
||||
if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != Constants.ClientId)
|
||||
Theme theme = _themes.GetTheme(id, siteid);
|
||||
if (theme != null && theme.SiteId == _alias.SiteId && Utilities.GetAssemblyName(theme.ThemeName) != Constants.ClientId)
|
||||
{
|
||||
// remove theme assets
|
||||
if (_installationManager.UninstallPackage(theme.PackageName))
|
||||
@@ -126,7 +165,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Delete Attempt {Themename}", themename);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Delete Attempt {ThemeId} {SiteId}", id, siteid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,38 +179,22 @@ namespace Oqtane.Infrastructure
|
||||
fromEmail = settingRepository.GetSettingValue(settings, "SMTPSender", "");
|
||||
fromName = string.IsNullOrEmpty(fromName) ? site.Name : fromName;
|
||||
}
|
||||
try
|
||||
if (MailboxAddress.TryParse(fromEmail, out from))
|
||||
{
|
||||
// exception handler is necessary because of https://github.com/jstedfast/MimeKit/issues/1186
|
||||
if (MailboxAddress.TryParse(fromEmail, out _))
|
||||
{
|
||||
from = new MailboxAddress(fromName, fromEmail);
|
||||
}
|
||||
from.Name = fromName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// parse error creating sender mailbox address
|
||||
}
|
||||
if (from == null)
|
||||
else
|
||||
{
|
||||
|
||||
mailboxAddressValidationError += $" Invalid Sender: {fromName} <{fromEmail}>";
|
||||
}
|
||||
|
||||
// recipient
|
||||
try
|
||||
if (MailboxAddress.TryParse(toEmail, out to))
|
||||
{
|
||||
// exception handler is necessary because of https://github.com/jstedfast/MimeKit/issues/1186
|
||||
if (MailboxAddress.TryParse(toEmail, out _))
|
||||
{
|
||||
to = new MailboxAddress(toName, toEmail);
|
||||
}
|
||||
to.Name = toName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// parse error creating recipient mailbox address
|
||||
}
|
||||
if (to == null)
|
||||
else
|
||||
{
|
||||
mailboxAddressValidationError += $" Invalid Recipient: {toName} <{toEmail}>";
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.9" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.2" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
|
||||
<PackageReference Include="MailKit" Version="4.13.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -386,6 +386,7 @@ namespace Oqtane.Repository
|
||||
moduledefinition.Categories = "Common";
|
||||
}
|
||||
|
||||
// default permissions
|
||||
if (moduledefinition.Categories == "Admin")
|
||||
{
|
||||
var shortName = moduledefinition.ModuleDefinitionName.Replace("Oqtane.Modules.Admin.", "").Replace(", Oqtane.Client", "");
|
||||
@@ -455,18 +456,21 @@ namespace Oqtane.Repository
|
||||
private List<Permission> ClonePermissions(int siteId, List<Permission> permissionList)
|
||||
{
|
||||
var permissions = new List<Permission>();
|
||||
foreach (var p in permissionList)
|
||||
if (permissionList != null)
|
||||
{
|
||||
var permission = new Permission();
|
||||
permission.SiteId = siteId;
|
||||
permission.EntityName = p.EntityName;
|
||||
permission.EntityId = p.EntityId;
|
||||
permission.PermissionName = p.PermissionName;
|
||||
permission.RoleId = null;
|
||||
permission.RoleName = p.RoleName;
|
||||
permission.UserId = p.UserId;
|
||||
permission.IsAuthorized = p.IsAuthorized;
|
||||
permissions.Add(permission);
|
||||
foreach (var p in permissionList)
|
||||
{
|
||||
var permission = new Permission();
|
||||
permission.SiteId = siteId;
|
||||
permission.EntityName = p.EntityName;
|
||||
permission.EntityId = p.EntityId;
|
||||
permission.PermissionName = p.PermissionName;
|
||||
permission.RoleId = null;
|
||||
permission.RoleName = p.RoleName;
|
||||
permission.UserId = p.UserId;
|
||||
permission.IsAuthorized = p.IsAuthorized;
|
||||
permissions.Add(permission);
|
||||
}
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace Oqtane.Repository
|
||||
if (site != null)
|
||||
{
|
||||
// initialize theme Assemblies
|
||||
site.Themes = _themeRepository.GetThemes().ToList();
|
||||
site.Themes = _themeRepository.GetThemes(site.SiteId).ToList();
|
||||
|
||||
// initialize module Assemblies
|
||||
var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
public interface IThemeRepository
|
||||
{
|
||||
IEnumerable<Theme> GetThemes();
|
||||
IEnumerable<Theme> GetThemes(int siteId);
|
||||
Theme GetTheme(int themeId, int siteId);
|
||||
void UpdateTheme(Theme theme);
|
||||
void DeleteTheme(int themeId);
|
||||
@@ -26,24 +26,25 @@ namespace Oqtane.Repository
|
||||
{
|
||||
private MasterDBContext _db;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly IPermissionRepository _permissions;
|
||||
private readonly ITenantManager _tenants;
|
||||
private readonly ISettingRepository _settings;
|
||||
private readonly IServerStateManager _serverState;
|
||||
private readonly string settingprefix = "SiteEnabled:";
|
||||
|
||||
public ThemeRepository(MasterDBContext context, IMemoryCache cache, ITenantManager tenants, ISettingRepository settings, IServerStateManager serverState)
|
||||
public ThemeRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ITenantManager tenants, ISettingRepository settings, IServerStateManager serverState)
|
||||
{
|
||||
_db = context;
|
||||
_cache = cache;
|
||||
_permissions = permissions;
|
||||
_tenants = tenants;
|
||||
_settings = settings;
|
||||
_serverState = serverState;
|
||||
}
|
||||
|
||||
public IEnumerable<Theme> GetThemes()
|
||||
public IEnumerable<Theme> GetThemes(int siteId)
|
||||
{
|
||||
// for consistency siteid should be passed in as parameter, but this would require breaking change
|
||||
return LoadThemes(_tenants.GetAlias().SiteId);
|
||||
return LoadThemes(siteId);
|
||||
}
|
||||
|
||||
public Theme GetTheme(int themeId, int siteId)
|
||||
@@ -56,6 +57,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
_db.Entry(theme).State = EntityState.Modified;
|
||||
_db.SaveChanges();
|
||||
_permissions.UpdatePermissions(theme.SiteId, EntityNames.Theme, theme.ThemeId, theme.PermissionList);
|
||||
|
||||
var settingname = $"{settingprefix}{_tenants.GetAlias().SiteKey}";
|
||||
var setting = _settings.GetSetting(EntityNames.Theme, theme.ThemeId, settingname);
|
||||
@@ -96,6 +98,7 @@ namespace Oqtane.Repository
|
||||
Theme.ThemeSettingsType = theme.ThemeSettingsType;
|
||||
Theme.ContainerSettingsType = theme.ContainerSettingsType;
|
||||
Theme.PackageName = theme.PackageName;
|
||||
Theme.PermissionList = theme.PermissionList;
|
||||
Theme.Fingerprint = Utilities.GenerateSimpleHash(theme.ModifiedOn.ToString("yyyyMMddHHmm"));
|
||||
Themes.Add(Theme);
|
||||
}
|
||||
@@ -176,6 +179,9 @@ namespace Oqtane.Repository
|
||||
var siteKey = _tenants.GetAlias().SiteKey;
|
||||
var assemblies = new List<string>();
|
||||
|
||||
// get all module definition permissions for site
|
||||
List<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.Theme).ToList();
|
||||
|
||||
// get settings for site
|
||||
var settings = _settings.GetSettings(EntityNames.Theme).ToList();
|
||||
|
||||
@@ -212,6 +218,26 @@ namespace Oqtane.Repository
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (permissions.Count == 0)
|
||||
{
|
||||
// no module definition permissions exist for this site
|
||||
theme.PermissionList = ClonePermissions(siteId, theme.PermissionList);
|
||||
_permissions.UpdatePermissions(siteId, EntityNames.Theme, theme.ThemeId, theme.PermissionList);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (permissions.Any(item => item.EntityId == theme.ThemeId))
|
||||
{
|
||||
theme.PermissionList = permissions.Where(item => item.EntityId == theme.ThemeId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// permissions for theme do not exist for this site
|
||||
theme.PermissionList = ClonePermissions(siteId, theme.PermissionList);
|
||||
_permissions.UpdatePermissions(siteId, EntityNames.Theme, theme.ThemeId, theme.PermissionList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cache site assemblies
|
||||
@@ -220,6 +246,20 @@ namespace Oqtane.Repository
|
||||
{
|
||||
if (!serverState.Assemblies.Contains(assembly)) serverState.Assemblies.Add(assembly);
|
||||
}
|
||||
|
||||
// clean up any orphaned permissions
|
||||
var ids = new HashSet<int>(Themes.Select(item => item.ThemeId));
|
||||
foreach (var permission in permissions.Where(item => !ids.Contains(item.EntityId)))
|
||||
{
|
||||
try
|
||||
{
|
||||
_permissions.DeletePermission(permission.PermissionId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// multi-threading can cause a race condition to occur
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Themes;
|
||||
@@ -295,6 +335,14 @@ namespace Oqtane.Repository
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default permissions
|
||||
theme.PermissionList = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.Utilize, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Utilize, RoleNames.Registered, true)
|
||||
};
|
||||
|
||||
Debug.WriteLine($"Oqtane Info: Registering Theme {theme.ThemeName}");
|
||||
themes.Add(theme);
|
||||
index = themes.FindIndex(item => item.ThemeName == qualifiedThemeType);
|
||||
@@ -335,5 +383,27 @@ namespace Oqtane.Repository
|
||||
}
|
||||
return themes;
|
||||
}
|
||||
|
||||
private List<Permission> ClonePermissions(int siteId, List<Permission> permissionList)
|
||||
{
|
||||
var permissions = new List<Permission>();
|
||||
if (permissionList != null)
|
||||
{
|
||||
foreach (var p in permissionList)
|
||||
{
|
||||
var permission = new Permission();
|
||||
permission.SiteId = siteId;
|
||||
permission.EntityName = p.EntityName;
|
||||
permission.EntityId = p.EntityId;
|
||||
permission.PermissionName = p.PermissionName;
|
||||
permission.RoleId = null;
|
||||
permission.RoleName = p.RoleName;
|
||||
permission.UserId = p.UserId;
|
||||
permission.IsAuthorized = p.IsAuthorized;
|
||||
permissions.Add(permission);
|
||||
}
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Oqtane.Services
|
||||
}
|
||||
|
||||
// themes
|
||||
site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList());
|
||||
site.Themes = _themes.FilterThemes(_themes.GetThemes(site.SiteId).ToList());
|
||||
|
||||
// installation date used for fingerprinting static assets
|
||||
site.Fingerprint = Utilities.GenerateSimpleHash(_configManager.GetSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm")));
|
||||
|
||||
@@ -94,6 +94,9 @@ namespace Oqtane.Models
|
||||
[NotMapped]
|
||||
public List<ThemeControl> Containers { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public List<Permission> PermissionList { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string Template { get; set; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user