10.0.0 Release
10.0.0 Release
This commit is contained in:
@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>6.2.1</Version>
|
||||
<Version>10.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -18,11 +18,6 @@
|
||||
},
|
||||
"sourceName": "Oqtane.Application",
|
||||
"preferNameDirectory": true,
|
||||
"guids": [
|
||||
"04B05448-788F-433D-92C0-FED35122D45A",
|
||||
"AA8E58A1-CD09-4208-BF66-A8BB341FD669",
|
||||
"18D73F73-D7BE-4388-85BA-FBD9AC96FCA2"
|
||||
],
|
||||
"symbols": {
|
||||
"Framework": {
|
||||
"type": "parameter",
|
||||
@ -30,12 +25,12 @@
|
||||
"datatype": "choice",
|
||||
"choices": [
|
||||
{
|
||||
"choice": "net9.0",
|
||||
"description": "Target net9.0"
|
||||
"choice": "net10.0",
|
||||
"description": "Target net10.0"
|
||||
}
|
||||
],
|
||||
"replaces": "net9.0",
|
||||
"defaultValue": "net9.0"
|
||||
"replaces": "net10.0",
|
||||
"defaultValue": "net10.0"
|
||||
},
|
||||
"HttpPort": {
|
||||
"type": "parameter",
|
||||
@ -80,7 +75,7 @@
|
||||
},
|
||||
"primaryOutputs": [
|
||||
{
|
||||
"path": "Oqtane.Application.sln"
|
||||
"path": "Oqtane.Application.slnx"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules;
|
||||
|
||||
namespace Oqtane.Application.MyModule
|
||||
{
|
||||
public class ModuleInfo : IModule
|
||||
{
|
||||
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||
{
|
||||
Name = "MyModule",
|
||||
Description = "Example module",
|
||||
Version = "1.0.0",
|
||||
ServerManagerType = "Oqtane.Application.Manager.MyModuleManager, Oqtane.Application.Server.Oqtane",
|
||||
ReleaseVersions = "1.0.0",
|
||||
Dependencies = "Oqtane.Application.Shared.Oqtane",
|
||||
PackageName = "Oqtane.Application"
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
|
||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
</PropertyGroup>
|
||||
|
||||
<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" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -24,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Client" Version="6.2.1" />
|
||||
<PackageReference Include="Oqtane.Client" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,141 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name.Text" xml:space="preserve">
|
||||
<value>Name: </value>
|
||||
</data>
|
||||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the name</value>
|
||||
</data>
|
||||
<data name="Save" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="Message.SaveValidation" xml:space="preserve">
|
||||
<value>Please Provide All Required Information</value>
|
||||
</data>
|
||||
<data name="Message.SaveError" xml:space="preserve">
|
||||
<value>Error Saving MyModule</value>
|
||||
</data>
|
||||
<data name="Message.LoadError" xml:space="preserve">
|
||||
<value>Error Loading MyModule</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -1,147 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="Add.Text" xml:space="preserve">
|
||||
<value>Add MyModule</value>
|
||||
</data>
|
||||
<data name="Edit.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="Delete.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="Delete.Header" xml:space="preserve">
|
||||
<value>Delete MyModule</value>
|
||||
</data>
|
||||
<data name="Delete.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete This MyModule?</value>
|
||||
</data>
|
||||
<data name="Message.DisplayNone" xml:space="preserve">
|
||||
<value>No MyModules To Display</value>
|
||||
</data>
|
||||
<data name="Message.LoadError" xml:space="preserve">
|
||||
<value>Error Loading MyModule</value>
|
||||
</data>
|
||||
<data name="Message.DeleteError" xml:space="preserve">
|
||||
<value>Error Deleting MyModule</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -1,55 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Application.Services
|
||||
{
|
||||
public interface IMyModuleService
|
||||
{
|
||||
Task<List<Models.MyModule>> GetMyModulesAsync(int ModuleId);
|
||||
|
||||
Task<Models.MyModule> GetMyModuleAsync(int MyModuleId, int ModuleId);
|
||||
|
||||
Task<Models.MyModule> AddMyModuleAsync(Models.MyModule MyModule);
|
||||
|
||||
Task<Models.MyModule> UpdateMyModuleAsync(Models.MyModule MyModule);
|
||||
|
||||
Task DeleteMyModuleAsync(int MyModuleId, int ModuleId);
|
||||
}
|
||||
|
||||
public class MyModuleService : ServiceBase, IMyModuleService
|
||||
{
|
||||
public MyModuleService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
private string Apiurl => CreateApiUrl("MyModule");
|
||||
|
||||
public async Task<List<Models.MyModule>> GetMyModulesAsync(int ModuleId)
|
||||
{
|
||||
List<Models.MyModule> Tasks = await GetJsonAsync<List<Models.MyModule>>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty<Models.MyModule>().ToList());
|
||||
return Tasks.OrderBy(item => item.Name).ToList();
|
||||
}
|
||||
|
||||
public async Task<Models.MyModule> GetMyModuleAsync(int MyModuleId, int ModuleId)
|
||||
{
|
||||
return await GetJsonAsync<Models.MyModule>(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModuleId}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||
}
|
||||
|
||||
public async Task<Models.MyModule> AddMyModuleAsync(Models.MyModule MyModule)
|
||||
{
|
||||
return await PostJsonAsync<Models.MyModule>(CreateAuthorizationPolicyUrl($"{Apiurl}", EntityNames.Module, MyModule.ModuleId), MyModule);
|
||||
}
|
||||
|
||||
public async Task<Models.MyModule> UpdateMyModuleAsync(Models.MyModule MyModule)
|
||||
{
|
||||
return await PutJsonAsync<Models.MyModule>(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModule.MyModuleId}", EntityNames.Module, MyModule.ModuleId), MyModule);
|
||||
}
|
||||
|
||||
public async Task DeleteMyModuleAsync(int MyModuleId, int ModuleId)
|
||||
{
|
||||
await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModuleId}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Application.Template</id>
|
||||
<version>6.2.1</version>
|
||||
<version>10.0.0</version>
|
||||
<title>Oqtane Application Template For Blazor</title>
|
||||
<authors>Shaun Walker</authors>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
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("{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.Shared", "Shared\Oqtane.Application.Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
5
Oqtane.Application/Oqtane.Application.slnx
Normal file
5
Oqtane.Application/Oqtane.Application.slnx
Normal file
@ -0,0 +1,5 @@
|
||||
<Solution>
|
||||
<Project Path="Server\Oqtane.Application.Server.csproj" DefaultStartup="true" />
|
||||
<Project Path="Client\Oqtane.Application.Client.csproj" />
|
||||
<Project Path="Shared\Oqtane.Application.Shared.csproj" />
|
||||
</Solution>
|
||||
@ -1,87 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Interfaces;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Application.Repository;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Application.Manager
|
||||
{
|
||||
public class MyModuleManager : MigratableModuleBase, IInstallable, IPortable, ISearchable
|
||||
{
|
||||
private readonly IMyModuleRepository _MyModuleRepository;
|
||||
private readonly IDBContextDependencies _DBContextDependencies;
|
||||
|
||||
public MyModuleManager(IMyModuleRepository MyModuleRepository, IDBContextDependencies DBContextDependencies)
|
||||
{
|
||||
_MyModuleRepository = MyModuleRepository;
|
||||
_DBContextDependencies = DBContextDependencies;
|
||||
}
|
||||
|
||||
public bool Install(Tenant tenant, string version)
|
||||
{
|
||||
return Migrate(new Context(_DBContextDependencies), tenant, MigrationType.Up);
|
||||
}
|
||||
|
||||
public bool Uninstall(Tenant tenant)
|
||||
{
|
||||
return Migrate(new Context(_DBContextDependencies), tenant, MigrationType.Down);
|
||||
}
|
||||
|
||||
public string ExportModule(Module module)
|
||||
{
|
||||
string content = "";
|
||||
List<Models.MyModule> MyModules = _MyModuleRepository.GetMyModules(module.ModuleId).ToList();
|
||||
if (MyModules != null)
|
||||
{
|
||||
content = JsonSerializer.Serialize(MyModules);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
public void ImportModule(Module module, string content, string version)
|
||||
{
|
||||
List<Models.MyModule> MyModules = null;
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
MyModules = JsonSerializer.Deserialize<List<Models.MyModule>>(content);
|
||||
}
|
||||
if (MyModules != null)
|
||||
{
|
||||
foreach(var Task in MyModules)
|
||||
{
|
||||
_MyModuleRepository.AddMyModule(new Models.MyModule { ModuleId = module.ModuleId, Name = Task.Name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
|
||||
{
|
||||
var searchContentList = new List<SearchContent>();
|
||||
|
||||
foreach (var MyModule in _MyModuleRepository.GetMyModules(pageModule.ModuleId))
|
||||
{
|
||||
if (MyModule.ModifiedOn >= lastIndexedOn)
|
||||
{
|
||||
searchContentList.Add(new SearchContent
|
||||
{
|
||||
EntityName = "MyModule",
|
||||
EntityId = MyModule.MyModuleId.ToString(),
|
||||
Title = MyModule.Name,
|
||||
Body = MyModule.Name,
|
||||
ContentModifiedBy = MyModule.ModifiedBy,
|
||||
ContentModifiedOn = MyModule.ModifiedOn
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(searchContentList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations;
|
||||
using Oqtane.Application.Migrations.EntityBuilders;
|
||||
using Oqtane.Application.Repository;
|
||||
|
||||
namespace Oqtane.Application.Migrations
|
||||
{
|
||||
[DbContext(typeof(Context))]
|
||||
[Migration("Oqtane.Application.01.00.00.00")]
|
||||
public class InitializeModule : MultiDatabaseMigration
|
||||
{
|
||||
public InitializeModule(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var myModuleEntityBuilder = new MyModuleEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
myModuleEntityBuilder.Create();
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var myModuleEntityBuilder = new MyModuleEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
myModuleEntityBuilder.Drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,20 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.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>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>
|
||||
</PropertyGroup>
|
||||
|
||||
<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" />
|
||||
<Compile Remove="wwwroot\Modules\Templates\**" />
|
||||
<Compile Remove="wwwroot\Themes\Templates\**" />
|
||||
<Content Remove="wwwroot\Modules\Templates\**" />
|
||||
<Content Remove="wwwroot\Themes\Templates\**" />
|
||||
<EmbeddedResource Remove="wwwroot\Modules\Templates\**" />
|
||||
<EmbeddedResource Remove="wwwroot\Themes\Templates\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -23,7 +33,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Server" Version="6.2.1" />
|
||||
<PackageReference Include="Oqtane.Server" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Oqtane.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Application.Server
|
||||
{
|
||||
@ -11,32 +15,41 @@ namespace Oqtane.Application.Server
|
||||
{
|
||||
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 builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(builder.Environment.ContentRootPath, "Data"));
|
||||
|
||||
var configurationBuilder = new ConfigurationBuilder()
|
||||
.SetBasePath(builder.Environment.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", false, true)
|
||||
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true, true)
|
||||
.AddEnvironmentVariables();
|
||||
var configuration = configurationBuilder.Build();
|
||||
|
||||
builder.Services.AddOqtane(configuration, builder.Environment);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
var corsService = app.Services.GetRequiredService<ICorsService>();
|
||||
var corsPolicyProvider = app.Services.GetRequiredService<ICorsPolicyProvider>();
|
||||
var syncManager = app.Services.GetRequiredService<ISyncManager>();
|
||||
|
||||
app.UseOqtane(configuration, builder.Environment, corsService, corsPolicyProvider, syncManager);
|
||||
|
||||
var databaseManager = app.Services.GetService<IDatabaseManager>();
|
||||
var install = databaseManager.Install();
|
||||
if (!string.IsNullOrEmpty(install.Message))
|
||||
{
|
||||
var filelogger = host.Services.GetRequiredService<ILogger<Program>>();
|
||||
var filelogger = app.Services.GetRequiredService<ILogger<Program>>();
|
||||
if (filelogger != null)
|
||||
{
|
||||
filelogger.LogError($"[Oqtane.Application.Server.Program.Main] {install.Message}");
|
||||
filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
host.Run();
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseConfiguration(new ConfigurationBuilder()
|
||||
.AddCommandLine(args)
|
||||
.AddEnvironmentVariables()
|
||||
.Build())
|
||||
.UseStartup<Startup>()
|
||||
.ConfigureLocalizationSettings()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Repository.Databases.Interfaces;
|
||||
|
||||
namespace Oqtane.Application.Repository
|
||||
{
|
||||
public class Context : DBContextBase, ITransientService, IMultiDatabase
|
||||
{
|
||||
public virtual DbSet<Models.MyModule> MyModule { get; set; }
|
||||
|
||||
public Context(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies)
|
||||
{
|
||||
// ContextBase handles multi-tenant database connections
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.Entity<Models.MyModule>().ToTable(ActiveDatabase.RewriteName("MyModule"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Modules;
|
||||
|
||||
namespace Oqtane.Application.Repository
|
||||
{
|
||||
public interface IMyModuleRepository
|
||||
{
|
||||
IEnumerable<Models.MyModule> GetMyModules(int ModuleId);
|
||||
Models.MyModule GetMyModule(int MyModuleId);
|
||||
Models.MyModule GetMyModule(int MyModuleId, bool tracking);
|
||||
Models.MyModule AddMyModule(Models.MyModule MyModule);
|
||||
Models.MyModule UpdateMyModule(Models.MyModule MyModule);
|
||||
void DeleteMyModule(int MyModuleId);
|
||||
}
|
||||
|
||||
public class MyModuleRepository : IMyModuleRepository, ITransientService
|
||||
{
|
||||
private readonly IDbContextFactory<Context> _factory;
|
||||
|
||||
public MyModuleRepository(IDbContextFactory<Context> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public IEnumerable<Models.MyModule> GetMyModules(int ModuleId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
return db.MyModule.Where(item => item.ModuleId == ModuleId).ToList();
|
||||
}
|
||||
|
||||
public Models.MyModule GetMyModule(int MyModuleId)
|
||||
{
|
||||
return GetMyModule(MyModuleId, true);
|
||||
}
|
||||
|
||||
public Models.MyModule GetMyModule(int MyModuleId, bool tracking)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
if (tracking)
|
||||
{
|
||||
return db.MyModule.Find(MyModuleId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return db.MyModule.AsNoTracking().FirstOrDefault(item => item.MyModuleId == MyModuleId);
|
||||
}
|
||||
}
|
||||
|
||||
public Models.MyModule AddMyModule(Models.MyModule MyModule)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.MyModule.Add(MyModule);
|
||||
db.SaveChanges();
|
||||
return MyModule;
|
||||
}
|
||||
|
||||
public Models.MyModule UpdateMyModule(Models.MyModule MyModule)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.Entry(MyModule).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
return MyModule;
|
||||
}
|
||||
|
||||
public void DeleteMyModule(int MyModuleId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
Models.MyModule MyModule = db.MyModule.Find(MyModuleId);
|
||||
db.MyModule.Remove(MyModule);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
/* Module Script */
|
||||
var App = App || {};
|
||||
|
||||
App.MyModule = {
|
||||
};
|
||||
@ -1,10 +1,10 @@
|
||||
@using Oqtane.Modules.Controls
|
||||
@using Oqtane.Application.Services
|
||||
@using Oqtane.Application.Models
|
||||
@using [Owner].Module.[Module].Services
|
||||
@using [Owner].Module.[Module].Models
|
||||
|
||||
@namespace Oqtane.Application.MyModule
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@inject IMyModuleService MyModuleService
|
||||
@inject I[Module]Service [Module]Service
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
|
||||
@ -31,12 +31,7 @@
|
||||
|
||||
public override string Actions => "Add,Edit";
|
||||
|
||||
public override string Title => "Manage MyModule";
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
new Stylesheet(ModulePath() + "Module.css")
|
||||
};
|
||||
public override string Title => "Manage [Module]";
|
||||
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
@ -55,20 +50,20 @@
|
||||
if (PageState.Action == "Edit")
|
||||
{
|
||||
_id = Int32.Parse(PageState.QueryString["id"]);
|
||||
MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
|
||||
if (MyModule != null)
|
||||
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||
if ([Module] != null)
|
||||
{
|
||||
_name = MyModule.Name;
|
||||
_createdby = MyModule.CreatedBy;
|
||||
_createdon = MyModule.CreatedOn;
|
||||
_modifiedby = MyModule.ModifiedBy;
|
||||
_modifiedon = MyModule.ModifiedOn;
|
||||
_name = [Module].Name;
|
||||
_createdby = [Module].CreatedBy;
|
||||
_createdon = [Module].CreatedOn;
|
||||
_modifiedby = [Module].ModifiedBy;
|
||||
_modifiedon = [Module].ModifiedOn;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading MyModule {MyModuleId} {Error}", _id, ex.Message);
|
||||
await logger.LogError(ex, "Error Loading [Module] {[Module]Id} {Error}", _id, ex.Message);
|
||||
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
@ -83,18 +78,18 @@
|
||||
{
|
||||
if (PageState.Action == "Add")
|
||||
{
|
||||
MyModule MyModule = new MyModule();
|
||||
MyModule.ModuleId = ModuleState.ModuleId;
|
||||
MyModule.Name = _name;
|
||||
MyModule = await MyModuleService.AddMyModuleAsync(MyModule);
|
||||
await logger.LogInformation("MyModule Added {MyModule}", MyModule);
|
||||
[Module] [Module] = new [Module]();
|
||||
[Module].ModuleId = ModuleState.ModuleId;
|
||||
[Module].Name = _name;
|
||||
[Module] = await [Module]Service.Add[Module]Async([Module]);
|
||||
await logger.LogInformation("[Module] Added {[Module]}", [Module]);
|
||||
}
|
||||
else
|
||||
{
|
||||
MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
|
||||
MyModule.Name = _name;
|
||||
await MyModuleService.UpdateMyModuleAsync(MyModule);
|
||||
await logger.LogInformation("MyModule Updated {MyModule}", MyModule);
|
||||
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||
[Module].Name = _name;
|
||||
await [Module]Service.Update[Module]Async([Module]);
|
||||
await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
|
||||
}
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
@ -105,7 +100,7 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving MyModule {Error}", ex.Message);
|
||||
await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
@ -1,32 +1,32 @@
|
||||
@using Oqtane.Application.Services
|
||||
@using Oqtane.Application.Models
|
||||
@using [Owner].Module.[Module].Services
|
||||
@using [Owner].Module.[Module].Models
|
||||
|
||||
@namespace Oqtane.Application.MyModule
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@inject IMyModuleService MyModuleService
|
||||
@inject I[Module]Service [Module]Service
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
|
||||
@if (_MyModules == null)
|
||||
@if (_[Module]s == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add MyModule" ResourceKey="Add" />
|
||||
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add [Module]" ResourceKey="Add" />
|
||||
<br />
|
||||
<br />
|
||||
@if (@_MyModules.Count != 0)
|
||||
@if (@_[Module]s.Count != 0)
|
||||
{
|
||||
<Pager Items="@_MyModules">
|
||||
<Pager Items="@_[Module]s">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Name"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.MyModuleId.ToString())" ResourceKey="Edit" /></td>
|
||||
<td><ActionDialog Header="Delete MyModule" Message="Are You Sure You Wish To Delete This MyModule?" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" Id="@context.MyModuleId.ToString()" /></td>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.[Module]Id.ToString())" ResourceKey="Edit" /></td>
|
||||
<td><ActionDialog Header="Delete [Module]" Message="Are You Sure You Wish To Delete This [Module]?" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" Id="@context.[Module]Id.ToString()" /></td>
|
||||
<td>@context.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@ -38,39 +38,41 @@ else
|
||||
}
|
||||
|
||||
@code {
|
||||
public override string RenderMode => RenderModes.Static;
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
new Stylesheet(ModulePath() + "Module.css"),
|
||||
new Script(ModulePath() + "Module.js")
|
||||
};
|
||||
|
||||
List<Models.MyModule> _MyModules;
|
||||
List<[Module]> _[Module]s;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
|
||||
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading MyModule {Error}", ex.Message);
|
||||
await logger.LogError(ex, "Error Loading [Module] {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Delete(MyModule MyModule)
|
||||
private async Task Delete([Module] [Module])
|
||||
{
|
||||
try
|
||||
{
|
||||
await MyModuleService.DeleteMyModuleAsync(MyModule.MyModuleId, ModuleState.ModuleId);
|
||||
await logger.LogInformation("MyModule Deleted {MyModule}", MyModule);
|
||||
_MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
|
||||
await [Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
|
||||
await logger.LogInformation("[Module] Deleted {[Module]}", [Module]);
|
||||
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting MyModule {MyModule} {Error}", MyModule, ex.Message);
|
||||
await logger.LogError(ex, "Error Deleting [Module] {[Module]} {Error}", [Module], ex.Message);
|
||||
AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
using Microsoft.JSInterop;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Application
|
||||
namespace [Owner].Module.[Module]
|
||||
{
|
||||
public class Interop
|
||||
{
|
||||
@ -0,0 +1,16 @@
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules;
|
||||
|
||||
namespace [Owner].Module.[Module]
|
||||
{
|
||||
public class ModuleInfo : IModule
|
||||
{
|
||||
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||
{
|
||||
Name = "[Module]",
|
||||
Description = "[Description]",
|
||||
Version = "1.0.0",
|
||||
ServerManagerType = "[ServerManagerType]"
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
@namespace Oqtane.Application.MyModule
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Settings> Localizer
|
||||
@ -13,8 +13,8 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string resourceType = "Oqtane.Application.MyModule.Settings, Oqtane.Application.Client.Oqtane"; // for localization
|
||||
public override string Title => "MyModdule Settings";
|
||||
private string resourceType = "[Owner].Module.[Module].Settings, [Owner].Module.[Module].Client.Oqtane"; // for localization
|
||||
public override string Title => "[Module] Settings";
|
||||
|
||||
string _value;
|
||||
|
||||
@ -35,8 +35,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, string> settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||
SettingService.SetSetting(settings, "SettingName", _value);
|
||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||
settings = SettingService.SetSetting(settings, "SettingName", _value);
|
||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name.Text" xml:space="preserve">
|
||||
<value>Name: </value>
|
||||
</data>
|
||||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the name</value>
|
||||
</data>
|
||||
<data name="Save" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="Message.LoadError" xml:space="preserve">
|
||||
<value>Error Loading [Module]</value>
|
||||
</data>
|
||||
<data name="Message.SaveValidation" xml:space="preserve">
|
||||
<value>Please Provide All Required Information</value>
|
||||
</data>
|
||||
<data name="Message.SaveError" xml:space="preserve">
|
||||
<value>Error Saving [Module]</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="Add.Text" xml:space="preserve">
|
||||
<value>Add [Module]</value>
|
||||
</data>
|
||||
<data name="Edit.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="Delete.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="Delete.Header" xml:space="preserve">
|
||||
<value>Delete [Module]</value>
|
||||
</data>
|
||||
<data name="Delete.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete This [Module]?</value>
|
||||
</data>
|
||||
<data name="Message.DisplayNone" xml:space="preserve">
|
||||
<value>No [Module]s To Display</value>
|
||||
</data>
|
||||
<data name="Message.LoadError" xml:space="preserve">
|
||||
<value>Error Loading [Module]</value>
|
||||
</data>
|
||||
<data name="Message.DeleteError" xml:space="preserve">
|
||||
<value>Error Deleting [Module]</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace [Owner].Module.[Module].Services
|
||||
{
|
||||
public interface I[Module]Service
|
||||
{
|
||||
Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task Delete[Module]Async(int [Module]Id, int ModuleId);
|
||||
}
|
||||
|
||||
public class [Module]Service : ServiceBase, I[Module]Service
|
||||
{
|
||||
public [Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
private string Apiurl => CreateApiUrl("[Module]");
|
||||
|
||||
public async Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId)
|
||||
{
|
||||
List<Models.[Module]> [Module]s = await GetJsonAsync<List<Models.[Module]>>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty<Models.[Module]>().ToList());
|
||||
return [Module]s.OrderBy(item => item.Name).ToList();
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId)
|
||||
{
|
||||
return await GetJsonAsync<Models.[Module]>(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module])
|
||||
{
|
||||
return await PostJsonAsync<Models.[Module]>(CreateAuthorizationPolicyUrl($"{Apiurl}", EntityNames.Module, [Module].ModuleId), [Module]);
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module])
|
||||
{
|
||||
return await PutJsonAsync<Models.[Module]>(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module].[Module]Id}", EntityNames.Module, [Module].ModuleId), [Module]);
|
||||
}
|
||||
|
||||
public async Task Delete[Module]Async(int [Module]Id, int ModuleId)
|
||||
{
|
||||
await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,17 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Application.Services;
|
||||
using [Owner].Module.[Module].Services;
|
||||
|
||||
namespace Oqtane.Application.Startup
|
||||
namespace [Owner].Module.[Module].Startup
|
||||
{
|
||||
public class ClientStartup : IClientStartup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
if (!services.Any(s => s.ServiceType == typeof(IMyModuleService)))
|
||||
if (!services.Any(s => s.ServiceType == typeof(I[Module]Service)))
|
||||
{
|
||||
services.AddScoped<IMyModuleService, MyModuleService>();
|
||||
services.AddScoped<I[Module]Service, [Module]Service>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,36 +5,36 @@ using Microsoft.AspNetCore.Http;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Application.Services;
|
||||
using [Owner].Module.[Module].Services;
|
||||
using Oqtane.Controllers;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Application.Controllers
|
||||
namespace [Owner].Module.[Module].Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class MyModuleController : ModuleControllerBase
|
||||
public class [Module]Controller : ModuleControllerBase
|
||||
{
|
||||
private readonly IMyModuleService _MyModuleService;
|
||||
private readonly I[Module]Service _[Module]Service;
|
||||
|
||||
public MyModuleController(IMyModuleService MyModuleService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
|
||||
public [Module]Controller(I[Module]Service [Module]Service, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
|
||||
{
|
||||
_MyModuleService = MyModuleService;
|
||||
_[Module]Service = [Module]Service;
|
||||
}
|
||||
|
||||
// GET: api/<controller>?moduleid=x
|
||||
[HttpGet]
|
||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||
public async Task<IEnumerable<Models.MyModule>> Get(string moduleid)
|
||||
public async Task<IEnumerable<Models.[Module]>> Get(string moduleid)
|
||||
{
|
||||
int ModuleId;
|
||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||
{
|
||||
return await _MyModuleService.GetMyModulesAsync(ModuleId);
|
||||
return await _[Module]Service.Get[Module]sAsync(ModuleId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {ModuleId}", moduleid);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {ModuleId}", moduleid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
@ -43,16 +43,16 @@ namespace Oqtane.Application.Controllers
|
||||
// GET api/<controller>/5
|
||||
[HttpGet("{id}/{moduleid}")]
|
||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||
public async Task<Models.MyModule> Get(int id, int moduleid)
|
||||
public async Task<Models.[Module]> Get(int id, int moduleid)
|
||||
{
|
||||
Models.MyModule MyModule = await _MyModuleService.GetMyModuleAsync(id, moduleid);
|
||||
if (MyModule != null && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||
Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid);
|
||||
if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||
{
|
||||
return MyModule;
|
||||
return [Module];
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {MyModuleId} {ModuleId}", id, moduleid);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {[Module]Id} {ModuleId}", id, moduleid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
@ -61,37 +61,37 @@ namespace Oqtane.Application.Controllers
|
||||
// POST api/<controller>
|
||||
[HttpPost]
|
||||
[Authorize(Policy = PolicyNames.EditModule)]
|
||||
public async Task<Models.MyModule> Post([FromBody] Models.MyModule MyModule)
|
||||
public async Task<Models.[Module]> Post([FromBody] Models.[Module] [Module])
|
||||
{
|
||||
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||
{
|
||||
MyModule = await _MyModuleService.AddMyModuleAsync(MyModule);
|
||||
[Module] = await _[Module]Service.Add[Module]Async([Module]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Post Attempt {MyModule}", MyModule);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Post Attempt {[Module]}", [Module]);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
MyModule = null;
|
||||
[Module] = null;
|
||||
}
|
||||
return MyModule;
|
||||
return [Module];
|
||||
}
|
||||
|
||||
// PUT api/<controller>/5
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(Policy = PolicyNames.EditModule)]
|
||||
public async Task<Models.MyModule> Put(int id, [FromBody] Models.MyModule MyModule)
|
||||
public async Task<Models.[Module]> Put(int id, [FromBody] Models.[Module] [Module])
|
||||
{
|
||||
if (ModelState.IsValid && MyModule.MyModuleId == id && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||
if (ModelState.IsValid && [Module].[Module]Id == id && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||
{
|
||||
MyModule = await _MyModuleService.UpdateMyModuleAsync(MyModule);
|
||||
[Module] = await _[Module]Service.Update[Module]Async([Module]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Put Attempt {MyModule}", MyModule);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Put Attempt {[Module]}", [Module]);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
MyModule = null;
|
||||
[Module] = null;
|
||||
}
|
||||
return MyModule;
|
||||
return [Module];
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5
|
||||
@ -99,14 +99,14 @@ namespace Oqtane.Application.Controllers
|
||||
[Authorize(Policy = PolicyNames.EditModule)]
|
||||
public async Task Delete(int id, int moduleid)
|
||||
{
|
||||
Models.MyModule MyModule = await _MyModuleService.GetMyModuleAsync(id, moduleid);
|
||||
if (MyModule != null && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||
Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid);
|
||||
if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||
{
|
||||
await _MyModuleService.DeleteMyModuleAsync(id, MyModule.ModuleId);
|
||||
await _[Module]Service.Delete[Module]Async(id, [Module].ModuleId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized v Delete Attempt {MyModuleId} {ModuleId}", id, moduleid);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Delete Attempt {[Module]Id} {ModuleId}", id, moduleid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Interfaces;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Repository;
|
||||
using [Owner].Module.[Module].Repository;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace [Owner].Module.[Module].Manager
|
||||
{
|
||||
public class [Module]Manager : MigratableModuleBase, IInstallable, IPortable, ISearchable
|
||||
{
|
||||
private readonly I[Module]Repository _[Module]Repository;
|
||||
private readonly IDBContextDependencies _DBContextDependencies;
|
||||
|
||||
public [Module]Manager(I[Module]Repository [Module]Repository, IDBContextDependencies DBContextDependencies)
|
||||
{
|
||||
_[Module]Repository = [Module]Repository;
|
||||
_DBContextDependencies = DBContextDependencies;
|
||||
}
|
||||
|
||||
public bool Install(Tenant tenant, string version)
|
||||
{
|
||||
return Migrate(new [Module]Context(_DBContextDependencies), tenant, MigrationType.Up);
|
||||
}
|
||||
|
||||
public bool Uninstall(Tenant tenant)
|
||||
{
|
||||
return Migrate(new [Module]Context(_DBContextDependencies), tenant, MigrationType.Down);
|
||||
}
|
||||
|
||||
public string ExportModule(Oqtane.Models.Module module)
|
||||
{
|
||||
string content = "";
|
||||
List<Models.[Module]> [Module]s = _[Module]Repository.Get[Module]s(module.ModuleId).ToList();
|
||||
if ([Module]s != null)
|
||||
{
|
||||
content = JsonSerializer.Serialize([Module]s);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
public void ImportModule(Oqtane.Models.Module module, string content, string version)
|
||||
{
|
||||
List<Models.[Module]> [Module]s = null;
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
[Module]s = JsonSerializer.Deserialize<List<Models.[Module]>>(content);
|
||||
}
|
||||
if ([Module]s != null)
|
||||
{
|
||||
foreach(var [Module] in [Module]s)
|
||||
{
|
||||
_[Module]Repository.Add[Module](new Models.[Module] { ModuleId = module.ModuleId, Name = [Module].Name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
|
||||
{
|
||||
var searchContentList = new List<SearchContent>();
|
||||
|
||||
foreach (var [Module] in _[Module]Repository.Get[Module]s(pageModule.ModuleId))
|
||||
{
|
||||
if ([Module].ModifiedOn >= lastIndexedOn)
|
||||
{
|
||||
searchContentList.Add(new SearchContent
|
||||
{
|
||||
EntityName = "[Owner][Module]",
|
||||
EntityId = [Module].[Module]Id.ToString(),
|
||||
Title = [Module].Name,
|
||||
Body = [Module].Name,
|
||||
ContentModifiedBy = [Module].ModifiedBy,
|
||||
ContentModifiedOn = [Module].ModifiedOn
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(searchContentList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations;
|
||||
using [Owner].Module.[Module].Migrations.EntityBuilders;
|
||||
using [Owner].Module.[Module].Repository;
|
||||
|
||||
namespace [Owner].Module.[Module].Migrations
|
||||
{
|
||||
[DbContext(typeof([Module]Context))]
|
||||
[Migration("[Owner].Module.[Module].01.00.00.00")]
|
||||
public class [Module]Initialize : MultiDatabaseMigration
|
||||
{
|
||||
public [Module]Initialize(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var entityBuilder = new [Module]EntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
entityBuilder.Create();
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var entityBuilder = new [Module]EntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
entityBuilder.Drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,31 +5,31 @@ using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
|
||||
namespace Oqtane.Application.Migrations.EntityBuilders
|
||||
namespace [Owner].Module.[Module].Migrations.EntityBuilders
|
||||
{
|
||||
public class MyModuleEntityBuilder : AuditableBaseEntityBuilder<MyModuleEntityBuilder>
|
||||
public class [Module]EntityBuilder : AuditableBaseEntityBuilder<[Module]EntityBuilder>
|
||||
{
|
||||
private const string _entityTableName = "MyModule";
|
||||
private readonly PrimaryKey<MyModuleEntityBuilder> _primaryKey = new("PK_MyModule", x => x.MyModuleId);
|
||||
private readonly ForeignKey<MyModuleEntityBuilder> _moduleForeignKey = new("FK_MyModule_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade);
|
||||
private const string _entityTableName = "[Owner][Module]";
|
||||
private readonly PrimaryKey<[Module]EntityBuilder> _primaryKey = new("PK_[Owner][Module]", x => x.[Module]Id);
|
||||
private readonly ForeignKey<[Module]EntityBuilder> _moduleForeignKey = new("FK_[Owner][Module]_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade);
|
||||
|
||||
public MyModuleEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
public [Module]EntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
{
|
||||
EntityTableName = _entityTableName;
|
||||
PrimaryKey = _primaryKey;
|
||||
ForeignKeys.Add(_moduleForeignKey);
|
||||
}
|
||||
|
||||
protected override MyModuleEntityBuilder BuildTable(ColumnsBuilder table)
|
||||
protected override [Module]EntityBuilder BuildTable(ColumnsBuilder table)
|
||||
{
|
||||
MyModuleId = AddAutoIncrementColumn(table, "MyModuleId");
|
||||
[Module]Id = AddAutoIncrementColumn(table,"[Module]Id");
|
||||
ModuleId = AddIntegerColumn(table,"ModuleId");
|
||||
Name = AddMaxStringColumn(table,"Name");
|
||||
AddAuditableColumns(table);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OperationBuilder<AddColumnOperation> MyModuleId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> [Module]Id { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Name { get; set; }
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository.Databases.Interfaces;
|
||||
|
||||
namespace [Owner].Module.[Module].Repository
|
||||
{
|
||||
public class [Module]Context : DBContextBase, ITransientService, IMultiDatabase
|
||||
{
|
||||
public virtual DbSet<Models.[Module]> [Module] { get; set; }
|
||||
|
||||
public [Module]Context(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies)
|
||||
{
|
||||
// ContextBase handles multi-tenant database connections
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.Entity<Models.[Module]>().ToTable(ActiveDatabase.RewriteName("[Owner][Module]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Modules;
|
||||
|
||||
namespace [Owner].Module.[Module].Repository
|
||||
{
|
||||
public interface I[Module]Repository
|
||||
{
|
||||
IEnumerable<Models.[Module]> Get[Module]s(int ModuleId);
|
||||
Models.[Module] Get[Module](int [Module]Id);
|
||||
Models.[Module] Get[Module](int [Module]Id, bool tracking);
|
||||
Models.[Module] Add[Module](Models.[Module] [Module]);
|
||||
Models.[Module] Update[Module](Models.[Module] [Module]);
|
||||
void Delete[Module](int [Module]Id);
|
||||
}
|
||||
|
||||
public class [Module]Repository : I[Module]Repository, ITransientService
|
||||
{
|
||||
private readonly IDbContextFactory<[Module]Context> _factory;
|
||||
|
||||
public [Module]Repository(IDbContextFactory<[Module]Context> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public IEnumerable<Models.[Module]> Get[Module]s(int ModuleId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
return db.[Module].Where(item => item.ModuleId == ModuleId).ToList();
|
||||
}
|
||||
|
||||
public Models.[Module] Get[Module](int [Module]Id)
|
||||
{
|
||||
return Get[Module]([Module]Id, true);
|
||||
}
|
||||
|
||||
public Models.[Module] Get[Module](int [Module]Id, bool tracking)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
if (tracking)
|
||||
{
|
||||
return db.[Module].Find([Module]Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
return db.[Module].AsNoTracking().FirstOrDefault(item => item.[Module]Id == [Module]Id);
|
||||
}
|
||||
}
|
||||
|
||||
public Models.[Module] Add[Module](Models.[Module] [Module])
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.[Module].Add([Module]);
|
||||
db.SaveChanges();
|
||||
return [Module];
|
||||
}
|
||||
|
||||
public Models.[Module] Update[Module](Models.[Module] [Module])
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.Entry([Module]).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
return [Module];
|
||||
}
|
||||
|
||||
public void Delete[Module](int [Module]Id)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
Models.[Module] [Module] = db.[Module].Find([Module]Id);
|
||||
db.[Module].Remove([Module]);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,93 +7,93 @@ using Oqtane.Infrastructure;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Application.Repository;
|
||||
using [Owner].Module.[Module].Repository;
|
||||
|
||||
namespace Oqtane.Application.Services
|
||||
namespace [Owner].Module.[Module].Services
|
||||
{
|
||||
public class ServerMyModuleService : IMyModuleService
|
||||
public class Server[Module]Service : I[Module]Service
|
||||
{
|
||||
private readonly IMyModuleRepository _MyModuleRepository;
|
||||
private readonly I[Module]Repository _[Module]Repository;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly IHttpContextAccessor _accessor;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public ServerMyModuleService(IMyModuleRepository MyModuleRepository, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor)
|
||||
public Server[Module]Service(I[Module]Repository [Module]Repository, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor)
|
||||
{
|
||||
_MyModuleRepository = MyModuleRepository;
|
||||
_[Module]Repository = [Module]Repository;
|
||||
_userPermissions = userPermissions;
|
||||
_logger = logger;
|
||||
_accessor = accessor;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
|
||||
public Task<List<Models.MyModule>> GetMyModulesAsync(int ModuleId)
|
||||
public Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View))
|
||||
{
|
||||
return Task.FromResult(_MyModuleRepository.GetMyModules(ModuleId).ToList());
|
||||
return Task.FromResult(_[Module]Repository.Get[Module]s(ModuleId).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {ModuleId}", ModuleId);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {ModuleId}", ModuleId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Models.MyModule> GetMyModuleAsync(int MyModuleId, int ModuleId)
|
||||
public Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View))
|
||||
{
|
||||
return Task.FromResult(_MyModuleRepository.GetMyModule(MyModuleId));
|
||||
return Task.FromResult(_[Module]Repository.Get[Module]([Module]Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {TaskId} {ModuleId}", MyModuleId, ModuleId);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {[Module]Id} {ModuleId}", [Module]Id, ModuleId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Models.MyModule> AddMyModuleAsync(Models.MyModule MyModule)
|
||||
public Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module])
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, MyModule.ModuleId, PermissionNames.Edit))
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, [Module].ModuleId, PermissionNames.Edit))
|
||||
{
|
||||
MyModule = _MyModuleRepository.AddMyModule(MyModule);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "MyModule Added {MyModule}", MyModule);
|
||||
[Module] = _[Module]Repository.Add[Module]([Module]);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "[Module] Added {[Module]}", [Module]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Add Attempt {MyModule}", MyModule);
|
||||
MyModule = null;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Add Attempt {[Module]}", [Module]);
|
||||
[Module] = null;
|
||||
}
|
||||
return Task.FromResult(MyModule);
|
||||
return Task.FromResult([Module]);
|
||||
}
|
||||
|
||||
public Task<Models.MyModule> UpdateMyModuleAsync(Models.MyModule MyModule)
|
||||
public Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module])
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, MyModule.ModuleId, PermissionNames.Edit))
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, [Module].ModuleId, PermissionNames.Edit))
|
||||
{
|
||||
MyModule = _MyModuleRepository.UpdateMyModule(MyModule);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "MyModule Updated {MyModule}", MyModule);
|
||||
[Module] = _[Module]Repository.Update[Module]([Module]);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "[Module] Updated {[Module]}", [Module]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Update Attempt {MyModule}", MyModule);
|
||||
MyModule = null;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Update Attempt {[Module]}", [Module]);
|
||||
[Module] = null;
|
||||
}
|
||||
return Task.FromResult(MyModule);
|
||||
return Task.FromResult([Module]);
|
||||
}
|
||||
|
||||
public Task DeleteMyModuleAsync(int MyModuleId, int ModuleId)
|
||||
public Task Delete[Module]Async(int [Module]Id, int ModuleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
||||
{
|
||||
_MyModuleRepository.DeleteMyModule(MyModuleId);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "MyModule Deleted {MyModuleId}", MyModuleId);
|
||||
_[Module]Repository.Delete[Module]([Module]Id);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "[Module] Deleted {[Module]Id}", [Module]Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Delete Attempt {MyModuleId} {ModuleId}", MyModuleId, ModuleId);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Delete Attempt {[Module]Id} {ModuleId}", [Module]Id, ModuleId);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -2,12 +2,12 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Application.Repository;
|
||||
using Oqtane.Application.Services;
|
||||
using [Owner].Module.[Module].Repository;
|
||||
using [Owner].Module.[Module].Services;
|
||||
|
||||
namespace Oqtane.Application.Startup
|
||||
namespace [Owner].Module.[Module].Startup
|
||||
{
|
||||
public class ServerStartup : IServerStartup
|
||||
public class [Module]ServerStartup : IServerStartup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
@ -21,8 +21,8 @@ namespace Oqtane.Application.Startup
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<IMyModuleService, ServerMyModuleService>();
|
||||
services.AddDbContextFactory<Context>(opt => { }, ServiceLifetime.Transient);
|
||||
services.AddTransient<I[Module]Service, Server[Module]Service>();
|
||||
services.AddDbContextFactory<[Module]Context>(opt => { }, ServiceLifetime.Transient);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
/* Module Script */
|
||||
var [Owner] = [Owner] || {};
|
||||
|
||||
[Owner].[Module] = {
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace [Owner].Module.[Module].Models
|
||||
{
|
||||
[Table("[Owner][Module]")]
|
||||
public class [Module] : ModelBase
|
||||
{
|
||||
[Key]
|
||||
public int [Module]Id { get; set; }
|
||||
public int ModuleId { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"Title": "Default Module Template",
|
||||
"Type": "Internal",
|
||||
"Version": "10.0.0",
|
||||
"Namespace": "[Owner].Module.[Module]"
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 1.3
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">1.3</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1">this is my long string</data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
[base64 mime encoded serialized .NET Framework object]
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 1.3
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">1.3</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1">this is my long string</data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
[base64 mime encoded serialized .NET Framework object]
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -1,4 +1,4 @@
|
||||
@namespace Oqtane.Application.MyTheme
|
||||
@namespace [Owner].Theme.[Theme]
|
||||
@inherits ContainerBase
|
||||
@inject ISettingService SettingService
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
public override string Name => "Container";
|
||||
public override string Name => "[Owner] [Theme] - Container1";
|
||||
|
||||
private bool _title = true;
|
||||
private string _classes = "container-fluid";
|
||||
@ -1,4 +1,4 @@
|
||||
@namespace Oqtane.Application.MyTheme
|
||||
@namespace [Owner].Theme.[Theme]
|
||||
@inherits ModuleBase
|
||||
@implements Oqtane.Interfaces.ISettingsControl
|
||||
@inject ISettingService SettingService
|
||||
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string resourceType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane"; // for localization
|
||||
private string resourceType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane"; // for localization
|
||||
private string _title = "true";
|
||||
|
||||
protected override void OnInitialized()
|
||||
@ -1,4 +1,4 @@
|
||||
@namespace Oqtane.Application.MyTheme
|
||||
@namespace [Owner].Theme.[Theme]
|
||||
@inherits ThemeBase
|
||||
@inject ISettingService SettingService
|
||||
|
||||
@ -95,7 +95,7 @@
|
||||
</main>
|
||||
|
||||
@code {
|
||||
public override string Name => "MyTheme";
|
||||
public override string Name => "Theme1";
|
||||
|
||||
public override string Panes => PaneNames.Admin + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width";
|
||||
|
||||
@ -3,23 +3,25 @@ using Oqtane.Models;
|
||||
using Oqtane.Themes;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Application.MyTheme
|
||||
namespace [Owner].Theme.[Theme]
|
||||
{
|
||||
public class ThemeInfo : ITheme
|
||||
{
|
||||
public Oqtane.Models.Theme Theme => new Oqtane.Models.Theme
|
||||
{
|
||||
Name = "MyTheme",
|
||||
Name = "[Owner] [Theme]",
|
||||
Version = "1.0.0",
|
||||
PackageName = "Oqtane.Application",
|
||||
ThemeSettingsType = "Oqtane.Application.MyTheme.ThemeSettings, Oqtane.Application.Client.Oqtane",
|
||||
ContainerSettingsType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane",
|
||||
PackageName = "[Owner].Theme.[Theme]",
|
||||
ThemeSettingsType = "[Owner].Theme.[Theme].ThemeSettings, [Owner].Theme.[Theme].Client.Oqtane",
|
||||
ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane",
|
||||
Resources = new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
|
||||
new Stylesheet("~/Theme.css"),
|
||||
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
@namespace Oqtane.Application.MyTheme
|
||||
@namespace [Owner].Theme.[Theme]
|
||||
@inherits ModuleBase
|
||||
@implements Oqtane.Interfaces.ISettingsControl
|
||||
@inject ISettingService SettingService
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
@code {
|
||||
private int pageId = -1;
|
||||
private string resourceType = "Oqtane.Application.MyTheme.ThemeSettings, Oqtane.Application.Client.Oqtane"; // for localization
|
||||
private string resourceType = "[Owner].Theme.[Theme].ThemeSettings, [Owner].Theme.[Theme].Client.Oqtane"; // for localization
|
||||
private string _scope = "page";
|
||||
private string _login = "-";
|
||||
private string _register = "-";
|
||||
@ -1,4 +1,4 @@
|
||||
/* Oqtane Styles */
|
||||
/* Oqtane Styles */
|
||||
|
||||
body {
|
||||
padding-top: 7rem;
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"Title": "Default Theme Template",
|
||||
"Type": "Internal",
|
||||
"Version": "10.0.0",
|
||||
"Namespace": "[Owner].Theme.[Theme]"
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Application.Models
|
||||
{
|
||||
public class MyModule : IAuditable
|
||||
{
|
||||
[Key]
|
||||
public int MyModuleId { get; set; }
|
||||
public int ModuleId { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public string CreatedBy { get; set; }
|
||||
public DateTime CreatedOn { get; set; }
|
||||
public string ModifiedBy { get; set; }
|
||||
public DateTime ModifiedOn { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
|
||||
</PropertyGroup>
|
||||
@ -11,7 +11,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Shared" Version="6.2.1" />
|
||||
<PackageReference Include="Oqtane.Shared" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -20,39 +20,50 @@ else
|
||||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<hr class="app-rule mt-3 mb-2" />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<div class="form-group text-center">
|
||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<div class="form-group text-center mt-2">
|
||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
@if (!_alwaysremember)
|
||||
{
|
||||
<div class="form-check">
|
||||
@if (!_alwaysremember)
|
||||
{
|
||||
<div class="form-group text-center mt-2">
|
||||
<div>
|
||||
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="btn-group mt-2 col-12" role="group">
|
||||
<button type="button" class="btn btn-primary col-6" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary col-6" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
|
||||
@if (_allowpasskeys)
|
||||
{
|
||||
<br /><br />
|
||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||
<hr class="app-rule mt-3" />
|
||||
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="Passkey">@Localizer["Passkey"]</button>
|
||||
}
|
||||
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
<hr class="app-rule mt-3" />
|
||||
<div class="text-center mt-2">
|
||||
<NavLink href="@_registerurl">@Localizer["Register"]</NavLink>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@ -77,6 +88,7 @@ else
|
||||
@code {
|
||||
private bool _allowsitelogin = true;
|
||||
private bool _allowexternallogin = false;
|
||||
private bool _allowpasskeys = false;
|
||||
private ElementReference login;
|
||||
private bool validated = false;
|
||||
private bool twofactor = false;
|
||||
@ -88,6 +100,7 @@ else
|
||||
private bool _remember = false;
|
||||
private bool _alwaysremember = false;
|
||||
private string _code = string.Empty;
|
||||
private string _registerurl = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||
public override bool? Prerender => true;
|
||||
@ -103,8 +116,18 @@ else
|
||||
{
|
||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||
_allowpasskeys = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false"));
|
||||
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
|
||||
|
||||
if (!string.IsNullOrEmpty(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "")))
|
||||
{
|
||||
_registerurl = SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
_registerurl = NavigateUrl("register");
|
||||
}
|
||||
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
|
||||
if (PageState.QueryString.ContainsKey("name"))
|
||||
@ -120,7 +143,7 @@ else
|
||||
|
||||
if (PageState.QueryString.ContainsKey("key"))
|
||||
{
|
||||
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
||||
user = await UserService.AddLoginAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
||||
if (user != null)
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
||||
@ -163,23 +186,6 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender && PageState.User == null && _allowsitelogin)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||
{
|
||||
await username.FocusAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// redirect logged in user to specified page
|
||||
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Login()
|
||||
{
|
||||
try
|
||||
@ -331,4 +337,58 @@ else
|
||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
|
||||
}
|
||||
|
||||
private async Task Passkey()
|
||||
{
|
||||
// post back to the Passkey page so that the cookies are set correctly
|
||||
var interop = new Interop(JSRuntime);
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = NavigateUrl() };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender && PageState.QueryString.ContainsKey("options"))
|
||||
{
|
||||
// user has initiated a passkey login
|
||||
try
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
var credential = await interop.RequestCredential(WebUtility.UrlDecode(PageState.QueryString["options"]));
|
||||
if (!string.IsNullOrEmpty(credential))
|
||||
{
|
||||
// post back to the Passkey page so that the cookies are set correctly
|
||||
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path + "/";
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = returnurl };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
else
|
||||
{
|
||||
await logger.LogError("Passkey Login Was Not Successful");
|
||||
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Passkey Login Was Not Successful");
|
||||
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstRender && PageState.User == null && _allowsitelogin)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||
{
|
||||
await username.FocusAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// redirect logged in user to specified page
|
||||
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,29 +42,32 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="reference" class="form-select" @bind="@_reference" required>
|
||||
@foreach (string version in _versions)
|
||||
{
|
||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
||||
{
|
||||
<option value="@(version)">@(version)</option>
|
||||
}
|
||||
}
|
||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_location))
|
||||
@if (_type == "External")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
|
||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||
<select id="reference" class="form-select" @bind="@_reference" required>
|
||||
@foreach (string version in _versions)
|
||||
{
|
||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
||||
{
|
||||
<option value="@(version)">@(version)</option>
|
||||
}
|
||||
}
|
||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_location))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
|
||||
@ -80,9 +83,10 @@
|
||||
private string _description = string.Empty;
|
||||
private List<Template> _templates;
|
||||
private string _template = "-";
|
||||
private string _minversion = "2.0.0";
|
||||
private string _type = "";
|
||||
private string[] _versions;
|
||||
private string _reference = "local";
|
||||
private string _minversion = "2.0.0";
|
||||
private string _location = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
@ -123,11 +127,18 @@
|
||||
if (string.IsNullOrEmpty(_description)) _description = _module;
|
||||
if (IsValidXML(_description))
|
||||
{
|
||||
if (_type == "Internal")
|
||||
{
|
||||
AddModuleMessage(Localizer["Success.Module.Create.Internal"], MessageType.Success);
|
||||
}
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
|
||||
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
||||
GetLocation();
|
||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
if (_type == "External")
|
||||
{
|
||||
GetLocation();
|
||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -153,7 +164,7 @@
|
||||
private bool IsValid(string name)
|
||||
{
|
||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$");
|
||||
}
|
||||
|
||||
private bool IsValidXML(string description)
|
||||
@ -165,11 +176,16 @@
|
||||
private void TemplateChanged(ChangeEventArgs e)
|
||||
{
|
||||
_template = (string)e.Value;
|
||||
_minversion = "2.0.0";
|
||||
if (_template != "-")
|
||||
{
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
_minversion = template.Version;
|
||||
_type = template.Type;
|
||||
}
|
||||
else
|
||||
{
|
||||
_minversion = "2.0.0";
|
||||
_type = "";
|
||||
}
|
||||
GetLocation();
|
||||
}
|
||||
|
||||
@ -36,30 +36,33 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="reference" class="form-select" @bind="@_reference">
|
||||
@foreach (string version in _versions)
|
||||
{
|
||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
||||
{
|
||||
<option value="@(version)">@(version)</option>
|
||||
}
|
||||
}
|
||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_location))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_type == "External")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="reference" class="form-select" @bind="@_reference">
|
||||
@foreach (string version in _versions)
|
||||
{
|
||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
||||
{
|
||||
<option value="@(version)">@(version)</option>
|
||||
}
|
||||
}
|
||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_location))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
|
||||
@ -71,9 +74,10 @@
|
||||
private string _theme = string.Empty;
|
||||
private List<Template> _templates;
|
||||
private string _template = "-";
|
||||
private string _minversion = "2.0.0";
|
||||
private string _type = "";
|
||||
private string[] _versions;
|
||||
private string _reference = "local";
|
||||
private string _minversion = "2.0.0";
|
||||
private string _location = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
@ -85,8 +89,8 @@
|
||||
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -105,11 +109,18 @@
|
||||
{
|
||||
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
|
||||
{
|
||||
if (_type == "Internal")
|
||||
{
|
||||
AddModuleMessage(Localizer["Success.Theme.Create.Internal"], MessageType.Success);
|
||||
}
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
|
||||
theme = await ThemeService.CreateThemeAsync(theme);
|
||||
GetLocation();
|
||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
if (_type == "External")
|
||||
{
|
||||
GetLocation();
|
||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -125,17 +136,22 @@
|
||||
private bool IsValid(string name)
|
||||
{
|
||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$");
|
||||
}
|
||||
|
||||
private void TemplateChanged(ChangeEventArgs e)
|
||||
{
|
||||
_template = (string)e.Value;
|
||||
_minversion = "2.0.0";
|
||||
if (_template != "-")
|
||||
{
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
_minversion = template.Version;
|
||||
_type = template.Type;
|
||||
}
|
||||
else
|
||||
{
|
||||
_minversion = "2.0.0";
|
||||
_type = "";
|
||||
}
|
||||
GetLocation();
|
||||
}
|
||||
|
||||
@ -26,8 +26,7 @@
|
||||
<br />
|
||||
}
|
||||
<TabStrip>
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
||||
@ -35,36 +34,6 @@
|
||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (_allowtwofactor)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor" 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="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||
<div class="col-sm-9">
|
||||
@ -99,9 +68,113 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
<TabPanel Name="Security" Heading="Security" ResourceKey="Security">
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br /><br />
|
||||
@if (_allowtwofactor)
|
||||
{
|
||||
<Section Name="MFA" Heading="Multi-Factor Authentication" ResourceKey="MFA">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<br />
|
||||
}
|
||||
@if (_allowpasskeys)
|
||||
{
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys">
|
||||
<button type="button" class="btn btn-primary" @onclick="AddPasskey">@SharedLocalizer["Add"]</button>
|
||||
@if (_passkeys != null && _passkeys.Count > 0)
|
||||
{
|
||||
<Pager Items="@_passkeys">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Passkey"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
@if (context.CredentialId != _passkeyId)
|
||||
{
|
||||
<td><button type="button" class="btn btn-primary" @onclick="@(() => EditPasskey(context))">@SharedLocalizer["Edit"]</button></td>
|
||||
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeletePasskey(context))" ResourceKey="DeletePasskey" Class="btn btn-danger" Header="Delete Passkey" Message="@string.Format(Localizer["Confirm.Passkey.Delete", context.Name])" /></td>
|
||||
<td>@context.Name</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SavePasskey())">@SharedLocalizer["Save"]</button></td>
|
||||
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelPasskey())">@SharedLocalizer["Cancel"]</button></td>
|
||||
<td><input id="passkeyname" class="form-control" @bind="@_passkeyName" /></td>
|
||||
}
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Passkeys.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
}
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins">
|
||||
@if (_logins != null && _logins.Count > 0)
|
||||
{
|
||||
<Pager Items="@_logins">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Login"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeleteLogin(context))" ResourceKey="DeleteLogin" Class="btn btn-danger" Header="Delete Login" Message="@string.Format(Localizer["Confirm.Login.Delete", context.Name])" /></td>
|
||||
<td>@context.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Logins.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
}
|
||||
<br />
|
||||
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
||||
<br />
|
||||
</TabPanel>
|
||||
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@foreach (Profile profile in _profiles)
|
||||
@ -230,11 +303,11 @@
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Notifications" ResourceKey="Notifications">
|
||||
<TabPanel Name="Notifications" Heading="Notifications" ResourceKey="Notifications">
|
||||
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
|
||||
<br />
|
||||
<br />
|
||||
<select class="form-select" @onchange="(e => FilterChanged(e))">
|
||||
<select class="form-select" @onchange="(e => FilterNotifications(e))">
|
||||
<option value="to">@Localizer["Inbox"]</option>
|
||||
<option value="from">@Localizer["Items.Sent"]</option>
|
||||
</select>
|
||||
@ -253,7 +326,7 @@
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await DeleteNotification(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||
|
||||
@if (context.IsRead)
|
||||
{
|
||||
@ -298,7 +371,7 @@
|
||||
else
|
||||
{
|
||||
<div class="no-notifications-text">
|
||||
@Localizer["NoNotificationsReceived.Text"]
|
||||
@Localizer["NoNotificationsReceived"]
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@ -316,7 +389,7 @@
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await DeleteNotification(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||
|
||||
@if (context.IsRead)
|
||||
{
|
||||
@ -362,7 +435,7 @@
|
||||
else
|
||||
{
|
||||
<div class="no-notifications-text">
|
||||
@Localizer["NoNotificationsSent.Text"]
|
||||
@Localizer["NoNotificationsSent"]
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@ -373,15 +446,14 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private string _username = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string _confirm = string.Empty;
|
||||
private bool _allowtwofactor = false;
|
||||
private string _twofactor = "False";
|
||||
private bool _allowpasskeys = false;
|
||||
private bool _allowexternallogin = false;
|
||||
|
||||
private string _username = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private FileManager _filemanager;
|
||||
@ -392,6 +464,17 @@
|
||||
private File _photo = null;
|
||||
private string _imagefiles = string.Empty;
|
||||
|
||||
private string _passwordrequirements;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string _confirm = string.Empty;
|
||||
private string _twofactor = "False";
|
||||
private List<UserPasskey> _passkeys;
|
||||
private byte[] _passkeyId;
|
||||
private string _passkeyName = string.Empty;
|
||||
private List<UserLogin> _logins;
|
||||
|
||||
private List<Profile> _profiles;
|
||||
private Dictionary<string, string> _userSettings;
|
||||
private string _category = string.Empty;
|
||||
@ -400,42 +483,29 @@
|
||||
private List<Notification> _notifications;
|
||||
private string _notificationSummary = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_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);
|
||||
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||
}
|
||||
}
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
|
||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||
|
||||
if (PageState.User != null)
|
||||
{
|
||||
// identity section
|
||||
_username = PageState.User.Username;
|
||||
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
||||
_email = PageState.User.Email;
|
||||
_displayname = PageState.User.DisplayName;
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
|
||||
// get user folder
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||
if (folder != null)
|
||||
{
|
||||
_folderid = folder.FolderId;
|
||||
}
|
||||
|
||||
_imagefiles = SettingService.GetSetting(PageState.Site.Settings, "ImageFiles", Constants.ImageFiles);
|
||||
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||
if (PageState.User.PhotoFileId != null)
|
||||
{
|
||||
_photofileid = PageState.User.PhotoFileId.Value;
|
||||
@ -447,11 +517,27 @@
|
||||
_photo = null;
|
||||
}
|
||||
|
||||
_userSettings = PageState.User.Settings;
|
||||
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles);
|
||||
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||
// security section
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
||||
await GetPasskeys();
|
||||
await GetLogins();
|
||||
|
||||
// profile section
|
||||
_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);
|
||||
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||
}
|
||||
}
|
||||
_userSettings = PageState.User.Settings;
|
||||
|
||||
// notification section
|
||||
await LoadNotificationsAsync();
|
||||
|
||||
_initialized = true;
|
||||
@ -468,22 +554,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadNotificationsAsync()
|
||||
{
|
||||
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
|
||||
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
||||
}
|
||||
|
||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||
{
|
||||
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
|
||||
if (value.Contains("]"))
|
||||
{
|
||||
value = value.Substring(value.IndexOf("]") + 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// identity methods
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
@ -558,6 +629,124 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
|
||||
// security methods
|
||||
|
||||
private void TogglePassword()
|
||||
{
|
||||
if (_passwordtype == "password")
|
||||
{
|
||||
_passwordtype = "text";
|
||||
_togglepassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordtype = "password";
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetPasskeys()
|
||||
{
|
||||
if (_allowpasskeys)
|
||||
{
|
||||
_passkeys = await UserService.GetPasskeysAsync(PageState.User.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddPasskey()
|
||||
{
|
||||
// post back to the Passkey page so that the cookies are set correctly
|
||||
var interop = new Interop(JSRuntime);
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "create", returnurl = NavigateUrl(PageState.Page.Path, "tab=Security") };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// user has initiated a passkey addition
|
||||
if (PageState.QueryString.ContainsKey("options"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
var credential = await interop.CreateCredential(WebUtility.UrlDecode(PageState.QueryString["options"]));
|
||||
if (!string.IsNullOrEmpty(credential))
|
||||
{
|
||||
// post back to the Passkey page so that the cookies are set correctly
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "validate", credential = credential, returnurl = NavigateUrl(PageState.Page.Path, "tab=Security") };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
else
|
||||
{
|
||||
await logger.LogError("Passkey Could Not Be Created");
|
||||
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Passkey Could Not Be Created");
|
||||
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EditPasskey(UserPasskey passkey)
|
||||
{
|
||||
_passkeyId = passkey.CredentialId;
|
||||
_passkeyName = passkey.Name;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeletePasskey(UserPasskey passkey)
|
||||
{
|
||||
await UserService.DeletePasskeyAsync(PageState.User.UserId, passkey.CredentialId);
|
||||
await GetPasskeys();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task SavePasskey()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_passkeyName))
|
||||
{
|
||||
await UserService.UpdatePasskeyAsync(new UserPasskey { CredentialId = _passkeyId, Name = _passkeyName, UserId = PageState.User.UserId });
|
||||
await GetPasskeys();
|
||||
_passkeyName = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelPasskey()
|
||||
{
|
||||
await GetPasskeys();
|
||||
_passkeyName = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task GetLogins()
|
||||
{
|
||||
if (_allowexternallogin)
|
||||
{
|
||||
_logins = await UserService.GetLoginsAsync(PageState.User.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteLogin(UserLogin login)
|
||||
{
|
||||
await UserService.DeleteLoginAsync(PageState.User.UserId, login.Provider, login.Key);
|
||||
await GetLogins();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task Logout()
|
||||
{
|
||||
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
|
||||
@ -584,6 +773,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
// profile methods
|
||||
|
||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||
{
|
||||
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
|
||||
if (value.Contains("]"))
|
||||
{
|
||||
value = value.Substring(value.IndexOf("]") + 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||
{
|
||||
var value = (string)e.Value;
|
||||
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
|
||||
}
|
||||
|
||||
private bool ValidateProfiles()
|
||||
{
|
||||
foreach (Profile profile in _profiles)
|
||||
@ -615,18 +822,22 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
// notification methods
|
||||
|
||||
private async Task LoadNotificationsAsync()
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
|
||||
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
||||
}
|
||||
|
||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||
private async void FilterNotifications(ChangeEventArgs e)
|
||||
{
|
||||
var value = (string)e.Value;
|
||||
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
|
||||
_filter = (string)e.Value;
|
||||
await LoadNotificationsAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task Delete(Notification Notification)
|
||||
private async Task DeleteNotification(Notification Notification)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -651,13 +862,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async void FilterChanged(ChangeEventArgs e)
|
||||
{
|
||||
_filter = (string)e.Value;
|
||||
await LoadNotificationsAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeleteAllNotifications()
|
||||
{
|
||||
try
|
||||
@ -689,18 +893,4 @@
|
||||
HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
{
|
||||
if (_passwordtype == "password")
|
||||
{
|
||||
_passwordtype = "text";
|
||||
_togglepassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordtype = "password";
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,8 +14,7 @@
|
||||
@if (_initialized)
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
|
||||
@ -23,24 +22,6 @@
|
||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -100,7 +81,77 @@
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
<TabPanel Name="Security" Heading="Security" ResourceKey="Security">
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
@if (_allowpasskeys)
|
||||
{
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys">
|
||||
@if (_passkeys != null && _passkeys.Count > 0)
|
||||
{
|
||||
<Pager Items="@_passkeys">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Passkey"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeletePasskey(context))" ResourceKey="DeletePasskey" Class="btn btn-danger" Header="Delete Passkey" Message="@string.Format(Localizer["Confirm.Passkey.Delete", context.Name])" /></td>
|
||||
<td>@context.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Passkeys.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
}
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins">
|
||||
@if (_logins != null && _logins.Count > 0)
|
||||
{
|
||||
<Pager Items="@_logins">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Login"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeleteLogin(context))" ResourceKey="DeleteLogin" Class="btn btn-danger" Header="Delete Login" Message="@string.Format(Localizer["Confirm.Login.Delete", context.Name])" /></td>
|
||||
<td>@context.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Logins.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@foreach (Profile profile in _profiles)
|
||||
@ -169,24 +220,30 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private bool _allowpasskeys = false;
|
||||
private bool _allowexternallogin = false;
|
||||
|
||||
private int _userid;
|
||||
private string _username = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string _confirm = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _confirmed = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _isdeleted;
|
||||
private string _lastlogin;
|
||||
private string _lastipaddress;
|
||||
private bool _ishost = false;
|
||||
|
||||
private string _passwordrequirements;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string _confirm = string.Empty;
|
||||
private List<UserPasskey> _passkeys;
|
||||
private List<UserLogin> _logins;
|
||||
|
||||
private List<Profile> _profiles;
|
||||
private Dictionary<string, string> _settings;
|
||||
private string _category = string.Empty;
|
||||
@ -204,19 +261,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_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);
|
||||
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||
}
|
||||
}
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
|
||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||
|
||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||
{
|
||||
@ -228,13 +274,30 @@
|
||||
_email = user.Email;
|
||||
_confirmed = user.EmailConfirmed.ToString();
|
||||
_displayname = user.DisplayName;
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
_isdeleted = user.IsDeleted.ToString();
|
||||
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||
_lastipaddress = user.LastIPAddress;
|
||||
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||
|
||||
_settings = user.Settings;
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
await GetPasskeys();
|
||||
await GetLogins();
|
||||
|
||||
_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);
|
||||
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||
}
|
||||
}
|
||||
_settings = user.Settings;
|
||||
|
||||
_createdby = user.CreatedBy;
|
||||
_createdon = user.CreatedOn;
|
||||
_modifiedby = user.ModifiedBy;
|
||||
@ -354,6 +417,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetPasskeys()
|
||||
{
|
||||
if (_allowpasskeys)
|
||||
{
|
||||
_passkeys = await UserService.GetPasskeysAsync(_userid);
|
||||
}
|
||||
}
|
||||
private async Task DeletePasskey(UserPasskey passkey)
|
||||
{
|
||||
await UserService.DeletePasskeyAsync(_userid, passkey.CredentialId);
|
||||
await GetPasskeys();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task GetLogins()
|
||||
{
|
||||
if (_allowexternallogin)
|
||||
{
|
||||
_logins = await UserService.GetLoginsAsync(_userid);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteLogin(UserLogin login)
|
||||
{
|
||||
await UserService.DeleteLoginAsync(_userid, login.Provider, login.Key);
|
||||
await GetLogins();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private bool ValidateProfiles()
|
||||
{
|
||||
foreach (Profile profile in _profiles)
|
||||
|
||||
@ -72,32 +72,41 @@ else
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_allowregistration == "true")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="registerurl" HelpText="Optionally provide a custom registration url" ResourceKey="RegisterUrl">Register Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="registerurl" class="form-control" @bind="@_registerurl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireconfirmedemail" HelpText="Do you want to require registered users to verify their email address before they are allowed to log in?" ResourceKey="RequireConfirmedEmail">Require Verified Email?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireconfirmedemail" class="form-select" @bind="@_requireconfirmedemail">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
@if (_allowregistration == "true")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="registerurl" HelpText="Optionally provide a custom registration url" ResourceKey="RegisterUrl">Register Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="registerurl" class="form-control" @bind="@_registerurl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireconfirmedemail" HelpText="Do you want to require registered users to verify their email address before they are allowed to log in?" ResourceKey="RequireConfirmedEmail">Require Verified Email?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireconfirmedemail" class="form-select" @bind="@_requireconfirmedemail">
|
||||
<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="passkeys" HelpText="Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)" ResourceKey="Passkeys">Allow Passkeys?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="passkeys" class="form-select" @bind="@_passkeys">
|
||||
<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="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor Authentication?</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -538,6 +547,7 @@ else
|
||||
private string _registerurl;
|
||||
private string _profileurl;
|
||||
private string _requireconfirmedemail;
|
||||
private string _passkeys;
|
||||
private string _twofactor;
|
||||
private string _cookiename;
|
||||
private string _cookiedomain;
|
||||
@ -609,12 +619,13 @@ else
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||
_passkeys = SettingService.GetSetting(settings, "LoginOptions:Passkeys", "false");
|
||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
|
||||
@ -753,6 +764,7 @@ else
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:Passkeys", _passkeys, 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);
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
|
||||
@if (_style == MessageStyle.Toast)
|
||||
{
|
||||
<div class="app-modulemessage-toast bottom-0 end-0">
|
||||
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||
<div class="app-modulemessage-toast bottom-0 end-0" @key="DateTime.UtcNow">
|
||||
<div class="@_classname alert-dismissible fade show mb-3 rounded-end-0" role="alert">
|
||||
@((MarkupString)Message)
|
||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
|
||||
@ -64,7 +64,7 @@
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div @ref="@_editorElement"></div>
|
||||
<div @ref="@_editorElement" class="app-editor-resizable"></div>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@using Radzen
|
||||
@using Radzen.Blazor
|
||||
@using System.Reflection
|
||||
|
||||
@namespace Oqtane.Modules.Controls
|
||||
@inherits ModuleControlBase
|
||||
@ -17,7 +18,7 @@
|
||||
<RadzenTheme Theme="@RadzenEditorDefinitions.DefaultTheme" />
|
||||
<RadzenComponents />
|
||||
<RadzenHtmlEditor @ref="_editor" Visible="_visible" Placeholder="@Placeholder" style="@($"height: {Height}px;")"
|
||||
@bind-Value="_value" Execute="OnExecute" class="rz-text-editor">
|
||||
@bind-Value="_value" Execute="OnExecute" class="rz-text-editor app-editor-resizable">
|
||||
<ChildContent>
|
||||
@_toolbar
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
@ -93,6 +94,17 @@
|
||||
}
|
||||
await _interop.SetBackgroundColor(_editor.Element, backgroundColor);
|
||||
}
|
||||
|
||||
var subscribers = GetEventSubscribers(DialogService, "OnOpen");
|
||||
var dialogSubscibers = subscribers?.Where(s => s.Method.DeclaringType == typeof(RadzenDialog)) ?? Enumerable.Empty<Delegate>();
|
||||
if (dialogSubscibers.Count() > 1)
|
||||
{
|
||||
//clean the event to avoid multiple RadzenDialog instances subscribing to the event
|
||||
dialogSubscibers.Skip(1).ToList().ForEach(s =>
|
||||
{
|
||||
DialogService.OnOpen -= s as Action<string, Type, Dictionary<string, object>, DialogOptions>;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,4 +224,23 @@
|
||||
{
|
||||
await _interop.UpdateDialogLayout(_editor.Element);
|
||||
}
|
||||
|
||||
private Delegate[] GetEventSubscribers(object target, string eventName)
|
||||
{
|
||||
var type = target.GetType();
|
||||
var eventField = type.GetField(eventName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (eventField == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var eventDelegate = eventField.GetValue(target) as Delegate;
|
||||
if (eventDelegate == null)
|
||||
{
|
||||
return new Delegate[0];
|
||||
}
|
||||
|
||||
return eventDelegate.GetInvocationList();
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,7 @@ namespace Oqtane.Modules
|
||||
private Dictionary<string, string> _urlparameters;
|
||||
private bool _scriptsloaded = false;
|
||||
|
||||
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
||||
public Logger logger => _logger ?? (_logger = new Logger(this));
|
||||
|
||||
[Inject]
|
||||
protected ILogService LoggingService { get; set; }
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
</PropertyGroup>
|
||||
|
||||
<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="Radzen.Blazor" Version="7.4.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Radzen.Blazor" Version="8.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ForgotPassword" xml:space="preserve">
|
||||
<value>Forgot Password</value>
|
||||
<value>Forgot Password?</value>
|
||||
</data>
|
||||
<data name="Success.Account.Verified" xml:space="preserve">
|
||||
<value>User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password.</value>
|
||||
@ -127,7 +127,7 @@
|
||||
<value>User Account Email Address Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
|
||||
</data>
|
||||
<data name="Success.Account.Linked" xml:space="preserve">
|
||||
<value>User Account Linked Successfully. You Can Now Login With Your External Login Below.</value>
|
||||
<value>External Login Linked Successfully. You Can Now Login.</value>
|
||||
</data>
|
||||
<data name="Message.Account.NotLinked" xml:space="preserve">
|
||||
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
||||
@ -231,4 +231,10 @@
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value>Register as new user?</value>
|
||||
</data>
|
||||
<data name="Passkey" xml:space="preserve">
|
||||
<value>Use Passkey</value>
|
||||
</data>
|
||||
<data name="Error.Passkey.Fail" xml:space="preserve">
|
||||
<value>Passkey Login Was Not Successful</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -136,7 +136,7 @@
|
||||
<value>You Must Provide A Valid Description (ie. No Punctuation)</value>
|
||||
</data>
|
||||
<data name="OwnerName.HelpText" xml:space="preserve">
|
||||
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation or contain the word "oqtane".</value>
|
||||
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation or contain the word "oqtane". If you are using an Internal template then make sure the owner matches the name of the project.</value>
|
||||
</data>
|
||||
<data name="ModuleName.HelpText" xml:space="preserve">
|
||||
<value>Enter a name for this module. It should not contain spaces or punctuation or contain the word "oqtane".</value>
|
||||
@ -168,7 +168,10 @@
|
||||
<data name="Location.Text" xml:space="preserve">
|
||||
<value>Location: </value>
|
||||
</data>
|
||||
<data name="Success.Module.Create" xml:space="preserve">
|
||||
<data name="Success.Module.Create.Internal" xml:space="preserve">
|
||||
<value>The Source Code For Your Module Has Been Created In Your Solution And Must Be Compiled In Order To Make It Functional</value>
|
||||
</data>
|
||||
<data name="Success.Module.Create.External" xml:space="preserve">
|
||||
<value>The Source Code For Your Module Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must <a href={0}>Restart</a> Your Application To Activate The Module.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@ -141,14 +141,17 @@
|
||||
<data name="Info.Theme.CreatorIntent" xml:space="preserve">
|
||||
<value>Please Note That The Theme Creator Is Only Intended To Be Used In A Development Environment</value>
|
||||
</data>
|
||||
<data name="Success.Theme.Create" xml:space="preserve">
|
||||
<value>The Source Code For Your Theme Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must <a href={0}>Restart</a> Your Application To Activate The Module.</value>
|
||||
<data name="Success.Theme.Create.Internal" xml:space="preserve">
|
||||
<value>The Source Code For Your Theme Has Been Created In Your Solution And Must Be Compiled In Order To Make It Functional</value>
|
||||
</data>
|
||||
<data name="Success.Theme.Create.External" xml:space="preserve">
|
||||
<value>The Source Code For Your Theme Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must <a href={0}>Restart</a> Your Application To Activate The Theme.</value>
|
||||
</data>
|
||||
<data name="Message.Required.ValidName" xml:space="preserve">
|
||||
<value>You Must Provide A Valid Owner Name And Theme Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value>
|
||||
</data>
|
||||
<data name="OwnerName.HelpText" xml:space="preserve">
|
||||
<value>Enter the name of the organization who is developing this theme. It should not contain spaces or punctuation.</value>
|
||||
<value>Enter the name of the organization who is developing this theme. It should not contain spaces or punctuation or contain the word "oqtane". If you are using an Internal template then make sure the owner matches the name of the project.</value>
|
||||
</data>
|
||||
<data name="ThemeName.HelpText" xml:space="preserve">
|
||||
<value>Enter a name for this theme. It should not contain spaces or punctuation.</value>
|
||||
|
||||
@ -168,9 +168,6 @@
|
||||
<data name="DeleteNotification.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete This Notification?</value>
|
||||
</data>
|
||||
<data name="Identity.Name" xml:space="preserve">
|
||||
<value>Identity</value>
|
||||
</data>
|
||||
<data name="Confirm.HelpText" xml:space="preserve">
|
||||
<value>If you are changing your password you must enter it again to confirm it matches the value entered above</value>
|
||||
</data>
|
||||
@ -211,7 +208,7 @@
|
||||
<value>Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in.</value>
|
||||
</data>
|
||||
<data name="TwoFactor.Text" xml:space="preserve">
|
||||
<value>Two Factor?</value>
|
||||
<value>Use Two Factor?</value>
|
||||
</data>
|
||||
<data name="DeleteAllNotifications.Header" xml:space="preserve">
|
||||
<value>Clear Notifications</value>
|
||||
@ -234,11 +231,11 @@
|
||||
<data name="DeleteNotification.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="NoNotificationsReceived.Text" xml:space="preserve">
|
||||
<value>No notifications have been received</value>
|
||||
<data name="NoNotificationsReceived" xml:space="preserve">
|
||||
<value>You Have Not Received Any Notifications</value>
|
||||
</data>
|
||||
<data name="NoNotificationsSent.Text" xml:space="preserve">
|
||||
<value>No notifications have been sent</value>
|
||||
<data name="NoNotificationsSent" xml:space="preserve">
|
||||
<value>You Have Not Sent Any Notifications</value>
|
||||
</data>
|
||||
<data name="Logout Everywhere" xml:space="preserve">
|
||||
<value>Logout Everywhere</value>
|
||||
@ -249,4 +246,46 @@
|
||||
<data name="TimeZone.HelpText" xml:space="preserve">
|
||||
<value>Your time zone</value>
|
||||
</data>
|
||||
<data name="Identity.Heading" xml:space="preserve">
|
||||
<value>Identity</value>
|
||||
</data>
|
||||
<data name="Security.Heading" xml:space="preserve">
|
||||
<value>Security</value>
|
||||
</data>
|
||||
<data name="MFA.Heading" xml:space="preserve">
|
||||
<value>Multi-Factor Authenticationxxx</value>
|
||||
</data>
|
||||
<data name="Passkeys.Heading" xml:space="preserve">
|
||||
<value>Passkeys</value>
|
||||
</data>
|
||||
<data name="Logins.Heading" xml:space="preserve">
|
||||
<value>External Logins</value>
|
||||
</data>
|
||||
<data name="Passkey" xml:space="preserve">
|
||||
<value>Passkey</value>
|
||||
</data>
|
||||
<data name="DeletePasskey.Header" xml:space="preserve">
|
||||
<value>Delete Passkey</value>
|
||||
</data>
|
||||
<data name="Confirm.Passkey.Delete" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||
</data>
|
||||
<data name="Login" xml:space="preserve">
|
||||
<value>Login</value>
|
||||
</data>
|
||||
<data name="DeleteLogin.Header" xml:space="preserve">
|
||||
<value>Delete Login</value>
|
||||
</data>
|
||||
<data name="Confirm.Login.Delete" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||
</data>
|
||||
<data name="Message.Passkeys.None" xml:space="preserve">
|
||||
<value>You Have Not Created Any Passkeys</value>
|
||||
</data>
|
||||
<data name="Message.Logins.None" xml:space="preserve">
|
||||
<value>You Do Not Have Any External Logins For This Site</value>
|
||||
</data>
|
||||
<data name="Error.Passkey.Fail" xml:space="preserve">
|
||||
<value>Passkey Could Not Be Created</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -123,9 +123,6 @@
|
||||
<data name="Message.Password.Complexity" xml:space="preserve">
|
||||
<value>Password Provided Does Not Meet The Complexity Policy</value>
|
||||
</data>
|
||||
<data name="Identity.Name" xml:space="preserve">
|
||||
<value>Identity</value>
|
||||
</data>
|
||||
<data name="Confirm.Delete" xml:space="preserve">
|
||||
<value>Is Deleted?</value>
|
||||
</data>
|
||||
@ -222,4 +219,37 @@
|
||||
<data name="Confirmed.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the user's email is verified</value>
|
||||
</data>
|
||||
<data name="Security.Heading" xml:space="preserve">
|
||||
<value>Security</value>
|
||||
</data>
|
||||
<data name="Passkeys.Heading" xml:space="preserve">
|
||||
<value>Passkeys</value>
|
||||
</data>
|
||||
<data name="Logins.Heading" xml:space="preserve">
|
||||
<value>External Logins</value>
|
||||
</data>
|
||||
<data name="Passkey" xml:space="preserve">
|
||||
<value>Passkey</value>
|
||||
</data>
|
||||
<data name="Login" xml:space="preserve">
|
||||
<value>Login</value>
|
||||
</data>
|
||||
<data name="DeletePasskey.Header" xml:space="preserve">
|
||||
<value>Delete Passkey</value>
|
||||
</data>
|
||||
<data name="DeleteLogin.Header" xml:space="preserve">
|
||||
<value>Delete Login</value>
|
||||
</data>
|
||||
<data name="Confirm.Passkey.Delete" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||
</data>
|
||||
<data name="Confirm.Login.Delete" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||
</data>
|
||||
<data name="Message.Passkeys.None" xml:space="preserve">
|
||||
<value>You Have Not Created Any Passkeys</value>
|
||||
</data>
|
||||
<data name="Message.Logins.None" xml:space="preserve">
|
||||
<value>You Do Not Have Any External Logins For This Site</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -561,4 +561,10 @@
|
||||
<data name="SingleLogout.HelpText" xml:space="preserve">
|
||||
<value>Specify if users should be logged out of both the application and provider (the default is false indicating they will only be logged out of the application)</value>
|
||||
</data>
|
||||
<data name="Passkeys.Text" xml:space="preserve">
|
||||
<value>Allow Passkeys?</value>
|
||||
</data>
|
||||
<data name="Passkeys.HelpText" xml:space="preserve">
|
||||
<value>Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)?</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -3,12 +3,14 @@ using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
@ -206,6 +208,17 @@ namespace Oqtane.Services
|
||||
await CheckResponse(response, uri);
|
||||
}
|
||||
|
||||
protected async Task<string> PostStringAsync(string uri)
|
||||
{
|
||||
var response = await GetHttpClient().PostAsync(uri, null);
|
||||
if (await CheckResponse(response, uri) && ValidateJsonContent(response.Content))
|
||||
{
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
return result;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
protected async Task<T> PostJsonAsync<T>(string uri, T value)
|
||||
{
|
||||
return await PostJsonAsync<T, T>(uri, value);
|
||||
|
||||
@ -434,11 +434,26 @@ namespace Oqtane.Services
|
||||
foreach (KeyValuePair<string, string> kvp in settings)
|
||||
{
|
||||
var setting = new Setting();
|
||||
setting.SettingId = 0;
|
||||
setting.EntityName = entityName;
|
||||
setting.EntityId = entityId;
|
||||
setting.SettingName = kvp.Key;
|
||||
setting.SettingValue = kvp.Value;
|
||||
setting.IsPrivate = true;
|
||||
|
||||
// manage settings modified with SetSetting method
|
||||
if (setting.SettingValue.StartsWith("[Private]"))
|
||||
{
|
||||
setting.SettingValue = setting.SettingValue.Substring(9);
|
||||
setting.IsPrivate = true;
|
||||
setting.SettingId = -1; // indicates IsPrivate was explicitly set
|
||||
}
|
||||
if (setting.SettingValue.StartsWith("[Public]"))
|
||||
{
|
||||
setting.SettingValue = setting.SettingValue.Substring(8);
|
||||
setting.IsPrivate = false;
|
||||
setting.SettingId = -1; // indicates IsPrivate was explicitly set
|
||||
}
|
||||
|
||||
settingsList.Add(setting);
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Models;
|
||||
using System.Buffers.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Documentation;
|
||||
using System.Net;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
@ -146,17 +147,6 @@ namespace Oqtane.Services
|
||||
/// <returns></returns>
|
||||
Task<string> GetPersonalAccessTokenAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Link an external login with a local user account
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="User"/> we're verifying</param>
|
||||
/// <param name="token">A Hash value in the URL which verifies this user got the e-mail (containing this token)</param>
|
||||
/// <param name="type">External Login provider type</param>
|
||||
/// <param name="key">External Login provider key</param>
|
||||
/// <param name="name">External Login provider display name</param>
|
||||
/// <returns></returns>
|
||||
Task<User> LinkUserAsync(User user, string token, string type, string key, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Get password requirements for site
|
||||
/// </summary>
|
||||
@ -172,6 +162,55 @@ namespace Oqtane.Services
|
||||
/// <param name="notify">Indicates if new users should be notified by email</param>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<string, string>> ImportUsersAsync(int siteId, int fileId, bool notify);
|
||||
|
||||
/// <summary>
|
||||
/// Get passkeys for a user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<UserPasskey>> GetPasskeysAsync(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Update a user passkey
|
||||
/// </summary>
|
||||
/// <param name="passkey"></param>
|
||||
/// <returns></returns>
|
||||
Task<UserPasskey> UpdatePasskeyAsync(UserPasskey passkey);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a user passkey
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="credentialId"></param>
|
||||
/// <returns></returns>
|
||||
Task DeletePasskeyAsync(int userId, byte[] credentialId);
|
||||
|
||||
/// <summary>
|
||||
/// Get logins for a user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<UserLogin>> GetLoginsAsync(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Link an external login with a local user account
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="User"/> we're verifying</param>
|
||||
/// <param name="token">A Hash value in the URL which verifies this user got the e-mail (containing this token)</param>
|
||||
/// <param name="type">External Login provider type</param>
|
||||
/// <param name="key">External Login provider key</param>
|
||||
/// <param name="name">External Login provider display name</param>
|
||||
/// <returns></returns>
|
||||
Task<User> AddLoginAsync(User user, string token, string type, string key, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a user login
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteLoginAsync(int userId, string provider, string key);
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
@ -218,7 +257,7 @@ namespace Oqtane.Services
|
||||
|
||||
public async Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent)
|
||||
{
|
||||
return await PostJsonAsync<User>($"{Apiurl}/login?setcookie={setCookie}&persistent={isPersistent}", user);
|
||||
return await PostJsonAsync<User>($"{Apiurl}/signin?setcookie={setCookie}&persistent={isPersistent}", user);
|
||||
}
|
||||
|
||||
public async Task LogoutUserAsync(User user)
|
||||
@ -271,11 +310,6 @@ namespace Oqtane.Services
|
||||
return await GetStringAsync($"{Apiurl}/personalaccesstoken");
|
||||
}
|
||||
|
||||
public async Task<User> LinkUserAsync(User user, string token, string type, string key, string name)
|
||||
{
|
||||
return await PostJsonAsync<User>($"{Apiurl}/link?token={token}&type={type}&key={key}&name={name}", user);
|
||||
}
|
||||
|
||||
public async Task<string> GetPasswordRequirementsAsync(int siteId)
|
||||
{
|
||||
var requirements = await GetJsonAsync<Dictionary<string, string>>($"{Apiurl}/passwordrequirements/{siteId}");
|
||||
@ -302,5 +336,35 @@ namespace Oqtane.Services
|
||||
{
|
||||
return await PostJsonAsync<Dictionary<string, string>>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}¬ify={notify}", null);
|
||||
}
|
||||
|
||||
public async Task<List<UserPasskey>> GetPasskeysAsync(int userId)
|
||||
{
|
||||
return await GetJsonAsync<List<UserPasskey>>($"{Apiurl}/passkey?id={userId}");
|
||||
}
|
||||
|
||||
public async Task<UserPasskey> UpdatePasskeyAsync(UserPasskey passkey)
|
||||
{
|
||||
return await PutJsonAsync<UserPasskey>($"{Apiurl}/passkey", passkey);
|
||||
}
|
||||
|
||||
public async Task DeletePasskeyAsync(int userId, byte[] credentialId)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/passkey?id={userId}&credential={Base64Url.EncodeToString(credentialId)}");
|
||||
}
|
||||
|
||||
public async Task<List<UserLogin>> GetLoginsAsync(int userId)
|
||||
{
|
||||
return await GetJsonAsync<List<UserLogin>>($"{Apiurl}/login?id={userId}");
|
||||
}
|
||||
|
||||
public async Task<User> AddLoginAsync(User user, string token, string type, string key, string name)
|
||||
{
|
||||
return await PostJsonAsync<User>($"{Apiurl}/login?token={token}&type={type}&key={key}&name={name}", user);
|
||||
}
|
||||
|
||||
public async Task DeleteLoginAsync(int userId, string provider, string key)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/login?id={userId}&provider={provider}&key={key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,31 +3,29 @@
|
||||
@inject IStringLocalizer<Login> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<span class="app-login">
|
||||
@if (PageState.User != null)
|
||||
@if (PageState.User != null)
|
||||
{
|
||||
@if (PageState.Runtime == Runtime.Hybrid)
|
||||
{
|
||||
@if (PageState.Runtime == Runtime.Hybrid)
|
||||
{
|
||||
<button type="button" class="@CssClass" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="returnurl" value="@returnurl" />
|
||||
<input type="hidden" name="everywhere" value="@everywhere" />
|
||||
<button type="submit" class="@CssClass">@Localizer["Logout"]</button>
|
||||
</form>
|
||||
}
|
||||
<button type="button" class="@CssClass app-login" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ShowLogin)
|
||||
{
|
||||
<a href="@loginurl" class="@CssClass">@SharedLocalizer["Login"]</a>
|
||||
}
|
||||
<form method="post" class="app-form-inline app-login" action="@logouturl" @formname="LogoutForm">
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="returnurl" value="@returnurl" />
|
||||
<input type="hidden" name="everywhere" value="@everywhere" />
|
||||
<button type="submit" class="@CssClass">@Localizer["Logout"]</button>
|
||||
</form>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ShowLogin)
|
||||
{
|
||||
<a href="@loginurl" class="@CssClass app-login">@SharedLocalizer["Login"]</a>
|
||||
}
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@ -62,7 +63,14 @@ namespace Oqtane.Themes.Controls
|
||||
// verify anonymous users can access current page
|
||||
if (UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList) && Utilities.IsEffectiveAndNotExpired(PageState.Page.EffectiveDate, PageState.Page.ExpiryDate))
|
||||
{
|
||||
returnurl = PageState.Route.PathAndQuery;
|
||||
if (PageState.Route.Action != Constants.DefaultAction && PageState.Modules.Any() && PageState.Modules.First().SecurityAccessLevel > SecurityAccessLevel.View)
|
||||
{
|
||||
returnurl = PageState.Route.PagePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
returnurl = PageState.Route.PathAndQuery;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -5,19 +5,17 @@
|
||||
@inject IStringLocalizer<UserProfile> Localizer
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<span class="app-profile">
|
||||
@if (PageState.User != null)
|
||||
@if (PageState.User != null)
|
||||
{
|
||||
<a href="@_profileurl" class="@CssClass app-profile">@PageState.User.Username</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ShowRegister && PageState.Site.AllowRegistration)
|
||||
{
|
||||
<a href="@_profileurl" class="@CssClass">@PageState.User.Username</a>
|
||||
<a href="@_registerurl" class="@CssClass app-profile">@Localizer["Register"]</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ShowRegister && PageState.Site.AllowRegistration)
|
||||
{
|
||||
<a href="@_registerurl" class="@CssClass">@Localizer["Register"]</a>
|
||||
}
|
||||
}
|
||||
</span>
|
||||
}
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ namespace Oqtane.Themes.OqtaneTheme
|
||||
Resources = new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries/bootswatch
|
||||
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.7/cyborg/bootstrap.min.css", "sha512-/LQFzDeXqysGQ/POl5YOEjgVZH1BmqDHvshhnFIChf50bMGQ470qhUrsecD9MRCUwzwqRoshwAbmA2oTW4I6Yg==", "anonymous"),
|
||||
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.8/cyborg/bootstrap.min.css", "sha512-Sq+1MhDgkXwshbeZBKh8j5N7bjn56Jg40kyGm27FoBYEBPksAG+GcRwLEHT/UL4F/WdYUCl65IAQiGTANnBzLg==", "anonymous"),
|
||||
new Stylesheet("~/Theme.css"),
|
||||
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||
}
|
||||
|
||||
@ -417,5 +417,30 @@ namespace Oqtane.UI
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<string> CreateCredential(string optionsResponse)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _jsRuntime.InvokeAsync<string>("Oqtane.Interop.createCredential", optionsResponse);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ValueTask<string>(Task.FromResult(string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<string> RequestCredential(string optionsResponse)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _jsRuntime.InvokeAsync<string>("Oqtane.Interop.requestCredential", optionsResponse);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ValueTask<string>(Task.FromResult(string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@
|
||||
|
||||
public void AddModuleMessage(string message, MessageType type, string position, MessageStyle style)
|
||||
{
|
||||
if (message != _messageContent || type != _messageType || position != _messagePosition || style != _messageStyle)
|
||||
if (message != _messageContent || type != _messageType || position != _messagePosition || style != _messageStyle || style == MessageStyle.Toast)
|
||||
{
|
||||
_messageContent = message;
|
||||
_messageType = type;
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31611.283
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Maui", "Oqtane.Maui\Oqtane.Maui.csproj", "{5EE64148-2152-4908-A3E7-658EB1D87754}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
3
Oqtane.Maui.slnx
Normal file
3
Oqtane.Maui.slnx
Normal file
@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="Oqtane.Maui\Oqtane.Maui.csproj" DefaultStartup="true" />
|
||||
</Solution>
|
||||
@ -1,12 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build android, ios, and maccatalyst -->
|
||||
<!-- <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@ -19,32 +18,31 @@
|
||||
<ApplicationId>com.oqtane.maui</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>6.2.1</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>10.0.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||
|
||||
<!-- Images -->
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
@ -55,24 +53,24 @@
|
||||
<None Remove="Platforms\Android\Resources\xml\network_security_config.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.110" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.110" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.110" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.0" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Oqtane.Client">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net9.0\Oqtane.Client.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Oqtane.Shared">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net9.0\Oqtane.Shared.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Oqtane.Client">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net10.0\Oqtane.Client.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Oqtane.Shared">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Binary file not shown.
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Client</id>
|
||||
<version>6.2.1</version>
|
||||
<version>10.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,24 +12,24 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="net9.0">
|
||||
<dependency id="Oqtane.Shared" version="6.2.1" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<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.4.3" exclude="Build,Analyzers" />
|
||||
<group targetFramework="net10.0">
|
||||
<dependency id="Oqtane.Shared" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.Localization" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.Http" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Radzen.Blazor" version="8.3.0" exclude="Build,Analyzers" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Client\bin\Release\net9.0\Oqtane.Client.dll" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Client\bin\Release\net9.0\Oqtane.Client.pdb" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Client\bin\Release\net10.0\Oqtane.Client.dll" target="lib\net10.0" />
|
||||
<file src="..\Oqtane.Client\bin\Release\net10.0\Oqtane.Client.pdb" target="lib\net10.0" />
|
||||
<file src="icon.png" target="" />
|
||||
<file src="readme.md" target="" />
|
||||
</files>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Framework</id>
|
||||
<version>6.2.1</version>
|
||||
<version>10.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -11,8 +11,8 @@
|
||||
<copyright>.NET Foundation</copyright>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.2.1/Oqtane.Framework.6.2.1.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1</releaseNotes>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v10.0.0/Oqtane.Framework.10.0.0.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane framework</tags>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Server</id>
|
||||
<version>6.2.1</version>
|
||||
<version>10.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,49 +12,45 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="net9.0">
|
||||
<dependency id="Oqtane.Client" version="6.2.1" exclude="Build,Analyzers" />
|
||||
<dependency id="Oqtane.Shared" version="6.2.1" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Server" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Identity.EntityFrameworkCore" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Data.SqlClient" version="6.1.1" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<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.3" exclude="Build,Analyzers" />
|
||||
<dependency id="Swashbuckle.AspNetCore" version="9.0.5" exclude="Build,Analyzers" />
|
||||
<dependency id="MailKit" version="4.14.0" exclude="Build,Analyzers" />
|
||||
<dependency id="MySql.Data" version="9.4.0" exclude="Build,Analyzers" />
|
||||
<group targetFramework="net10.0">
|
||||
<dependency id="Oqtane.Client" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Oqtane.Shared" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Server" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Identity.EntityFrameworkCore" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Authentication.OpenIdConnect" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="SixLabors.ImageSharp" version="3.1.12" exclude="Build,Analyzers" />
|
||||
<dependency id="HtmlAgilityPack" version="1.12.4" exclude="Build,Analyzers" />
|
||||
<dependency id="Swashbuckle.AspNetCore" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="MailKit" version="4.14.1" exclude="Build,Analyzers" />
|
||||
<dependency id="MySql.Data" version="9.5.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Pomelo.EntityFrameworkCore.MySql" version="9.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="EFCore.NamingConventions" version="9.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Npgsql.EntityFrameworkCore.PostgreSQL" version="9.0.4" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.Sqlite" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Data.Sqlite.Core" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="SQLitePCLRaw.bundle_e_sqlite3" version="3.0.2" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.SqlServer" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="EFCore.NamingConventions" version="10.0.0-rc.2" exclude="Build,Analyzers" />
|
||||
<dependency id="Npgsql.EntityFrameworkCore.PostgreSQL" version="10.0.0-rc.2" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.Sqlite" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Data.Sqlite.Core" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.SqlServer" version="10.0.0" exclude="Build,Analyzers" />
|
||||
</group>
|
||||
</dependencies>
|
||||
<frameworkReferences>
|
||||
<group targetFramework="net9.0">
|
||||
<group targetFramework="net10.0">
|
||||
<frameworkReference name="Microsoft.AspNetCore.App" />
|
||||
</group>
|
||||
</frameworkReferences>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Server\bin\Release\net9.0\Oqtane.Server.dll" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net9.0\Oqtane.Server.pdb" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net9.0\staticwebassets\msbuild.Oqtane.Server.Microsoft.AspNetCore.StaticWebAssetEndpoints.props" target="build\Microsoft.AspNetCore.StaticWebAssetEndpoints.props" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net9.0\staticwebassets\msbuild.Oqtane.Server.Microsoft.AspNetCore.StaticWebAssets.props" target="build\Microsoft.AspNetCore.StaticWebAssets.props" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net9.0\staticwebassets\msbuild.build.Oqtane.Server.props" target="build\Oqtane.Server.props" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net9.0\staticwebassets\msbuild.buildMultiTargeting.Oqtane.Server.props" target="buildMultiTargeting\Oqtane.Server.props" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net9.0\staticwebassets\msbuild.buildTransitive.Oqtane.Server.props" target="buildTransitive\Oqtane.Server.props" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net10.0\Oqtane.Server.dll" target="lib\net10.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net10.0\Oqtane.Server.pdb" target="lib\net10.0" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net10.0\staticwebassets\msbuild.Oqtane.Server.Microsoft.AspNetCore.StaticWebAssetEndpoints.props" target="build\Microsoft.AspNetCore.StaticWebAssetEndpoints.props" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net10.0\staticwebassets\msbuild.Oqtane.Server.Microsoft.AspNetCore.StaticWebAssets.props" target="build\Microsoft.AspNetCore.StaticWebAssets.props" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net10.0\staticwebassets\msbuild.build.Oqtane.Server.props" target="build\Oqtane.Server.props" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net10.0\staticwebassets\msbuild.buildMultiTargeting.Oqtane.Server.props" target="buildMultiTargeting\Oqtane.Server.props" />
|
||||
<file src="..\Oqtane.Server\obj\Release\net10.0\staticwebassets\msbuild.buildTransitive.Oqtane.Server.props" target="buildTransitive\Oqtane.Server.props" />
|
||||
<file src="..\Oqtane.Server\wwwroot\**\*" exclude="..\Oqtane.Server\wwwroot\Modules\Templates\**;..\Oqtane.Server\wwwroot\Themes\Templates\**" target="staticwebassets" />
|
||||
<file src="icon.png" target="" />
|
||||
<file src="readme.md" target="" />
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Shared</id>
|
||||
<version>6.2.1</version>
|
||||
<version>10.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,24 +12,21 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="net9.0">
|
||||
<dependency id="Microsoft.EntityFrameworkCore" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="9.0.9" exclude="Build,Analyzers" />
|
||||
<group targetFramework="net10.0">
|
||||
<dependency id="Microsoft.EntityFrameworkCore" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="NodaTime" version="3.2.2" exclude="Build,Analyzers" />
|
||||
<dependency id="System.ComponentModel.Annotations" version="5.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="System.Text.Json" version="9.0.9" exclude="Build,Analyzers" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Shared\bin\Release\net9.0\Oqtane.Shared.dll" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Shared\bin\Release\net9.0\Oqtane.Shared.pdb" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Shared\bin\Release\net10.0\Oqtane.Shared.dll" target="lib\net10.0" />
|
||||
<file src="..\Oqtane.Shared\bin\Release\net10.0\Oqtane.Shared.pdb" target="lib\net10.0" />
|
||||
<file src="icon.png" target="" />
|
||||
<file src="readme.md" target="" />
|
||||
</files>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Updater</id>
|
||||
<version>6.2.1</version>
|
||||
<version>10.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,13 +12,13 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Updater\bin\Release\net9.0\publish\*.*" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Updater\bin\Release\net10.0\publish\*.*" target="lib\net10.0" />
|
||||
<file src="icon.png" target="" />
|
||||
<file src="readme.md" target="" />
|
||||
</files>
|
||||
|
||||
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.2.1.Install.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.0.Install.zip" -Force
|
||||
|
||||
@ -1,29 +1,30 @@
|
||||
dotnet build -c Release ..\Oqtane.sln
|
||||
dotnet build -c Release ..\Oqtane.slnx
|
||||
FixProps.exe
|
||||
nuget.exe pack Oqtane.Client.nuspec
|
||||
nuget.exe pack Oqtane.Server.nuspec
|
||||
nuget.exe pack Oqtane.Shared.nuspec
|
||||
nuget.exe pack Oqtane.Framework.nuspec
|
||||
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\Content"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x86"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-arm64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x86"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Modules\Templates"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Themes\Templates"
|
||||
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
|
||||
ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\Content"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\wwwroot\Content"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\android-arm"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\android-arm64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\android-x64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\android-x86"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\ios-arm"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\ios-arm64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\iossimulator-arm64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\iossimulator-x64"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\iossimulator-x86"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\wwwroot\Modules\Templates"
|
||||
rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\wwwroot\Themes\Templates"
|
||||
del "..\Oqtane.Server\bin\Release\net10.0\publish\appsettings.json"
|
||||
ren "..\Oqtane.Server\bin\Release\net10.0\publish\appsettings.release.json" "appsettings.json"
|
||||
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
|
||||
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
|
||||
del "..\Oqtane.Server\bin\Release\net9.0\publish\web.config"
|
||||
del "..\Oqtane.Server\bin\Release\net10.0\publish\appsettings.json"
|
||||
del "..\Oqtane.Server\bin\Release\net10.0\publish\web.config"
|
||||
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1"
|
||||
dotnet build -c Release ..\Oqtane.Updater.sln
|
||||
dotnet build -c Release ..\Oqtane.Updater.slnx
|
||||
dotnet publish ..\Oqtane.Updater\Oqtane.Updater.csproj /p:Configuration=Release
|
||||
nuget.exe pack Oqtane.Updater.nuspec
|
||||
nuget.exe pack ..\Oqtane.Application\Oqtane.Application.Template.nuspec -NoDefaultExcludes
|
||||
|
||||
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.2.1.Upgrade.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.0.Upgrade.zip" -Force
|
||||
|
||||
@ -197,10 +197,6 @@
|
||||
{
|
||||
_scripts += CreatePWAScript(alias, site, route);
|
||||
}
|
||||
@if (_renderMode == RenderModes.Static)
|
||||
{
|
||||
_scripts += CreateScrollPositionScript();
|
||||
}
|
||||
|
||||
// set culture if not specified
|
||||
string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName];
|
||||
@ -510,25 +506,6 @@
|
||||
"</script>" + Environment.NewLine;
|
||||
}
|
||||
|
||||
private string CreateScrollPositionScript()
|
||||
{
|
||||
return Environment.NewLine +
|
||||
"<script>" + Environment.NewLine +
|
||||
" // Blazor Static Rendering Scroll Position" + Environment.NewLine +
|
||||
" window.interceptNavigation = () => {" + Environment.NewLine +
|
||||
" let currentUrl = window.location.pathname;" + Environment.NewLine +
|
||||
" Blazor.addEventListener('enhancedload', () => {" + Environment.NewLine +
|
||||
" let newUrl = window.location.pathname;" + Environment.NewLine +
|
||||
" if (currentUrl !== newUrl || window.location.hash === '#top') {" + Environment.NewLine +
|
||||
" window.scrollTo({ top: 0, left: 0, behavior: 'instant' });" + Environment.NewLine +
|
||||
" }" + Environment.NewLine +
|
||||
" currentUrl = newUrl;" + Environment.NewLine +
|
||||
" });" + Environment.NewLine +
|
||||
" };" + Environment.NewLine +
|
||||
" document.onload += window.interceptNavigation();" + Environment.NewLine +
|
||||
"</script>" + Environment.NewLine;
|
||||
}
|
||||
|
||||
private void AddScript(Resource resource, Alias alias)
|
||||
{
|
||||
var script = CreateScript(resource, alias);
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Text.Json;
|
||||
using System.Net;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
@ -132,8 +132,8 @@ namespace Oqtane.Controllers
|
||||
if (moduleDefinition.Template.ToLower().Contains("internal"))
|
||||
{
|
||||
rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString());
|
||||
moduleDefinition.ServerManagerType = moduleDefinition.ModuleDefinitionName + ".Manager." + moduleDefinition.Name + "Manager, Oqtane.Server";
|
||||
moduleDefinition.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName + ", Oqtane.Client";
|
||||
moduleDefinition.ServerManagerType = moduleDefinition.ModuleDefinitionName + ".Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + ".Server.Oqtane";
|
||||
moduleDefinition.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName + ", " + moduleDefinition.Owner + ".Client.Oqtane";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -270,9 +270,10 @@ namespace Oqtane.Controllers
|
||||
foreach (string directory in Directory.GetDirectories(templatePath))
|
||||
{
|
||||
string name = directory.Replace(templatePath, "");
|
||||
if (System.IO.File.Exists(Path.Combine(directory, "template.json")))
|
||||
var manifest = Directory.GetFiles(directory, "*.json");
|
||||
if (manifest.Any())
|
||||
{
|
||||
var template = JsonSerializer.Deserialize<Template>(System.IO.File.ReadAllText(Path.Combine(directory, "template.json")));
|
||||
var template = JsonSerializer.Deserialize<Template>(System.IO.File.ReadAllText(manifest[0]));
|
||||
template.Name = name;
|
||||
template.Location = "";
|
||||
if (template.Type.ToLower() != "internal")
|
||||
@ -350,9 +351,9 @@ namespace Oqtane.Controllers
|
||||
return new Dictionary<string, object>()
|
||||
{
|
||||
{ "FrameworkVersion", Constants.Version },
|
||||
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Client.dll</HintPath></Reference>" },
|
||||
{ "ServerReference", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Server.dll</HintPath></Reference>" },
|
||||
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Shared.dll</HintPath></Reference>" },
|
||||
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Client.dll</HintPath></Reference>" },
|
||||
{ "ServerReference", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Server.dll</HintPath></Reference>" },
|
||||
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Shared.dll</HintPath></Reference>" },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -216,6 +216,13 @@ namespace Oqtane.Controllers
|
||||
page.UserId = int.Parse(userid);
|
||||
page = _pages.AddPage(page);
|
||||
|
||||
// copy parent page settings
|
||||
var settings = _settings.GetSettings(EntityNames.Page, parent.PageId);
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
_settings.AddSetting(new Setting { EntityName = EntityNames.Page, EntityId = page.PageId, SettingName = setting.SettingName, SettingValue = setting.SettingValue, IsPrivate = setting.IsPrivate });
|
||||
}
|
||||
|
||||
// copy modules
|
||||
List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId).ToList();
|
||||
foreach (PageModule pm in pagemodules.Where(item => item.PageId == parent.PageId && !item.IsDeleted))
|
||||
@ -258,8 +265,7 @@ namespace Oqtane.Controllers
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||
|
||||
// set user personalized page path
|
||||
var setting = new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = $"PersonalizedPagePath:{page.SiteId}:{parent.PageId}", SettingValue = path, IsPrivate = false };
|
||||
_settings.AddSetting(setting);
|
||||
_settings.AddSetting(new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = $"PersonalizedPagePath:{page.SiteId}:{parent.PageId}", SettingValue = path, IsPrivate = false });
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,8 +16,6 @@ using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Oqtane.Migrations.Tenant;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using System;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
@ -220,39 +218,25 @@ namespace Oqtane.Controllers
|
||||
var existingSettings = _settings.GetSettings(entityName, entityId).ToList();
|
||||
foreach (Setting setting in settings)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
// manage settings modified with SetSetting method
|
||||
if (setting.SettingValue.StartsWith("[Private]"))
|
||||
Setting existing = existingSettings.FirstOrDefault(item => item.SettingName.Equals(setting.SettingName, StringComparison.OrdinalIgnoreCase));
|
||||
if (existing == null)
|
||||
{
|
||||
modified = true;
|
||||
setting.IsPrivate = true;
|
||||
setting.SettingValue = setting.SettingValue.Substring(9);
|
||||
}
|
||||
if (setting.SettingValue.StartsWith("[Public]"))
|
||||
{
|
||||
modified = true;
|
||||
setting.IsPrivate = false;
|
||||
setting.SettingValue = setting.SettingValue.Substring(8);
|
||||
}
|
||||
|
||||
Setting existingSetting = existingSettings.FirstOrDefault(item => item.SettingName.Equals(setting.SettingName, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingSetting == null)
|
||||
{
|
||||
_settings.AddSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Created {Setting}", setting);
|
||||
setting.SettingId = 0; // initialize
|
||||
existing = _settings.AddSetting(setting);
|
||||
AddSyncEvent(existing.EntityName, existing.EntityId, existing.SettingId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Created {Setting}", existing);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (existingSetting.SettingValue != setting.SettingValue || (modified && existingSetting.IsPrivate != setting.IsPrivate))
|
||||
// note that setting.SettingId will be 0 or -1 if the Settings were converted from a Dictionary in the SettingService
|
||||
if (existing.SettingValue != setting.SettingValue || (existing.IsPrivate != setting.IsPrivate && setting.SettingId != 0))
|
||||
{
|
||||
existingSetting.SettingValue = setting.SettingValue;
|
||||
existingSetting.IsPrivate = setting.IsPrivate;
|
||||
_settings.UpdateSetting(existingSetting);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting);
|
||||
existing.SettingValue = setting.SettingValue;
|
||||
existing.IsPrivate = setting.IsPrivate;
|
||||
existing = _settings.UpdateSetting(existing);
|
||||
AddSyncEvent(existing.EntityName, existing.EntityId, existing.SettingId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -366,13 +350,16 @@ namespace Oqtane.Controllers
|
||||
var setting = _settings.GetSetting(cols[0], entityId, cols[2]);
|
||||
if (setting == null)
|
||||
{
|
||||
_settings.AddSetting(new Setting { EntityName = cols[0], EntityId = entityId, SettingName = cols[2], SettingValue = cols[3], IsPrivate = isPrivate });
|
||||
setting = new Setting { EntityName = cols[0], EntityId = entityId, SettingName = cols[2], SettingValue = cols[3], IsPrivate = isPrivate };
|
||||
_settings.AddSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create);
|
||||
}
|
||||
else
|
||||
{
|
||||
setting.SettingValue = cols[3];
|
||||
setting.IsPrivate = isPrivate;
|
||||
_settings.UpdateSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update);
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5?siteid=x
|
||||
[HttpDelete("{themename}")]
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public void Delete(int id, int siteid)
|
||||
{
|
||||
@ -183,9 +183,10 @@ namespace Oqtane.Controllers
|
||||
foreach (string directory in Directory.GetDirectories(templatePath))
|
||||
{
|
||||
string name = directory.Replace(templatePath, "");
|
||||
if (System.IO.File.Exists(Path.Combine(directory, "template.json")))
|
||||
var manifest = Directory.GetFiles(directory, "*.json");
|
||||
if (manifest.Any())
|
||||
{
|
||||
var template = JsonSerializer.Deserialize<Template>(System.IO.File.ReadAllText(Path.Combine(directory, "template.json")));
|
||||
var template = JsonSerializer.Deserialize<Template>(System.IO.File.ReadAllText(manifest[0]));
|
||||
template.Name = name;
|
||||
template.Location = "";
|
||||
if (template.Type.ToLower() != "internal")
|
||||
@ -226,7 +227,7 @@ namespace Oqtane.Controllers
|
||||
if (theme.Template.ToLower().Contains("internal"))
|
||||
{
|
||||
rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString());
|
||||
theme.ThemeName = theme.ThemeName + ", Oqtane.Client";
|
||||
theme.ThemeName = theme.ThemeName + ", " + theme.Owner + ".Client.Oqtane";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -304,8 +305,8 @@ namespace Oqtane.Controllers
|
||||
return new Dictionary<string, object>()
|
||||
{
|
||||
{ "FrameworkVersion", Constants.Version },
|
||||
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Client.dll</HintPath></Reference>" },
|
||||
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Shared.dll</HintPath></Reference>" },
|
||||
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Client.dll</HintPath></Reference>" },
|
||||
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Shared.dll</HintPath></Reference>" },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,19 +1,22 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Buffers.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Policy;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Oqtane.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Oqtane.Shared;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Managers;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Managers;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
@ -238,8 +241,8 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
// POST api/<controller>/login
|
||||
[HttpPost("login")]
|
||||
// POST api/<controller>/signin
|
||||
[HttpPost("signin")]
|
||||
public async Task<User> Login([FromBody] User user, bool setCookie, bool isPersistent)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
@ -328,22 +331,6 @@ namespace Oqtane.Controllers
|
||||
return user;
|
||||
}
|
||||
|
||||
// POST api/<controller>/link
|
||||
[HttpPost("link")]
|
||||
public async Task<User> Link([FromBody] User user, string token, string type, string key, string name)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
user = await _userManager.LinkExternalAccount(user, token, type, key, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "External Login Linkage Failed For {Username} And Token {Token}", user.Username, token);
|
||||
user = null;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
// GET api/<controller>/validate/x
|
||||
[HttpGet("validateuser")]
|
||||
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
|
||||
@ -463,5 +450,114 @@ namespace Oqtane.Controllers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/<controller>/passkey?id=x
|
||||
[HttpGet("passkey")]
|
||||
[Authorize]
|
||||
public async Task<IEnumerable<UserPasskey>> GetPasskeys(int id)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
|
||||
{
|
||||
return await _userManager.GetPasskeys(id, _tenantManager.GetAlias().SiteId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Get Attempt {UserId} {SiteId}", id, _tenantManager.GetAlias().SiteId);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// PUT api/<controller>/passkey
|
||||
[HttpPut("passkey")]
|
||||
[Authorize]
|
||||
public async Task UpdatePasskey([FromBody] UserPasskey passkey)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == passkey.UserId)
|
||||
{
|
||||
// passkey name is prefixed with SiteId for multi-tenancy
|
||||
passkey.Name = $"{_tenantManager.GetAlias().SiteId}:" + passkey.Name;
|
||||
await _userManager.UpdatePasskey(passkey);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Put Attempt {PassKey}", passkey);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Put Attempt {PassKey}", passkey);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/passkey?id=x&credential=y
|
||||
[HttpDelete("passkey")]
|
||||
[Authorize]
|
||||
public async Task DeletePasskey(int id, string credential)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
|
||||
{
|
||||
await _userManager.DeletePasskey(id, Base64Url.DecodeFromChars(credential));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Delete Attempt {UserId} {Credential}", id, credential);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/<controller>/login?id=x
|
||||
[HttpGet("login")]
|
||||
[Authorize]
|
||||
public async Task<IEnumerable<UserLogin>> GetLogins(int id)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
|
||||
{
|
||||
return await _userManager.GetLogins(id, _tenantManager.GetAlias().SiteId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized External Login Get Attempt {UserId} {SiteId}", id, _tenantManager.GetAlias().SiteId);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// PUT api/<controller>/login
|
||||
[HttpPost("login")]
|
||||
public async Task<User> AddLogin([FromBody] User user, string token, string type, string key, string name)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
user = await _userManager.AddLogin(user, token, type, key, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized External Login Post Attempt {Username} {Token}", user.Username, token);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
user = null;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/login?id=x&provider=y&key=z
|
||||
[HttpDelete("login")]
|
||||
[Authorize]
|
||||
public async Task DeleteLogin(int id, string provider, string key)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
|
||||
{
|
||||
await _userManager.DeleteLogin(id, provider, key);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized External Login Delete Attempt {UserId} {Provider} {Key}", id, provider, key);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Interfaces;
|
||||
@ -102,7 +102,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
})
|
||||
.AddCookie(Constants.AuthenticationScheme)
|
||||
.AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { })
|
||||
.AddOAuth(AuthenticationProviderTypes.OAuth2, options => { });
|
||||
.AddOAuth(AuthenticationProviderTypes.OAuth2, options => { })
|
||||
.AddTwoFactorUserIdCookie();
|
||||
|
||||
services.ConfigureOqtaneCookieOptions();
|
||||
services.ConfigureOqtaneAuthenticationOptions(configuration);
|
||||
|
||||
@ -676,12 +676,12 @@ namespace Oqtane.Extensions
|
||||
var _syncManager = httpContext.RequestServices.GetRequiredService<ISyncManager>();
|
||||
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
|
||||
|
||||
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName);
|
||||
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName);
|
||||
}
|
||||
else
|
||||
{
|
||||
identity.Label = ExternalLoginStatus.AccessDenied;
|
||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "External User Login Denied For {Username}. User Account Is Deleted Or Not An Active Member Of Site {SiteId}.", user.Username, user.SiteId);
|
||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "External Login Denied For {Username}. User Account Is Deleted Or Not An Active Member Of Site {SiteId}.", user.Username, user.SiteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,7 +395,7 @@ namespace Oqtane.Infrastructure
|
||||
var connectionString = _configManager.GetSetting($"{SettingKeys.ConnectionStringsSection}:{tenant.DBConnectionString}", "");
|
||||
if (!string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
using (var tenantDbContext = new TenantDBContext(DBContextDependencies))
|
||||
using (var tenantDbContext = new TenantDBContext(new DbContextOptions<TenantDBContext>(), DBContextDependencies))
|
||||
{
|
||||
AddEFMigrationsHistory(sql, connectionString, tenant.DBType, tenant.Version, false);
|
||||
// push latest model into database
|
||||
@ -507,6 +507,10 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " - ServerManagerType " + moduleDefinition.ServerManagerType + " Does Not Exist";
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(result.Message) && moduleDefinition.Version != versions[versions.Length - 1])
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user