Merge pull request #5592 from oqtane/dev

6.2.0 Release
This commit is contained in:
Shaun Walker
2025-09-09 16:24:12 -04:00
committed by GitHub
192 changed files with 3418 additions and 5562 deletions

9
Oqtane.Application/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.vs/
bin/
obj/
*.user
artifacts/
msbuild.binlog
.vscode/
*.binlog
*.nupkg

View File

@ -1,47 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Version>6.1.5</Version>
<AssemblyName>Oqtane.Application.AppHost</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.8" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.8" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
<PackageReference Include="MailKit" Version="4.13.0" />
</ItemGroup>
<ItemGroup>
<!-- MySQL Database Provider Dependencies -->
<PackageReference Include="MySql.Data" Version="9.4.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.3.efcore.9.0.0" />
<!-- PostgreSQL Database Provider Dependencies -->
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<!-- SQLite Database Provider Dependencies -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.8" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.1" />
<!-- SQL Server Database Provider Dependencies -->
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.8" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Client" Version="6.1.5" />
<PackageReference Include="Oqtane.Server" Version="6.1.5" />
<PackageReference Include="Oqtane.Shared" Version="6.1.5" />
</ItemGroup>
</Project>

View File

@ -1,29 +0,0 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:44358/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Oqtane.AppHost": {
"commandName": "Project",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:44358/"
}
}
}

View File

@ -1,7 +0,0 @@
# Oqtane Application Template
![Oqtane](https://github.com/oqtane/framework/blob/master/oqtane.png?raw=true "Oqtane")
Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a fully dynamic digital experience which can be hosted on Static Blazor, Blazor Server, Blazor WebAssembly, or Blazor Hybrid (via .NET MAUI).
More information about Oqtane can be found at: [https://www.oqtane.org](https://www.oqtane.org)

View File

@ -1,11 +0,0 @@
The _content folder should only contain static resources from shared razor component libraries (RCLs). Static resources can be extracted from shared RCL Nuget packages by executing a Publish task on the module's Server project to a local folder and copying the files from the _content folder which is created. Each shared RCL would have its own appropriately named subfolder within the module's _content folder.
ie.
/_content
/Radzen.Blazor
/css
/fonts
/syncfusion.blazor
/scripts
/styles

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
This is the location where static resources such as images or style sheets should be located

View File

@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<AccelerateBuildsInVisualStudio>false</AccelerateBuildsInVisualStudio>
</PropertyGroup>
<ItemGroup>
<None Include="icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\Oqtane.Application.Client.csproj" />
<ProjectReference Include="..\Server\Oqtane.Application.Server.csproj" />
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Build',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Build',''))" />
</Target>
</Project>

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>$projectname$</id>
<version>1.0.0</version>
<authors>Oqtane.Application</authors>
<owners>Oqtane.Application</owners>
<title>Oqtane.Application</title>
<description>Oqtane.Application</description>
<copyright>Oqtane.Application</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<icon>icon.png</icon>
<tags>oqtane module</tags>
<releaseNotes></releaseNotes>
<summary></summary>
<dependencies>
<dependency id="Oqtane.Framework" version="6.1.4" />
</dependencies>
</metadata>
<files>
<file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Server\wwwroot\**\*.*" target="wwwroot" />
<file src="icon.png" target="" />
</files>
</package>

View File

@ -1,11 +0,0 @@
@echo off
set TargetFramework=%1
set ProjectName=%2
XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.dll" "..\AppHost\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.pdb" "..\AppHost\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.dll" "..\AppHost\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.pdb" "..\AppHost\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.dll" "..\AppHost\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.pdb" "..\AppHost\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Server\wwwroot\*" "..\AppHost\wwwroot\" /Y /S /I

View File

@ -1,12 +0,0 @@
#!/bin/bash
TargetFramework=$1
ProjectName=$2
cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.dll" "../AppHost/bin/Debug/$TargetFramework/"
cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.pdb" "../AppHost/bin/Debug/$TargetFramework/"
cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName$.Server.Oqtane.dll" "../AppHost/bin/Debug/$TargetFramework/"
cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName$.Server.Oqtane.pdb" "../AppHost/bin/Debug/$TargetFramework/"
cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName$.Shared.Oqtane.dll" "../AppHost/bin/Debug/$TargetFramework/"
cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName$.Shared.Oqtane.pdb" "../AppHost/bin/Debug/$TargetFramework/"
cp -rf "../Server/wwwroot/"* "../AppHost/wwwroot/"

Binary file not shown.

View File

@ -1,2 +0,0 @@
del "*.nupkg"
"nuget.exe" pack Oqtane.Application.nuspec -Properties projectname=Oqtane.Application

View File

@ -1 +0,0 @@
"nuget.exe" pack Oqtane.Application.nuspec -Properties projectname=Oqtane.Application

View File

@ -35,7 +35,7 @@
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
new Stylesheet(ModulePath() + "Module.css")
};
private ElementReference form;

View File

@ -38,12 +38,10 @@ else
}
@code {
public override string RenderMode => RenderModes.Static;
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
new Resource { ResourceType = ResourceType.Script, Url = ModulePath() + "Module.js" }
new Stylesheet(ModulePath() + "Module.css"),
new Script(ModulePath() + "Module.js")
};
List<Models.MyModule> _MyModules;

View File

@ -1,33 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
<BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.8" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Client" Version="6.1.4" />
<PackageReference Include="Oqtane.Shared" Version="6.1.4" />
</ItemGroup>
<PropertyGroup>
<!-- there may be other elements here -->
<BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Client" Version="6.2.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
using System.Threading.Tasks;
namespace Oqtane.Application.Client
{
internal class Program
{
static async Task Main(string[] args)
{
// defer client startup to Oqtane - do not modify
await Oqtane.Client.Program.Main(args);
}
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
using Oqtane.Services;
using Oqtane.Application.Services;
@ -8,7 +9,10 @@ namespace Oqtane.Application.Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyModuleService, MyModuleService>();
if (!services.Any(s => s.ServiceType == typeof(IMyModuleService)))
{
services.AddScoped<IMyModuleService, MyModuleService>();
}
}
}
}

View File

@ -16,8 +16,8 @@ namespace Oqtane.Application.MyTheme
ContainerSettingsType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane",
Resources = new List<Resource>()
{
new Script(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
new Stylesheet("~/Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
}
};

View File

@ -2,18 +2,18 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>Oqtane.Application.Template</id>
<version>6.1.5</version>
<title>Oqtane Application Solution For Blazor</title>
<version>6.2.0</version>
<title>Oqtane Application Template For Blazor</title>
<authors>Shaun Walker</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
<icon>Build/icon.png</icon>
<icon>icon.png</icon>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<description>Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a fully dynamic digital experience which can be hosted on Static Blazor, Blazor Server, Blazor WebAssembly, or Blazor Hybrid (via .NET MAUI).</description>
<language>en-US</language>
<tags>Web ASP.NET Blazor Oqtane Modular Multi-Tenant "Open Source" "SQL Server" MySQL PostgreSQL SQLite</tags>
<readme>AppHost/README.md</readme>
<readme>README.md</readme>
<packageTypes>
<packageType name="Template" />
</packageTypes>

View File

@ -2,47 +2,32 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35506.116 d17.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oqtane.Application.AppHost", "AppHost\Oqtane.Application.AppHost.csproj", "{5BDDA15B-05CF-41B2-BF12-D532D1A561D1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Server", "Server\Oqtane.Application.Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Client", "Client\Oqtane.Application.Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Server", "Server\Oqtane.Application.Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Shared", "Shared\Oqtane.Application.Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Build", "Build\Oqtane.Application.Build.csproj", "{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5BDDA15B-05CF-41B2-BF12-D532D1A561D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BDDA15B-05CF-41B2-BF12-D532D1A561D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BDDA15B-05CF-41B2-BF12-D532D1A561D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BDDA15B-05CF-41B2-BF12-D532D1A561D1}.Release|Any CPU.Build.0 = Release|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.Build.0 = Release|Any CPU
{04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.Build.0 = Release|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.Build.0 = Release|Any CPU
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.Build.0 = Release|Any CPU
{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1D016F15-46FE-4726-8DFD-2E4FD4DC7668}
EndGlobalSection
EndGlobal

View File

@ -1,6 +1,6 @@
# Oqtane Application Template
This folder contains content files for a Visual Studio Project Template designed for Oqtane development projects. The template relies on the native templating capabilities of the .NET Command Line Interface (CLI):
This is a Visual Studio Project Template designed for Oqtane development projects. This template relies on the native templating capabilities of the .NET Command Line Interface (CLI):
```
dotnet new install Oqtane.Application.Template
@ -16,5 +16,4 @@ The solution also contains Build, Client, Server, and Shared folders which is wh
*Known Issues*
- do not use the term "Oqtane" in your output name or else you will experience namespace conflicts
- the application's Build project is missing the *.nuspec file as Nuget is excluding it from the template - not sure why
- when calling "dotnet new" the PostBuild section in the Oqtane.Application.Build.csproj is being modified incorrectly - not sure why

View File

@ -1,36 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Content Remove="wwwroot\_content\**\*.*" />
<None Include="wwwroot\_content\**\*.*" />
</ItemGroup>
<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.8" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\Oqtane.Application.Client.csproj" />
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Server" Version="6.1.4" />
<PackageReference Include="Oqtane.Shared" Version="6.1.4" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\Themes\Oqtane.Application.MyTheme\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Server" Version="6.2.0" />
</ItemGroup>
</Project>

View File

@ -1,17 +1,17 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Oqtane.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
namespace Oqtane.Application.AppHost
namespace Oqtane.Application.Server
{
public class Program
{
public static void Main(string[] args)
{
// defer server startup to Oqtane - do not modify
var host = BuildWebHost(args);
var databaseManager = host.Services.GetService<IDatabaseManager>();
var install = databaseManager.Install();
@ -20,7 +20,7 @@ namespace Oqtane.Application.AppHost
var filelogger = host.Services.GetRequiredService<ILogger<Program>>();
if (filelogger != null)
{
filelogger.LogError($"[Oqtane.Application.AppHost.Program.Main] {install.Message}");
filelogger.LogError($"[Oqtane.Application.Server.Program.Main] {install.Message}");
}
}
else
@ -35,9 +35,8 @@ namespace Oqtane.Application.AppHost
.AddCommandLine(args)
.AddEnvironmentVariables()
.Build())
.UseStartup<Oqtane.Startup>()
.UseStartup<Startup>()
.ConfigureLocalizationSettings()
.Build();
}
}

View File

@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Shared;
using Microsoft.AspNetCore.Cors.Infrastructure;
namespace Oqtane.Application.Server
{
public class Startup
{
private readonly IConfigurationRoot _configuration;
private readonly IWebHostEnvironment _environment;
public Startup(IWebHostEnvironment environment)
{
AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(environment.ContentRootPath, "Data"));
var builder = new ConfigurationBuilder()
.SetBasePath(environment.ContentRootPath)
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{environment.EnvironmentName}.json", true, true)
.AddEnvironmentVariables();
_configuration = builder.Build();
_environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
// defer server startup to Oqtane - do not modify
services.AddOqtane(_configuration, _environment);
}
public void Configure(IApplicationBuilder app, IConfigurationRoot configuration, IWebHostEnvironment environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ISyncManager sync)
{
// defer server startup to Oqtane - do not modify
app.UseOqtane(configuration, environment, corsService, corsPolicyProvider, sync);
}
}
}

View File

@ -1,8 +1,8 @@
{
"RenderMode": "Interactive",
"RenderMode": "Static",
"Runtime": "Server",
"Database": {
"DefaultDBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server"
"DefaultDBType": ""
},
"ConnectionStrings": {
"DefaultConnection": ""
@ -57,8 +57,7 @@
}
},
"LogLevel": {
"Default": "Information",
"Notify": "Error"
"Default": "Information"
}
}
}
}

View File

@ -1,11 +0,0 @@
The _content folder should only contain static resources from shared razor component libraries (RCLs). Static resources can be extracted from shared RCL Nuget packages by executing a Publish task on the module's Server project to a local folder and copying the files from the _content folder which is created. Each shared RCL would have its own appropriately named subfolder within the module's _content folder.
ie.
/_content
/Radzen.Blazor
/css
/fonts
/syncfusion.blazor
/scripts
/styles

View File

@ -0,0 +1,22 @@
.rz-text-editor {
outline: none !important;
}
.rz-html-editor-dropdown-items,
.rz-popup,
.rz-editor-dialog-wrapper {
z-index: 9999 !important;
}
.rz-html-editor-dropdown-items .rz-html-editor-dropdown-item,
.rz-html-editor-dropdown-items .rz-html-editor-dropdown-item > * {
color: var(--rz-editor-button-color);
}
.rz-text-editor .rz-html-editor-dropdown .rz-html-editor-dropdown-value,
.rz-text-editor .rz-html-editor-dropdown .rz-html-editor-dropdown-trigger,
.rz-text-editor .rz-html-editor-colorpicker .rz-html-editor-color {
color: var(--rz-editor-button-color);
}
.rz-text-editor .rz-colorpicker.rz-state-disabled {
border: none !important;
}

View File

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 318 B

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 427 B

After

Width:  |  Height:  |  Size: 427 B

View File

Before

Width:  |  Height:  |  Size: 875 B

After

Width:  |  Height:  |  Size: 875 B

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 801 B

After

Width:  |  Height:  |  Size: 801 B

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 177 B

After

Width:  |  Height:  |  Size: 177 B

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

@ -0,0 +1,47 @@
var Oqtane = Oqtane || {};
Oqtane.RadzenTextEditor = {
initialize: function (editor) {
if (typeof Radzen.openPopup === "function" && Radzen.openPopup !== Oqtane.RadzenTextEditor.openPopup) {
Oqtane.RadzenTextEditor.radzenOpenPopup = Radzen.openPopup;
Radzen.openPopup = Oqtane.RadzenTextEditor.openPopup;
}
},
openPopup: function () {
Oqtane.RadzenTextEditor.radzenOpenPopup.apply(this, arguments);
var id = arguments[1];
var popup = document.getElementById(id);
if (popup) {
Oqtane.RadzenTextEditor.updateButtonStyles(popup);
}
},
setBackgroundColor: function (editor, color) {
editor.getElementsByClassName("rz-html-editor-content")[0].style.backgroundColor = color;
},
updateDialogLayout: function (editor) {
var dialogs = editor.parentElement.getElementsByClassName('rz-dialog-wrapper');
for (var dialog of dialogs) {
document.body.appendChild(dialog);
dialog.classList.add('rz-editor-dialog-wrapper', 'text-dark');
this.updateButtonStyles(dialog);
}
},
updateButtonStyles: function (parent) {
var primaryBtns = parent.getElementsByClassName('rz-primary');
if (primaryBtns) {
for (var btn of primaryBtns) {
btn.classList.remove('rz-button', 'rz-primary');
btn.classList.add('btn', 'btn-primary');
}
}
var secondaryBtns = parent.getElementsByClassName('rz-secondary');
if (secondaryBtns) {
for (var btn of secondaryBtns) {
btn.classList.remove('rz-button', 'rz-secondary');
btn.classList.add('btn', 'btn-secondary');
}
}
}
}

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,17 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Shared" Version="6.1.4" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Shared" Version="6.2.0" />
</ItemGroup>
</Project>

BIN
Oqtane.Application/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,8 +1,10 @@
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Localization;
using Oqtane.Interfaces;
using Oqtane.Providers;
using Oqtane.Services;
using Oqtane.Shared;
using Radzen;
namespace Microsoft.Extensions.DependencyInjection
{
@ -23,7 +25,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<SiteState>();
services.AddScoped<IInstallationService, InstallationService>();
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
services.AddScoped<IThemeService, ThemeService>();
services.AddScoped<IThemeService, Oqtane.Services.ThemeService>();
services.AddScoped<IAliasService, AliasService>();
services.AddScoped<ITenantService, TenantService>();
services.AddScoped<ISiteService, SiteService>();
@ -39,7 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<INotificationService, NotificationService>();
services.AddScoped<INotificationService, Oqtane.Services.NotificationService>();
services.AddScoped<IFolderService, FolderService>();
services.AddScoped<IFileService, FileService>();
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
@ -59,6 +61,12 @@ namespace Microsoft.Extensions.DependencyInjection
// providers
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.RadzenTextEditor>();
services.AddRadzenComponents();
var localizer = services.BuildServiceProvider().GetService<IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor>>();
Oqtane.Modules.Controls.RadzenEditorDefinitions.Localizer = localizer;
return services;
}

View File

@ -14,7 +14,7 @@
<div class="container">
<div class="row">
<div class="mx-auto text-center">
<img src="oqtane-black.png" />
<img src="installer-logo.png" />
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET @Environment.Version.Major)</div>
</div>
</div>
@ -182,7 +182,7 @@
}
else
{
_databaseName = "LocalDB";
_databaseName = Constants.DefaultDBName;
}
LoadDatabaseConfigComponent();
@ -269,8 +269,8 @@
SiteName = Constants.DefaultSite,
Register = _register,
SiteTemplate = _template,
RenderMode = RenderModes.Static,
Runtime = Runtimes.Server
RenderMode = "", // provided by appsettings.json
Runtime = "" // provided by appsettings.json
};
var installation = await InstallationService.Install(config);

View File

@ -116,11 +116,19 @@ else
{
try
{
await JobService.StartJobAsync(jobId);
await logger.LogInformation("Job Started {JobId}", jobId);
AddModuleMessage(Localizer["Message.Job.Start"], MessageType.Success);
_jobs = await JobService.GetJobsAsync();
StateHasChanged();
Job _job = await JobService.GetJobAsync(jobId);
if (!_job.IsEnabled)
{
AddModuleMessage(Localizer["Message.Job.Disabled"], MessageType.Warning);
}
else
{
await JobService.StartJobAsync(jobId);
await logger.LogInformation("Job Started {JobId}", jobId);
AddModuleMessage(Localizer["Message.Job.Start"], MessageType.Success);
_jobs = await JobService.GetJobsAsync();
StateHasChanged();
}
}
catch (Exception ex)
{

View File

@ -310,6 +310,6 @@
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Module.Upload"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -56,7 +56,7 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options the user can select from" 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 custom Settings (ie. 'EntityName:Countries')." ResourceKey="Options">Options: </Label>
<div class="col-sm-9">
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
</div>

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) && _entityName != "-" && int.TryParse(_entityId, out int entityId))
{
var setting = new Setting();
setting.EntityName = _entityName;
setting.EntityId = 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,56 @@
@namespace Oqtane.Modules.Admin.Settings
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ISettingService SettingService
@inject IStringLocalizer<ImportSettings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="settings" HelpText="Provide settings in comma delimited format using the column template specified" ResourceKey="Settings">Settings:</Label>
<div class="col-sm-9">
<textarea id="settings" class="form-control" @bind="@_settings" rows="5" required></textarea>
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="Import">@Localizer["Import"]</button>&nbsp;
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _settings = "Entity,Id,Name,Value,Private\n";
public override string Title => "Import Settings";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
private async Task Import()
{
try
{
if (!string.IsNullOrEmpty(_settings))
{
ShowProgressIndicator();
var result = await SettingService.ImportSettingsAsync(new Result { Message = _settings });
if (result.Success)
{
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
}
HideProgressIndicator();
}
else
{
AddModuleMessage(Localizer["Message.Import.Validation"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Importing Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Import"], MessageType.Error);
}
}
}

View File

@ -0,0 +1,146 @@
@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-4">
<ActionLink Action="Add" Text="Add Setting" Security="SecurityAccessLevel.Host" ResourceKey="AddSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
<ActionLink Action="ImportSettings" Text="Import" Class="btn btn-secondary ms-1" Security="SecurityAccessLevel.Host" ResourceKey="ImportSettings" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
</div>
<div class="col-sm-4">
<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-4">
<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

@ -54,15 +54,18 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing. The sitemap is cached for 5 minutes and the cache can be manually cleared." ResourceKey="SiteMap">Site Map: </Label>
<div class="col-sm-9">
@ -224,11 +227,14 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="SmtpSSL">SSL Required: </Label>
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify the type of SSL connection for your SMTP server" ResourceKey="SmtpSSL">SSL Options: </Label>
<div class="col-sm-9">
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
<select id="smtpssl" class="form-select" @bind="@_smtpssl">
<option value="None">@Localizer["None"]</option>
<option value="Auto">@Localizer["Auto"]</option>
<option value="StartTls">@Localizer["StartTls"]</option>
<option value="SslOnConnect">@Localizer["SslOnConnect"]</option>
<option value="StartTlsWhenAvailable">@Localizer["StartTlsWhenAvailable"]</option>
</select>
</div>
</div>
@ -504,7 +510,7 @@
private string _smtpauthentication = "Basic";
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = "True";
private string _smtpssl = "Auto";
private string _smtpusername = string.Empty;
private string _smtppassword = string.Empty;
private string _smtppasswordtype = "password";
@ -613,7 +619,9 @@
{
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "Auto");
if (_smtpssl == "True") _smtpssl = "SslOnConnect";
if (_smtpssl == "False") _smtpssl = "StartTlsWhenAvailable";
_smtpauthentication = SettingService.GetSetting(settings, "SMTPAuthentication", "Basic");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
@ -657,7 +665,8 @@
if (tenant != null)
{
_tenant = tenant.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType && item.Name != "LocalDB")?.Name;
// hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order
_database = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name;
_connectionstring = tenant.DBConnectionString;
}
}

View File

@ -237,7 +237,7 @@ else
}
else
{
_databaseName = "LocalDB";
_databaseName = Constants.DefaultDBName;
}
LoadDatabaseConfigComponent();
}

View File

@ -200,7 +200,8 @@ else
if (tenant != null)
{
_tenant = tenant.Name;
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name;
// hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order
_databasetype = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name;
}
}
else
@ -211,7 +212,7 @@ else
}
else
{
_databasetype = "LocalDB";
_databasetype = Constants.DefaultDBName;
}
_showConnectionString = false;
LoadDatabaseConfigComponent();

View File

@ -310,6 +310,6 @@
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Theme.Upload"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -124,15 +124,19 @@
@if (!string.IsNullOrEmpty(p.Autocomplete))
{
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
var values = option.Split(':');
var name = values[0];
var value = values.Length > 1 ? values[1] : values[0];
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
{
<option value="@option" selected>@option</option>
<option value="@name" selected>@value</option>
}
else
{
<option value="@option">@option</option>
<option value="@name">@value</option>
}
}
</select>
@ -140,15 +144,19 @@
else
{
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
var values = option.Split(':');
var name = values[0];
var value = values.Length > 1 ? values[1] : values[0];
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
{
<option value="@option" selected>@option</option>
<option value="@name" selected>@value</option>
}
else
{
<option value="@option">@option</option>
<option value="@name">@value</option>
}
}
</select>
@ -404,6 +412,14 @@
_togglepassword = SharedLocalizer["ShowPassword"];
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
foreach (var profile in _profiles)
{
if (profile.Options.ToLower().StartsWith("entityname:"))
{
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
profile.Options = string.Join(",", options.Select(kvp => $"{kvp.Key}:{kvp.Value}"));
}
}
_timezones = TimeZoneService.GetTimeZones();
if (PageState.User != null)

View File

@ -86,15 +86,19 @@
@if (!string.IsNullOrEmpty(p.Options))
{
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
var values = option.Split(':');
var name = values[0];
var value = values.Length > 1 ? values[1] : values[0];
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
{
<option value="@option" selected>@option</option>
<option value="@name" selected>@value</option>
}
else
{
<option value="@option">@option</option>
<option value="@name">@value</option>
}
}
</select>
@ -145,6 +149,14 @@
{
_timezones = TimeZoneService.GetTimeZones();
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
foreach (var profile in _profiles)
{
if (profile.Options.ToLower().StartsWith("entityname:"))
{
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
profile.Options = string.Join(",", options.Select(kvp => $"{kvp.Key}:{kvp.Value}"));
}
}
_settings = new Dictionary<string, string>();
_timezoneid = PageState.Site.TimeZoneId;
_initialized = true;

View File

@ -119,15 +119,19 @@
@if (!string.IsNullOrEmpty(p.Options))
{
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
var values = option.Split(':');
var name = values[0];
var value = values.Length > 1 ? values[1] : values[0];
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
{
<option value="@option" selected>@option</option>
<option value="@name" selected>@value</option>
}
else
{
<option value="@option">@option</option>
<option value="@name">@value</option>
}
}
</select>
@ -204,6 +208,14 @@
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
foreach (var profile in _profiles)
{
if (profile.Options.ToLower().StartsWith("entityname:"))
{
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
profile.Options = string.Join(",", options.Select(kvp => $"{kvp.Key}:{kvp.Value}"));
}
}
_timezones = TimeZoneService.GetTimeZones();
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))

View File

@ -114,6 +114,12 @@ else
<input id="cookiename" class="form-control" @bind="@_cookiename" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cookiedomain" HelpText="If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')" ResourceKey="CookieDomain">Cookie Domain:</Label>
<div class="col-sm-9">
<input id="cookiedomain" class="form-control" @bind="@_cookiedomain" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
<div class="col-sm-9">
@ -314,6 +320,15 @@ else
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="requirenonce" HelpText="Specify if Nonce validation is required for the ID token (the default is true)" ResourceKey="RequireNonce">Require Nonce?</Label>
<div class="col-sm-9">
<select id="requirenonce" class="form-select" @bind="@_requirenonce" required>
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
@ -516,6 +531,7 @@ else
private string _requireconfirmedemail;
private string _twofactor;
private string _cookiename;
private string _cookiedomain;
private string _cookieexpiration;
private string _alwaysremember;
private string _logouteverywhere;
@ -543,6 +559,7 @@ else
private string _clientsecrettype = "password";
private string _toggleclientsecret = string.Empty;
private string _authresponsetype;
private string _requirenonce;
private string _scopes;
private string _parameters;
private string _pkce;
@ -590,6 +607,7 @@ else
{
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
@ -629,6 +647,7 @@ else
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = SharedLocalizer["ShowPassword"];
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
_requirenonce = SettingService.GetSetting(settings, "ExternalLogin:RequireNonce", "true");
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
@ -725,6 +744,7 @@ else
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieDomain", _cookiedomain, true);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false);
@ -750,6 +770,7 @@ else
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RequireNonce", _requirenonce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);

View File

@ -0,0 +1,45 @@
@namespace Oqtane.Modules.Controls
@using System.IO
@using Radzen
@using Radzen.Blazor
@inject DialogService DialogService
@inject IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer
<div class="d-flex">
<FileManager @ref="_fileManager" Filter="@Filters" />
</div>
<div class="d-flex">
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
</div>
<div class="mt-1 text-end">
<RadzenButton Text="OK" Click=@OnOkClick />
<RadzenButton Text="Cancel" Click=@OnCancelClick ButtonStyle="ButtonStyle.Secondary" />
</div>
@code {
private FileManager _fileManager;
private string _message = string.Empty;
[Parameter]
public string Filters { get; set; }
private void OnCancelClick()
{
DialogService.Close(null);
}
private void OnOkClick()
{
_message = string.Empty;
var file = _fileManager.GetFile();
if (file != null)
{
var result = $"<img src=\"{file.Url}\" style=\"max-width: 100%\" alt=\"{file.Name}\" />";
DialogService.Close(result);
}
else
{
_message = Localizer["Message.Require.Image"];
StateHasChanged();
}
}
}

View File

@ -0,0 +1,222 @@
@namespace Oqtane.Modules.Controls
@using System.IO
@using Radzen
@using Radzen.Blazor
@inherits ModuleControlBase
@inject DialogService DialogService
@inject Radzen.ThemeService ThemeService
@inject ISettingService SettingService
@inject IRadzenEditorSettingService EditorSettingService
@inject IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer
<div class="row">
<div class="col-12 col-sm-3">
@Localizer["Scope"]
</div>
<div class="col-12 col-sm-9">
<RadzenRadioButtonList @bind-Value="@_settingScope" TValue="int" Change="OnScopeChanged">
<Items>
<RadzenRadioButtonListItem Text="@Localizer["Site"]" Value="0" />
<RadzenRadioButtonListItem Text="@Localizer["Module"]" Value="1" />
</Items>
</RadzenRadioButtonList>
</div>
</div>
<div class="row mt-2">
<div class="col-12 col-sm-3">
@Localizer["Theme"]
</div>
<div class="col-12 col-sm-9">
<RadzenDropDown @bind-Value="_theme" TValue="string" Data="@_themes" Style="width: 100%;">
<Template>
<span>@Localizer[$"theme.{context}"]</span>
</Template>
</RadzenDropDown>
</div>
</div>
<div class="row mt-2">
<div class="col-12 col-sm-3">
@Localizer["Background"]
</div>
<div class="col-12 col-sm-9">
<RadzenDropDown @bind-Value="_background" TValue="string" Data="_backgroundColors" Style="width: 100%;">
<Template>
<span>@Localizer[context]</span>
</Template>
</RadzenDropDown>
</div>
</div>
<div class="row mt-2">
<div class="col-12 col-sm-3">
@Localizer["Toolbar"]
</div>
<div class="col-12 col-sm-9">
<div class="row">
<div class="col-12 col-sm-7">
<RadzenDropDown TValue="string" @bind-Value="_addToolbarItem" Data="@RadzenEditorDefinitions.ToolbarItems.Keys" Style="width: 100%;">
</RadzenDropDown>
</div>
<div class="col-12 col-sm-5 text-end">
<button type="button" class="btn btn-primary" @onclick="AddToolbarItem">@Localizer["Add"]</button>
<button type="button" class="btn btn-secondary" @onclick="ResetToolbarItem">@Localizer["Reset"]</button>
</div>
</div>
<div class="row mt-2" style="max-height: 500px; overflow-y: scroll;">
<div class="col">
<RadzenDropZoneContainer TItem="ToolbarItem" Data="_toolbarItems"
ItemSelector="@((i, z) => true)"
CanDrop="@((i) => true)"
Drop="OnToolbarItemDrop"
ItemRender="OnToolbarItemRender">
<ChildContent>
<RadzenDropZone TItem="ToolbarItem" class="rounded">
</RadzenDropZone>
</ChildContent>
<Template>
<div>
<strong>@context.Name</strong>
<RadzenButton Icon="delete" Click="@((e) => DeleteToolbarItem(context))" Size="ButtonSize.ExtraSmall" ButtonStyle="ButtonStyle.Light" />
</div>
</Template>
</RadzenDropZoneContainer>
</div>
</div>
</div>
</div>
<div class="mt-2 text-end">
<RadzenButton Text="OK" Click=@OnOkClick />
<RadzenButton Text="Cancel" Click=@OnCancelClick ButtonStyle="ButtonStyle.Secondary" />
</div>
@code {
private readonly IList<string> _themes = new List<string>
{
"default",
"dark",
"material",
"material-dark",
"standard",
"standard-dark",
"humanistic",
"humanistic-dark",
"software",
"software-dark"
};
private readonly IList<string> _backgroundColors = new List<string> { "Default", "Light", "Dark" };
private int _settingScope;
private string _theme;
private string _background;
private IList<ToolbarItem> _toolbarItems = new List<ToolbarItem>();
private string _addToolbarItem;
protected override async Task OnInitializedAsync()
{
_settingScope = await EditorSettingService.GetSettingScopeAsync(ModuleState.ModuleId);
await LoadSettings();
}
private async Task<RadzenEditorSetting> LoadSettingsFromModule()
{
return await EditorSettingService.LoadSettingsFromModuleAsync(ModuleState.ModuleId);
}
private async Task<RadzenEditorSetting> LoadSettingsFromSite()
{
return await EditorSettingService.LoadSettingsFromSiteAsync(PageState.Site.SiteId);
}
private async Task LoadSettings()
{
var editorSetting = _settingScope == 1 ? await LoadSettingsFromModule() : await LoadSettingsFromSite();
_theme = editorSetting.Theme;
_background = editorSetting.Background;
_toolbarItems = editorSetting.ToolbarItems.Split(',').Select((v, i) =>
{
return new ToolbarItem { Key = i, Name = v };
}).ToList();
}
private async Task OnScopeChanged()
{
await LoadSettings();
StateHasChanged();
}
private void AddToolbarItem()
{
if (!string.IsNullOrEmpty(_addToolbarItem))
{
_toolbarItems.Add(new ToolbarItem { Key = _toolbarItems.Count, Name = _addToolbarItem });
_addToolbarItem = string.Empty;
StateHasChanged();
}
}
private void ResetToolbarItem()
{
_toolbarItems = RadzenEditorDefinitions.DefaultToolbarItems.Split(',').Select((v, i) =>
{
return new ToolbarItem { Key = i, Name = v };
}).ToList();
StateHasChanged();
}
private void DeleteToolbarItem(ToolbarItem item)
{
_toolbarItems.Remove(item);
StateHasChanged();
}
private void OnCancelClick()
{
DialogService.Close(false);
}
private async Task OnOkClick()
{
var editorSetting = new RadzenEditorSetting
{
Theme = _theme,
Background = _background,
ToolbarItems = string.Join(",", _toolbarItems.Select(i => i.Name))
};
await EditorSettingService.UpdateSettingScopeAsync(ModuleState.ModuleId, _settingScope);
if (_settingScope == 1)
{
await EditorSettingService.SaveModuleSettingsAsync(ModuleState.ModuleId, editorSetting);
}
else
{
await EditorSettingService.SaveSiteSettingsAsync(PageState.Site.SiteId, editorSetting);
}
DialogService.Close(true);
}
private void OnToolbarItemDrop(RadzenDropZoneItemEventArgs<ToolbarItem> args)
{
if (args.ToItem != null && args.ToItem.Key != args.Item.Key)
{
_toolbarItems.Remove(args.Item);
_toolbarItems.Insert(_toolbarItems.IndexOf(args.ToItem), args.Item);
}
}
private void OnToolbarItemRender(RadzenDropZoneItemRenderEventArgs<ToolbarItem> args)
{
args.Attributes.Add("class", "rz-card rz-variant-flat rz-background-color-primary-lighter rz-color-on-primary-lighter rz-p-2 d-inline-block ms-1 mt-1");
}
public class ToolbarItem
{
public int Key { get; set; }
public string Name { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More