Merge branch 'dev' into dev

This commit is contained in:
gjwalk 2021-06-18 15:35:20 -04:00 committed by GitHub
commit f7363504c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1925 additions and 1092 deletions

View File

@ -40,7 +40,14 @@
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
SiteState.AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken); SiteState.AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken);
_installation = await InstallationService.IsInstalled(); _installation = await InstallationService.IsInstalled();
SiteState.Alias = _installation.Alias; if (_installation.Alias != null)
{
SiteState.Alias = _installation.Alias;
}
else
{
_installation.Message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name";
}
_initialized = true; _initialized = true;
StateHasChanged(); StateHasChanged();
} }

View File

@ -1,3 +1,5 @@
using Microsoft.Extensions.Localization; using System.Runtime.CompilerServices;
using Microsoft.Extensions.Localization;
[assembly: RootNamespace("Oqtane")] [assembly: RootNamespace("Oqtane")]
[assembly: InternalsVisibleTo("Oqtane.Server")]

View File

@ -0,0 +1,22 @@
namespace Microsoft.Extensions.Localization
{
public static class OqtaneLocalizationExtensions
{
/// <summary>
/// Gets the string resource for the specified key and returns the value if the resource does not exist
/// </summary>
/// <param name="localizer"></param>
/// <param name="key">the static key used to identify the string resource</param>
/// <param name="value">the default value if the resource for the static key does not exist</param>
/// <returns></returns>
public static string GetString(this IStringLocalizer localizer, string key, string value)
{
string localizedValue = localizer[key];
if (localizedValue == key && !string.IsNullOrEmpty(value)) // not localized
{
localizedValue = value;
}
return localizedValue;
}
}
}

View File

@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Components.Authorization;
using Oqtane.Providers;
using Oqtane.Services;
using Oqtane.Shared;
namespace Microsoft.Extensions.DependencyInjection
{
public static class OqtaneServiceCollectionExtensions
{
public static IServiceCollection AddOqtaneAuthorization(this IServiceCollection services)
{
services.AddAuthorizationCore();
services.AddScoped<IdentityAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
return services;
}
internal static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
{
services.AddScoped<SiteState>();
services.AddScoped<IInstallationService, InstallationService>();
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
services.AddScoped<IThemeService, ThemeService>();
services.AddScoped<IAliasService, AliasService>();
services.AddScoped<ITenantService, TenantService>();
services.AddScoped<ISiteService, SiteService>();
services.AddScoped<IPageService, PageService>();
services.AddScoped<IModuleService, ModuleService>();
services.AddScoped<IPageModuleService, PageModuleService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IProfileService, ProfileService>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<IUserRoleService, UserRoleService>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IPackageService, PackageService>();
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<INotificationService, NotificationService>();
services.AddScoped<IFolderService, FolderService>();
services.AddScoped<IFileService, FileService>();
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
services.AddScoped<ISqlService, SqlService>();
services.AddScoped<ISystemService, SystemService>();
services.AddScoped<ILocalizationService, LocalizationService>();
services.AddScoped<ILanguageService, LanguageService>();
services.AddScoped<IDatabaseService, DatabaseService>();
services.AddScoped<ISyncService, SyncService>();
return services;
}
}
}

View File

@ -1,47 +0,0 @@
namespace Oqtane
{
public class SharedResources
{
public static readonly string UserLogin = "User Login";
public static readonly string UserRegistration = "User Registration";
public static readonly string PasswordReset = "Password Reset";
public static readonly string UserProfile = "User Profile";
public static readonly string AdminDashboard = "Admin Dashboard";
public static readonly string SiteSettings = "Site Settings";
public static readonly string PageManagement = "Page Management";
public static readonly string UserManagement = "User Management";
public static readonly string ProfileManagement = "Profile Management";
public static readonly string RoleManagement = "Role Management";
public static readonly string FileManagement = "File Management";
public static readonly string RecycleBin = "Recycle Bin";
public static readonly string EventLog = "Event Log";
public static readonly string SiteManagement = "Site Management";
public static readonly string ModuleManagement = "Module Management";
public static readonly string ThemeManagement = "Theme Management";
public static readonly string LanguageManagement = "Language Management";
public static readonly string ScheduledJobs = "Scheduled Jobs";
public static readonly string SqlManagement = "Sql Management";
public static readonly string SystemInfo = "System Info";
public static readonly string SystemUpdate = "System Update";
}
}

View File

@ -2,7 +2,7 @@
@inherits ModuleBase @inherits ModuleBase
@inject IPageService PageService @inject IPageService PageService
@inject IUserService UserService @inject IUserService UserService
@inject IStringLocalizer<SharedResources> Localizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row"> <div class="row">
@foreach (var p in _pages) @foreach (var p in _pages)
@ -12,7 +12,7 @@
string url = NavigateUrl(p.Path); string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center"> <div class="col-md-2 mx-auto text-center">
<NavLink class="nav-link" href="@url" Match="NavLinkMatch.All"> <NavLink class="nav-link" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@Localizer[p.Name] <h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
</NavLink> </NavLink>
</div> </div>
} }

View File

@ -142,7 +142,7 @@
await logger.LogInformation("Login Successful For Username {Username}", _username); await logger.LogInformation("Login Successful For Username {Username}", _username);
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged(); authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, "reload")); NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true));
} }
else else
{ {

View File

@ -103,7 +103,7 @@ else
{ {
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId); await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success); AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "reload")); NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -51,12 +51,11 @@ else
private void Edit(string name) private void Edit(string name)
{ {
NavigationManager.NavigateTo(_scheme + name + "/admin/site", true); NavigationManager.NavigateTo(_scheme + name + "/admin/site/?reload");
} }
private void Browse(string name) private void Browse(string name)
{ {
NavigationManager.NavigateTo(_scheme + name, true); NavigationManager.NavigateTo(_scheme + name + "/?reload");
} }
} }

View File

@ -104,7 +104,7 @@ else
{ {
await ThemeService.DeleteThemeAsync(Theme.ThemeName); await ThemeService.DeleteThemeAsync(Theme.ThemeName);
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success); AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "reload")); NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -84,11 +84,21 @@ namespace Oqtane.Modules
return NavigateUrl(path, ""); return NavigateUrl(path, "");
} }
public string NavigateUrl(bool refresh)
{
return NavigateUrl(PageState.Page.Path, refresh);
}
public string NavigateUrl(string path, string parameters) public string NavigateUrl(string path, string parameters)
{ {
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters); return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);
} }
public string NavigateUrl(string path, bool refresh)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, refresh ? "refresh" : "");
}
public string EditUrl(string action) public string EditUrl(string action)
{ {
return EditUrl(ModuleState.ModuleId, action); return EditUrl(ModuleState.ModuleId, action);

View File

@ -33,4 +33,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" /> <ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
</ItemGroup>
</Project> </Project>

View File

@ -8,13 +8,11 @@ using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Providers;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
@ -27,7 +25,8 @@ namespace Oqtane.Client
{ {
var builder = WebAssemblyHostBuilder.CreateDefault(args); var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app"); builder.RootComponents.Add<App>("app");
HttpClient httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
var httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
builder.Services.AddSingleton(httpClient); builder.Services.AddSingleton(httpClient);
builder.Services.AddOptions(); builder.Services.AddOptions();
@ -36,40 +35,10 @@ namespace Oqtane.Client
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services // register auth services
builder.Services.AddAuthorizationCore(); builder.Services.AddOqtaneAuthorization();
builder.Services.AddScoped<IdentityAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
// register scoped core services // register scoped core services
builder.Services.AddScoped<SiteState>(); builder.Services.AddOqtaneScopedServices();
builder.Services.AddScoped<IInstallationService, InstallationService>();
builder.Services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
builder.Services.AddScoped<IThemeService, ThemeService>();
builder.Services.AddScoped<IAliasService, AliasService>();
builder.Services.AddScoped<ITenantService, TenantService>();
builder.Services.AddScoped<ISiteService, SiteService>();
builder.Services.AddScoped<IPageService, PageService>();
builder.Services.AddScoped<IModuleService, ModuleService>();
builder.Services.AddScoped<IPageModuleService, PageModuleService>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IProfileService, ProfileService>();
builder.Services.AddScoped<IRoleService, RoleService>();
builder.Services.AddScoped<IUserRoleService, UserRoleService>();
builder.Services.AddScoped<ISettingService, SettingService>();
builder.Services.AddScoped<IPackageService, PackageService>();
builder.Services.AddScoped<ILogService, LogService>();
builder.Services.AddScoped<IJobService, JobService>();
builder.Services.AddScoped<IJobLogService, JobLogService>();
builder.Services.AddScoped<INotificationService, NotificationService>();
builder.Services.AddScoped<IFolderService, FolderService>();
builder.Services.AddScoped<IFileService, FileService>();
builder.Services.AddScoped<ISiteTemplateService, SiteTemplateService>();
builder.Services.AddScoped<ISqlService, SqlService>();
builder.Services.AddScoped<ISystemService, SystemService>();
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
builder.Services.AddScoped<ILanguageService, LanguageService>();
builder.Services.AddScoped<IDatabaseService, DatabaseService>();
builder.Services.AddScoped<ISyncService, SyncService>();
await LoadClientAssemblies(httpClient); await LoadClientAssemblies(httpClient);
@ -77,38 +46,15 @@ namespace Oqtane.Client
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{ {
// dynamically register module services // dynamically register module services
var implementationTypes = assembly.GetInterfaces<IService>(); RegisterModuleServices(assembly, builder.Services);
foreach (var implementationType in implementationTypes)
{
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
builder.Services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
// register client startup services // register client startup services
var startUps = assembly.GetInstances<IClientStartup>(); RegisterClientStartups(assembly, builder.Services);
foreach (var startup in startUps)
{
startup.ConfigureServices(builder.Services);
}
} }
var host = builder.Build(); var host = builder.Build();
var jsRuntime = host.Services.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime);
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie).UICultures[0].Value;
var localizationService = host.Services.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync();
if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase))) await SetCultureFromLocalizationCookie(host.Services);
{
culture = cultures.Single(c => c.IsDefault).Name;
}
SetCulture(culture);
ServiceActivator.Configure(host.Services); ServiceActivator.Configure(host.Services);
@ -164,6 +110,45 @@ namespace Oqtane.Client
} }
} }
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
{
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
}
private static void RegisterClientStartups(Assembly assembly, IServiceCollection services)
{
var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(services);
}
}
private static async Task SetCultureFromLocalizationCookie(IServiceProvider serviceProvider)
{
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime);
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync();
if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase)))
{
culture = cultures.Single(c => c.IsDefault).Name;
}
SetCulture(culture);
}
private static void SetCulture(string culture) private static void SetCulture(string culture)
{ {
var cultureInfo = CultureInfo.GetCultureInfo(culture); var cultureInfo = CultureInfo.GetCultureInfo(culture);

View File

@ -0,0 +1,207 @@
<?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="True" xml:space="preserve">
<value>True</value>
</data>
<data name="False" xml:space="preserve">
<value>False</value>
</data>
<data name="Yes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="No" xml:space="preserve">
<value>No</value>
</data>
<data name="Save" xml:space="preserve">
<value>Save</value>
</data>
<data name="Update" xml:space="preserve">
<value>Update</value>
</data>
<data name="Delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Admin Dashboard" xml:space="preserve">
<value>Admin Dashboard</value>
</data>
<data name="User Login" xml:space="preserve">
<value>User Login</value>
</data>
<data name="User Registration" xml:space="preserve">
<value>User Registration</value>
</data>
<data name="Password Reset" xml:space="preserve">
<value>Password Reset</value>
</data>
<data name="User Profile" xml:space="preserve">
<value>User Profile</value>
</data>
<data name="Site Settings" xml:space="preserve">
<value>Site Settings</value>
</data>
<data name="Page Management" xml:space="preserve">
<value>Page Management</value>
</data>
<data name="User Management" xml:space="preserve">
<value>User Management</value>
</data>
<data name="Profile Management" xml:space="preserve">
<value>Profile Management</value>
</data>
<data name="Role Management" xml:space="preserve">
<value>Role Management</value>
</data>
<data name="File Management" xml:space="preserve">
<value>File Management</value>
</data>
<data name="Recycle Bin" xml:space="preserve">
<value>Recycle Bin</value>
</data>
<data name="Event Log" xml:space="preserve">
<value>Event Log</value>
</data>
<data name="Site Management" xml:space="preserve">
<value>Site Management</value>
</data>
<data name="Module Management" xml:space="preserve">
<value>Module Management</value>
</data>
<data name="Theme Management" xml:space="preserve">
<value>Theme Management</value>
</data>
<data name="Language Management" xml:space="preserve">
<value>Language Management</value>
</data>
<data name="Scheduled Jobs" xml:space="preserve">
<value>Scheduled Jobs</value>
</data>
<data name="Sql Management" xml:space="preserve">
<value>Sql Management</value>
</data>
<data name="System Info" xml:space="preserve">
<value>System Info</value>
</data>
<data name="System Update" xml:space="preserve">
<value>System Update</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -9,11 +9,5 @@ namespace Oqtane.Services
Task<List<JobLog>> GetJobLogsAsync(); Task<List<JobLog>> GetJobLogsAsync();
Task<JobLog> GetJobLogAsync(int jobLogId); Task<JobLog> GetJobLogAsync(int jobLogId);
Task<JobLog> AddJobLogAsync(JobLog jobLog);
Task<JobLog> UpdateJobLogAsync(JobLog jobLog);
Task DeleteJobLogAsync(int jobLogId);
} }
} }

View File

@ -21,26 +21,5 @@ namespace Oqtane.Services
/// <param name="tenantId">ID-reference of the <see cref="Tenant"/></param> /// <param name="tenantId">ID-reference of the <see cref="Tenant"/></param>
/// <returns></returns> /// <returns></returns>
Task<Tenant> GetTenantAsync(int tenantId); Task<Tenant> GetTenantAsync(int tenantId);
/// <summary>
/// Add / save another <see cref="Tenant"/> to the database
/// </summary>
/// <param name="tenant">A <see cref="Tenant"/> object containing the configuration</param>
/// <returns></returns>
Task<Tenant> AddTenantAsync(Tenant tenant);
/// <summary>
/// Update the <see cref="Tenant"/> information in the database.
/// </summary>
/// <param name="tenant"></param>
/// <returns></returns>
Task<Tenant> UpdateTenantAsync(Tenant tenant);
/// <summary>
/// Delete / remove a <see cref="Tenant"/>
/// </summary>
/// <param name="tenantId"></param>
/// <returns></returns>
Task DeleteTenantAsync(int tenantId);
} }
} }

View File

@ -30,19 +30,5 @@ namespace Oqtane.Services
{ {
return await GetJsonAsync<JobLog>($"{Apiurl}/{jobLogId}"); return await GetJsonAsync<JobLog>($"{Apiurl}/{jobLogId}");
} }
public async Task<JobLog> AddJobLogAsync(JobLog joblog)
{
return await PostJsonAsync<JobLog>(Apiurl, joblog);
}
public async Task<JobLog> UpdateJobLogAsync(JobLog joblog)
{
return await PutJsonAsync<JobLog>($"{Apiurl}/{joblog.JobLogId}", joblog);
}
public async Task DeleteJobLogAsync(int jobLogId)
{
await DeleteAsync($"{Apiurl}/{jobLogId}");
}
} }
} }

View File

@ -30,20 +30,5 @@ namespace Oqtane.Services
{ {
return await GetJsonAsync<Tenant>($"{Apiurl}/{tenantId}"); return await GetJsonAsync<Tenant>($"{Apiurl}/{tenantId}");
} }
public async Task<Tenant> AddTenantAsync(Tenant tenant)
{
return await PostJsonAsync<Tenant>(Apiurl, tenant);
}
public async Task<Tenant> UpdateTenantAsync(Tenant tenant)
{
return await PutJsonAsync<Tenant>($"{Apiurl}/{tenant.TenantId}", tenant);
}
public async Task DeleteTenantAsync(int tenantId)
{
await DeleteAsync($"{Apiurl}/{tenantId}");
}
} }
} }

View File

@ -0,0 +1,14 @@
namespace Oqtane
{
/// <summary>
/// Dummy class used to collect shared resource strings for this application
/// </summary>
/// <remarks>
/// This class is mostly used with IStringLocalizer and IHtmlLocalizer interfaces.
/// The class must reside at the project root.
/// </remarks>
public class SharedResources
{
}
}

View File

@ -115,7 +115,7 @@ namespace Oqtane.Themes.Controls
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane);
return NavigateUrl(url, "reload"); return NavigateUrl(url, true);
} }
private async Task<string> DeleteModule(string url, PageModule pagemodule) private async Task<string> DeleteModule(string url, PageModule pagemodule)
@ -123,7 +123,7 @@ namespace Oqtane.Themes.Controls
pagemodule.IsDeleted = true; pagemodule.IsDeleted = true;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(url, "reload"); return NavigateUrl(url, true);
} }
private async Task<string> Settings(string url, PageModule pagemodule) private async Task<string> Settings(string url, PageModule pagemodule)
@ -148,7 +148,7 @@ namespace Oqtane.Themes.Controls
} }
pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions); pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions);
await ModuleService.UpdateModuleAsync(pagemodule.Module); await ModuleService.UpdateModuleAsync(pagemodule.Module);
return NavigateUrl(s, "reload"); return NavigateUrl(s, true);
} }
private async Task<string> Unpublish(string s, PageModule pagemodule) private async Task<string> Unpublish(string s, PageModule pagemodule)
@ -166,7 +166,7 @@ namespace Oqtane.Themes.Controls
} }
pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions); pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions);
await ModuleService.UpdateModuleAsync(pagemodule.Module); await ModuleService.UpdateModuleAsync(pagemodule.Module);
return NavigateUrl(s, "reload"); return NavigateUrl(s, true);
} }
private async Task<string> MoveTop(string s, PageModule pagemodule) private async Task<string> MoveTop(string s, PageModule pagemodule)
@ -174,7 +174,7 @@ namespace Oqtane.Themes.Controls
pagemodule.Order = 0; pagemodule.Order = 0;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, "reload"); return NavigateUrl(s, true);
} }
private async Task<string> MoveBottom(string s, PageModule pagemodule) private async Task<string> MoveBottom(string s, PageModule pagemodule)
@ -182,7 +182,7 @@ namespace Oqtane.Themes.Controls
pagemodule.Order = int.MaxValue; pagemodule.Order = int.MaxValue;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, "reload"); return NavigateUrl(s, true);
} }
private async Task<string> MoveUp(string s, PageModule pagemodule) private async Task<string> MoveUp(string s, PageModule pagemodule)
@ -190,7 +190,7 @@ namespace Oqtane.Themes.Controls
pagemodule.Order -= 3; pagemodule.Order -= 3;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, "reload"); return NavigateUrl(s, true);
} }
private async Task<string> MoveDown(string s, PageModule pagemodule) private async Task<string> MoveDown(string s, PageModule pagemodule)
@ -198,7 +198,7 @@ namespace Oqtane.Themes.Controls
pagemodule.Order += 3; pagemodule.Order += 3;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, "reload"); return NavigateUrl(s, true);
} }
public class ActionViewModel public class ActionViewModel

View File

@ -10,6 +10,7 @@
@inject ILogService logger @inject ILogService logger
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<ControlPanel> Localizer @inject IStringLocalizer<ControlPanel> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_moduleDefinitions != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) @if (_moduleDefinitions != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{ {
@ -41,13 +42,13 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<button type="button" class="btn btn-primary btn-block mx-auto" @onclick=@(async () => Navigate("Add"))>@Localizer["Add"]</button> <button type="button" class="btn btn-primary btn-block mx-auto" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button>
</div> </div>
<div class="col"> <div class="col">
<button type="button" class="btn btn-primary btn-block mx-auto" @onclick=@(async () => Navigate("Edit"))>@Localizer["Edit"]</button> <button type="button" class="btn btn-primary btn-block mx-auto" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button>
</div> </div>
<div class="col"> <div class="col">
<button class="btn btn-danger btn-block mx-auto" @onclick="ConfirmDelete">@Localizer["Delete"]</button> <button class="btn btn-danger btn-block mx-auto" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
</div> </div>
</div> </div>
<br /> <br />
@ -81,8 +82,8 @@
<p>Are You Sure You Want To Delete This Page?</p> <p>Are You Sure You Want To Delete This Page?</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-danger" @onclick="DeletePage">@Localizer["Delete"]</button> <button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button>
<button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@Localizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button>
</div> </div>
</div> </div>
</div> </div>
@ -537,7 +538,7 @@
} }
page.Permissions = UserSecurity.SetPermissionStrings(permissions); page.Permissions = UserSecurity.SetPermissionStrings(permissions);
await PageService.UpdatePageAsync(page); await PageService.UpdatePageAsync(page);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "reload")); NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
} }
} }

View File

@ -47,7 +47,7 @@ namespace Oqtane.Themes.Controls
// client-side Blazor // client-side Blazor
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged(); authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(!authorizedtoviewpage ? PageState.Alias.Path : PageState.Page.Path, "reload")); NavigationManager.NavigateTo(NavigateUrl(!authorizedtoviewpage ? PageState.Alias.Path : PageState.Page.Path, true));
} }
} }
} }

View File

@ -64,6 +64,16 @@ namespace Oqtane.Themes
return NavigateUrl(path, ""); return NavigateUrl(path, "");
} }
public string NavigateUrl(bool refresh)
{
return NavigateUrl(PageState.Page.Path, refresh);
}
public string NavigateUrl(string path, bool refresh)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, refresh ? "refresh" : "");
}
public string NavigateUrl(string path, string parameters) public string NavigateUrl(string path, string parameters)
{ {
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters); return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);

View File

@ -1,6 +1,6 @@
namespace Oqtane.UI namespace Oqtane.UI
{ {
public enum Reload public enum Refresh
{ {
None, None,
Page, Page,

View File

@ -77,7 +77,7 @@
var action = Constants.DefaultAction; var action = Constants.DefaultAction;
var urlparameters = string.Empty; var urlparameters = string.Empty;
var editmode = false; var editmode = false;
var reload = Reload.None; var refresh = UI.Refresh.None;
var lastsyncdate = DateTime.UtcNow.AddHours(-1); var lastsyncdate = DateTime.UtcNow.AddHours(-1);
var runtime = GetRuntime(); var runtime = GetRuntime();
@ -89,10 +89,19 @@
// parse querystring // parse querystring
var querystring = ParseQueryString(uri.Query); var querystring = ParseQueryString(uri.Query);
// the reload parameter is used to reload the PageState // the refresh parameter is used to refresh the PageState
if (querystring.ContainsKey("reload")) if (querystring.ContainsKey("refresh"))
{ {
reload = Reload.Site; refresh = UI.Refresh.Site;
}
else
{
// reload the client application if the user navigated to a site with a different alias or there is a forced reload
if ((!path.StartsWith(SiteState.Alias.Path) && SiteState.Alias.Path != "") || querystring.ContainsKey("reload"))
{
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
return;
}
} }
if (PageState != null) if (PageState != null)
@ -104,23 +113,24 @@
// process any sync events // process any sync events
var sync = await SyncService.GetSyncAsync(lastsyncdate); var sync = await SyncService.GetSyncAsync(lastsyncdate);
lastsyncdate = sync.SyncDate; lastsyncdate = sync.SyncDate;
if (reload != Reload.Site && sync.SyncEvents.Any()) if (refresh != UI.Refresh.Site && sync.SyncEvents.Any())
{ {
// if running on WebAssembly reload the client application if the server application was restarted // if running on WebAssembly reload the client application if the server application was restarted
if (runtime == Shared.Runtime.WebAssembly && PageState != null && sync.SyncEvents.Exists(item => item.TenantId == -1)) if (runtime == Shared.Runtime.WebAssembly && PageState != null && sync.SyncEvents.Exists(item => item.TenantId == -1))
{ {
NavigationManager.NavigateTo(_absoluteUri + (!_absoluteUri.Contains("?") ? "?" : "&") + "reload", true); NavigationManager.NavigateTo(_absoluteUri, true);
return;
} }
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId)) if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{ {
reload = Reload.Site; refresh = UI.Refresh.Site;
} }
} }
if (reload == Reload.Site || PageState == null || PageState.Alias.SiteId != SiteState.Alias.SiteId) if (refresh == UI.Refresh.Site || PageState == null || PageState.Alias.SiteId != SiteState.Alias.SiteId)
{ {
site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId); site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId);
reload = Reload.Site; refresh = UI.Refresh.Site;
} }
else else
{ {
@ -129,7 +139,7 @@
if (site != null) if (site != null)
{ {
if (PageState == null || reload == Reload.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
// get user // get user
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
@ -144,15 +154,15 @@
} }
// process any sync events for user // process any sync events for user
if (reload != Reload.Site && user != null && sync.SyncEvents.Any()) if (refresh != UI.Refresh.Site && user != null && sync.SyncEvents.Any())
{ {
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId)) if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
{ {
reload = Reload.Site; refresh = UI.Refresh.Site;
} }
} }
if (PageState == null || reload == Reload.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
pages = await PageService.GetPagesAsync(site.SiteId); pages = await PageService.GetPagesAsync(site.SiteId);
} }
@ -169,7 +179,7 @@
path += "/"; path += "/";
} }
if (SiteState.Alias.Path != "") if (SiteState.Alias.Path != "" && path.StartsWith(SiteState.Alias.Path))
{ {
path = path.Substring(SiteState.Alias.Path.Length + 1); path = path.Substring(SiteState.Alias.Path.Length + 1);
} }
@ -229,7 +239,7 @@
// remove trailing slash so it can be used as a key for Pages // remove trailing slash so it can be used as a key for Pages
if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1); if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1);
if (PageState == null || reload == Reload.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
page = pages.FirstOrDefault(item => item.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); page = pages.FirstOrDefault(item => item.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
} }
@ -264,7 +274,7 @@
{ {
page = await ProcessPage(page, site, user); page = await ProcessPage(page, site, user);
if (PageState == null || reload == Reload.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
modules = await ModuleService.GetModulesAsync(site.SiteId); modules = await ModuleService.GetModulesAsync(site.SiteId);
} }

View File

@ -10,6 +10,7 @@
@using Microsoft.Extensions.Localization @using Microsoft.Extensions.Localization
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using Oqtane.Client
@using Oqtane.Models @using Oqtane.Models
@using Oqtane.Modules @using Oqtane.Modules
@using Oqtane.Modules.Controls @using Oqtane.Modules.Controls
@ -22,3 +23,4 @@
@using Oqtane.UI @using Oqtane.UI
@using Oqtane.Enums @using Oqtane.Enums
@using Oqtane.Installer @using Oqtane.Installer
@using Oqtane.Interfaces

View File

@ -18,6 +18,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Database.Sqlite", "O
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Database.SqlServer", "Oqtane.Database.SqlServer\Oqtane.Database.SqlServer.csproj", "{033DCA37-6354-4A3D-8250-4EC20740EE19}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Database.SqlServer", "Oqtane.Database.SqlServer\Oqtane.Database.SqlServer.csproj", "{033DCA37-6354-4A3D-8250-4EC20740EE19}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Server", "Oqtane.Server\Oqtane.Server.csproj", "{6A60C4DD-67E6-42A7-B9AA-A1EE45AD45C7}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -40,6 +42,10 @@ Global
{033DCA37-6354-4A3D-8250-4EC20740EE19}.Debug|Any CPU.Build.0 = Debug|Any CPU {033DCA37-6354-4A3D-8250-4EC20740EE19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{033DCA37-6354-4A3D-8250-4EC20740EE19}.Release|Any CPU.ActiveCfg = Release|Any CPU {033DCA37-6354-4A3D-8250-4EC20740EE19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{033DCA37-6354-4A3D-8250-4EC20740EE19}.Release|Any CPU.Build.0 = Release|Any CPU {033DCA37-6354-4A3D-8250-4EC20740EE19}.Release|Any CPU.Build.0 = Release|Any CPU
{6A60C4DD-67E6-42A7-B9AA-A1EE45AD45C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A60C4DD-67E6-42A7-B9AA-A1EE45AD45C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A60C4DD-67E6-42A7-B9AA-A1EE45AD45C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A60C4DD-67E6-42A7-B9AA-A1EE45AD45C7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -72,7 +72,7 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
public Alias Put(int id, [FromBody] Alias alias) public Alias Put(int id, [FromBody] Alias alias)
{ {
if (ModelState.IsValid) if (ModelState.IsValid && _aliases.GetAlias(alias.AliasId, false) != null)
{ {
alias = _aliases.UpdateAlias(alias); alias = _aliases.UpdateAlias(alias);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Alias Updated {Alias}", alias); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Alias Updated {Alias}", alias);
@ -91,8 +91,17 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
public void Delete(int id) public void Delete(int id)
{ {
_aliases.DeleteAlias(id); var alias = _aliases.GetAlias(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id); if (alias != null)
{
_aliases.DeleteAlias(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Alias Delete Attempt {AliasId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
} }
} }
} }

View File

@ -15,6 +15,7 @@ using Microsoft.Extensions.Caching.Memory;
using System.Net; using System.Net;
using Oqtane.Repository; using Oqtane.Repository;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System.Diagnostics;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -130,7 +131,7 @@ namespace Oqtane.Controllers
} }
else else
{ {
Console.WriteLine($"The satellite assemblies folder named '{culture}' is not found."); Debug.WriteLine($"Oqtane Error: The Satellite Assembly Folder For {culture} Does Not Exist");
} }
} }
@ -148,7 +149,7 @@ namespace Oqtane.Controllers
} }
else else
{ {
Console.WriteLine("Module " + instance.ModuleDefinition.ModuleDefinitionName + " dependency " + name + ".dll does not exist"); Debug.WriteLine($"Oqtane Error: Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist");
} }
} }
} }
@ -163,7 +164,7 @@ namespace Oqtane.Controllers
} }
else else
{ {
Console.WriteLine("Theme " + instance.Theme.ThemeName + " dependency " + name + ".dll does not exist" ); Debug.WriteLine($"Oqtane Error: Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist");
} }
} }
} }

View File

@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection;
using Oqtane.Enums; using Oqtane.Enums;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using System.Net;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -52,6 +53,12 @@ namespace Oqtane.Controllers
job = _jobs.AddJob(job); job = _jobs.AddJob(job);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Job Added {Job}", job); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Job Added {Job}", job);
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Post Attempt {Alias}", job);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
job = null;
}
return job; return job;
} }
@ -60,11 +67,17 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
public Job Put(int id, [FromBody] Job job) public Job Put(int id, [FromBody] Job job)
{ {
if (ModelState.IsValid) if (ModelState.IsValid && _jobs.GetJob(job.JobId, false) != null)
{ {
job = _jobs.UpdateJob(job); job = _jobs.UpdateJob(job);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Job Updated {Job}", job); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Job Updated {Job}", job);
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Put Attempt {Alias}", job);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
job = null;
}
return job; return job;
} }
@ -73,8 +86,17 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
public void Delete(int id) public void Delete(int id)
{ {
_jobs.DeleteJob(id); var job = _jobs.GetJob(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Job Deleted {JobId}", id); if (job != null)
{
_jobs.DeleteJob(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Job Deleted {JobId}", id);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Delete Attempt {JobId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
} }
// GET api/<controller>/start // GET api/<controller>/start
@ -83,12 +105,17 @@ namespace Oqtane.Controllers
public void Start(int id) public void Start(int id)
{ {
Job job = _jobs.GetJob(id); Job job = _jobs.GetJob(id);
Type jobtype = Type.GetType(job.JobType); if (job != null)
if (jobtype != null)
{ {
Type jobtype = Type.GetType(job.JobType);
var jobobject = ActivatorUtilities.CreateInstance(_serviceProvider, jobtype); var jobobject = ActivatorUtilities.CreateInstance(_serviceProvider, jobtype);
((IHostedService)jobobject).StartAsync(new System.Threading.CancellationToken()); ((IHostedService)jobobject).StartAsync(new System.Threading.CancellationToken());
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Start Attempt {JobId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
} }
// GET api/<controller>/stop // GET api/<controller>/stop
@ -97,12 +124,17 @@ namespace Oqtane.Controllers
public void Stop(int id) public void Stop(int id)
{ {
Job job = _jobs.GetJob(id); Job job = _jobs.GetJob(id);
Type jobtype = Type.GetType(job.JobType); if (job != null)
if (jobtype != null)
{ {
Type jobtype = Type.GetType(job.JobType);
var jobobject = ActivatorUtilities.CreateInstance(_serviceProvider, jobtype); var jobobject = ActivatorUtilities.CreateInstance(_serviceProvider, jobtype);
((IHostedService)jobobject).StopAsync(new System.Threading.CancellationToken()); ((IHostedService)jobobject).StopAsync(new System.Threading.CancellationToken());
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Stop Attempt {JobId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
} }
} }
} }

View File

@ -1,10 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Oqtane.Enums;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
namespace Oqtane.Controllers namespace Oqtane.Controllers
@ -13,12 +11,10 @@ namespace Oqtane.Controllers
public class JobLogController : Controller public class JobLogController : Controller
{ {
private readonly IJobLogRepository _jobLogs; private readonly IJobLogRepository _jobLogs;
private readonly ILogManager _logger;
public JobLogController(IJobLogRepository jobLogs, ILogManager logger) public JobLogController(IJobLogRepository jobLogs)
{ {
_jobLogs = jobLogs; _jobLogs = jobLogs;
_logger = logger;
} }
// GET: api/<controller> // GET: api/<controller>
@ -36,40 +32,5 @@ namespace Oqtane.Controllers
{ {
return _jobLogs.GetJobLog(id); return _jobLogs.GetJobLog(id);
} }
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Host)]
public JobLog Post([FromBody] JobLog jobLog)
{
if (ModelState.IsValid)
{
jobLog = _jobLogs.AddJobLog(jobLog);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Job Log Added {JobLog}", jobLog);
}
return jobLog;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Host)]
public JobLog Put(int id, [FromBody] JobLog jobLog)
{
if (ModelState.IsValid)
{
jobLog = _jobLogs.UpdateJobLog(jobLog);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Job Log Updated {JobLog}", jobLog);
}
return jobLog;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
_jobLogs.DeleteJobLog(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Job Log Deleted {JobLogId}", id);
}
} }
} }

View File

@ -103,7 +103,7 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
public ModuleDefinition Post([FromBody] ModuleDefinition moduleDefinition) public ModuleDefinition Post([FromBody] ModuleDefinition moduleDefinition)
{ {
if (ModelState.IsValid && moduleDefinition.SiteId == _alias.SiteId) if (ModelState.IsValid)
{ {
string rootPath; string rootPath;
DirectoryInfo rootFolder = Directory.GetParent(_environment.ContentRootPath); DirectoryInfo rootFolder = Directory.GetParent(_environment.ContentRootPath);

View File

@ -36,40 +36,5 @@ namespace Oqtane.Controllers
{ {
return _tenants.GetTenant(id); return _tenants.GetTenant(id);
} }
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Host)]
public Tenant Post([FromBody] Tenant tenant)
{
if (ModelState.IsValid)
{
tenant = _tenants.AddTenant(tenant);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Tenant Added {TenantId}", tenant.TenantId);
}
return tenant;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Host)]
public Tenant Put(int id, [FromBody] Tenant tenant)
{
if (ModelState.IsValid)
{
tenant = _tenants.UpdateTenant(tenant);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Tenant Updated {TenantId}", tenant.TenantId);
}
return tenant;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
_tenants.DeleteTenant(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Tenant Deleted {TenantId}", id);
}
} }
} }

View File

@ -11,6 +11,7 @@ using Oqtane.Enums;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using System.Text.Json; using System.Text.Json;
using System.Net;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -84,6 +85,11 @@ namespace Oqtane.Controllers
_themes.DeleteTheme(theme.ThemeName); _themes.DeleteTheme(theme.ThemeName);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Removed For {ThemeName}", theme.ThemeName); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Removed For {ThemeName}", theme.ThemeName);
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Delete Attempt {Themename}", themename);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
} }
// GET: api/<controller>/templates // GET: api/<controller>/templates
@ -141,6 +147,12 @@ namespace Oqtane.Controllers
ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, theme); ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, theme);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Theme Created {Theme}", theme); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Theme Created {Theme}", theme);
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Post Attempt {Theme}", theme);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
theme = null;
}
return theme; return theme;
} }

View File

@ -1,16 +1,24 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Interfaces;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
{ {
public static class OqtaneServiceCollectionExtensions public static class OqtaneServiceCollectionExtensions
@ -24,6 +32,161 @@ namespace Microsoft.Extensions.DependencyInjection
return services; return services;
} }
public static IServiceCollection AddOqtaneDbContext(this IServiceCollection services)
{
services.AddDbContext<MasterDBContext>(options => { });
services.AddDbContext<TenantDBContext>(options => { });
return services;
}
public static IServiceCollection AddOqtaneAuthorizationPolicies(this IServiceCollection services)
{
services.AddAuthorizationCore(options =>
{
options.AddPolicy(PolicyNames.ViewPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ListFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Browse)));
});
return services;
}
internal static IServiceCollection AddOqtaneSingletonServices(this IServiceCollection services)
{
services.AddSingleton<IInstallationManager, InstallationManager>();
services.AddSingleton<ISyncManager, SyncManager>();
services.AddSingleton<IDatabaseManager, DatabaseManager>();
services.AddSingleton<IConfigManager, ConfigManager>();
return services;
}
internal static IServiceCollection AddOqtaneTransientServices(this IServiceCollection services)
{
services.AddTransient<ITenantManager, TenantManager>();
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
services.AddTransient<IThemeRepository, ThemeRepository>();
services.AddTransient<IUserPermissions, UserPermissions>();
services.AddTransient<ITenantResolver, TenantResolver>();
services.AddTransient<IAliasRepository, AliasRepository>();
services.AddTransient<ITenantRepository, TenantRepository>();
services.AddTransient<ISiteRepository, SiteRepository>();
services.AddTransient<IPageRepository, PageRepository>();
services.AddTransient<IModuleRepository, ModuleRepository>();
services.AddTransient<IPageModuleRepository, PageModuleRepository>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<IProfileRepository, ProfileRepository>();
services.AddTransient<IRoleRepository, RoleRepository>();
services.AddTransient<IUserRoleRepository, UserRoleRepository>();
services.AddTransient<IPermissionRepository, PermissionRepository>();
services.AddTransient<ISettingRepository, SettingRepository>();
services.AddTransient<ILogRepository, LogRepository>();
services.AddTransient<ILogManager, LogManager>();
services.AddTransient<ILocalizationManager, LocalizationManager>();
services.AddTransient<IJobRepository, JobRepository>();
services.AddTransient<IJobLogRepository, JobLogRepository>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddTransient<IFolderRepository, FolderRepository>();
services.AddTransient<IFileRepository, FileRepository>();
services.AddTransient<ISiteTemplateRepository, SiteTemplateRepository>();
services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<ILanguageRepository, LanguageRepository>();
// obsolete - replaced by ITenantManager
services.AddTransient<ITenantResolver, TenantResolver>();
return services;
}
public static IServiceCollection ConfigureOqtaneCookieOptions(this IServiceCollection services)
{
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = false;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
};
options.Events.OnValidatePrincipal = PrincipalValidator.ValidateAsync;
});
return services;
}
public static IServiceCollection ConfigureOqtaneIdentityOptions(this IServiceCollection services)
{
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = false;
});
return services;
}
internal static IServiceCollection TryAddHttpClientWithAuthenticationCookie(this IServiceCollection services)
{
if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
{
services.AddScoped(s =>
{
// creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var navigationManager = s.GetRequiredService<NavigationManager>();
var client = new HttpClient(new HttpClientHandler { UseCookies = false });
client.BaseAddress = new Uri(navigationManager.Uri);
// set the cookies to allow HttpClient API calls to be authenticated
var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>();
foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies)
{
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value);
}
return client;
});
}
return services;
}
internal static IServiceCollection TryAddSwagger(this IServiceCollection services, bool useSwagger)
{
if (useSwagger)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Oqtane", Version = "v1" });
});
}
return services;
}
private static IServiceCollection AddOqtaneServices(this IServiceCollection services, Runtime runtime) private static IServiceCollection AddOqtaneServices(this IServiceCollection services, Runtime runtime)
{ {
if (services is null) if (services is null)
@ -95,7 +258,7 @@ namespace Microsoft.Extensions.DependencyInjection
} }
catch catch
{ {
Console.WriteLine($"Not Assembly : {dll.Name}"); Debug.WriteLine($"Oqtane Error: Cannot Get Assembly Name For {dll.Name}");
continue; continue;
} }
@ -136,24 +299,24 @@ namespace Microsoft.Extensions.DependencyInjection
} }
catch catch
{ {
Console.WriteLine($"Not Satellite Assembly : {assemblyFile.Name}"); Debug.WriteLine($"Oqtane Error: Cannot Get Satellite Assembly Name For {assemblyFile.Name}");
continue; continue;
} }
try try
{ {
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(assemblyFile.FullName))); Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(assemblyFile.FullName)));
Console.WriteLine($"Loaded : {assemblyName}"); Debug.WriteLine($"Oqtane Info: Loaded Assembly {assemblyName}");
} }
catch (Exception e) catch (Exception ex)
{ {
Console.WriteLine($"Failed : {assemblyName}\n{e}"); Debug.WriteLine($"Oqtane Error: Unable To Load Assembly {assemblyName} - {ex}");
} }
} }
} }
else else
{ {
Console.WriteLine($"The satellite assemblies folder named '{culture}' is not found."); Debug.WriteLine($"Oqtane Error: The Satellite Assembly Folder For {culture} Does Not Exist");
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -57,7 +58,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("Error modifying app settings {0}", ex); Debug.WriteLine($"Oqtane Error: Error Updating App Setting {key} - {ex}");
} }
} }
@ -78,7 +79,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("Error modifying app settings {0}", ex); Debug.WriteLine($"Oqtane Error: Error Removing App Setting {key} - {ex}");
} }
} }

View File

@ -483,11 +483,17 @@ namespace Oqtane.Infrastructure
{ {
tenantManager.SetTenant(tenant.TenantId); tenantManager.SetTenant(tenant.TenantId);
var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IInstallable; var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IInstallable;
moduleObject?.Install(tenant, versions[i]); if (moduleObject == null || !moduleObject.Install(tenant, versions[i]))
{
result.Message = "An Error Occurred Executing IInstallable Interface For " + moduleDefinition.ServerManagerType;
}
} }
else else
{ {
sql.ExecuteScript(tenant, moduleType.Assembly, Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql"); if (!sql.ExecuteScript(tenant, moduleType.Assembly, Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql"))
{
result.Message = "An Error Occurred Executing Database Script " + Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql";
}
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
@ -109,6 +110,16 @@ namespace Oqtane.Migrations.EntityBuilders
_migrationBuilder.AlterColumn<string>(RewriteName(name), RewriteName(EntityTableName), maxLength: length, nullable: nullable, unicode: unicode); _migrationBuilder.AlterColumn<string>(RewriteName(name), RewriteName(EntityTableName), maxLength: length, nullable: nullable, unicode: unicode);
} }
public void AddDecimalColumn(string name, int precision, int scale, bool nullable = false)
{
_migrationBuilder.AddColumn<decimal>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, precision: precision, scale: scale);
}
protected OperationBuilder<AddColumnOperation> AddDecimalColumn(ColumnsBuilder table, string name, int precision, int scale, bool nullable = false)
{
return table.Column<decimal>(name: RewriteName(name), nullable: nullable, precision: precision, scale: scale);
}
public void DropColumn(string name) public void DropColumn(string name)
{ {
_migrationBuilder.DropColumn(RewriteName(name), RewriteName(EntityTableName)); _migrationBuilder.DropColumn(RewriteName(name), RewriteName(EntityTableName));

View File

@ -1,10 +1,10 @@
using System; using System;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Enums; using Oqtane.Enums;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Modules namespace Oqtane.Modules
{ {
@ -28,9 +28,9 @@ namespace Oqtane.Modules
migrator.Migrate(); migrator.Migrate();
} }
} }
catch (Exception e) catch (Exception ex)
{ {
Console.WriteLine(e); Debug.WriteLine($"Oqtane Error: Error Executing Migration - {ex}");
result = false; result = false;
} }

View File

@ -34,13 +34,6 @@
<a class="dismiss">🗙</a> <a class="dismiss">🗙</a>
</div> </div>
@if (Model.Message != "")
{
<div class="app-alert">
@Model.Message
</div>
}
<script src="js/interop.js"></script> <script src="js/interop.js"></script>
@if (Model.Runtime == "WebAssembly") @if (Model.Runtime == "WebAssembly")

View File

@ -38,7 +38,6 @@ namespace Oqtane.Pages
public RenderMode RenderMode = RenderMode.Server; public RenderMode RenderMode = RenderMode.Server;
public string HeadResources = ""; public string HeadResources = "";
public string BodyResources = ""; public string BodyResources = "";
public string Message = "";
public void OnGet() public void OnGet()
{ {

View File

@ -1,9 +1,10 @@
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore; using Microsoft.AspNetCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using System.Diagnostics;
namespace Oqtane.Server namespace Oqtane.Server
{ {
@ -15,7 +16,11 @@ namespace Oqtane.Server
using (var serviceScope = host.Services.GetRequiredService<IServiceScopeFactory>().CreateScope()) using (var serviceScope = host.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{ {
var databaseManager = serviceScope.ServiceProvider.GetService<IDatabaseManager>(); var databaseManager = serviceScope.ServiceProvider.GetService<IDatabaseManager>();
databaseManager.Install(); var install = databaseManager.Install();
if (!string.IsNullOrEmpty(install.Message))
{
Debug.WriteLine($"Oqtane Error: {install.Message}");
}
} }
host.Run(); host.Run();
} }

View File

@ -45,15 +45,28 @@ namespace Oqtane.Repository
public Alias GetAlias(int aliasId) public Alias GetAlias(int aliasId)
{ {
return _db.Alias.Find(aliasId); return GetAlias(aliasId, true);
} }
public Alias GetAlias(string name) public Alias GetAlias(int aliasId, bool tracking)
{
if (tracking)
{
return _db.Alias.Find(aliasId);
}
else
{
return _db.Alias.AsNoTracking().FirstOrDefault(item => item.AliasId == aliasId);
}
}
// lookup alias based on url - note that alias values are hierarchical
public Alias GetAlias(string url)
{ {
Alias alias = null; Alias alias = null;
List<Alias> aliases = GetAliases().ToList(); List<Alias> aliases = GetAliases().ToList();
var segments = name.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); var segments = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
// iterate segments to find keywords // iterate segments to find keywords
int start = segments.Length; int start = segments.Length;

View File

@ -9,7 +9,8 @@ namespace Oqtane.Repository
Alias AddAlias(Alias alias); Alias AddAlias(Alias alias);
Alias UpdateAlias(Alias alias); Alias UpdateAlias(Alias alias);
Alias GetAlias(int aliasId); Alias GetAlias(int aliasId);
Alias GetAlias(string name); Alias GetAlias(int aliasId, bool tracking);
Alias GetAlias(string url);
void DeleteAlias(int aliasId); void DeleteAlias(int aliasId);
} }
} }

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Models; using Oqtane.Models;
namespace Oqtane.Repository namespace Oqtane.Repository
@ -9,6 +9,7 @@ namespace Oqtane.Repository
Job AddJob(Job job); Job AddJob(Job job);
Job UpdateJob(Job job); Job UpdateJob(Job job);
Job GetJob(int jobId); Job GetJob(int jobId);
Job GetJob(int jobId, bool tracking);
void DeleteJob(int jobId); void DeleteJob(int jobId);
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -48,6 +48,19 @@ namespace Oqtane.Repository
return _db.Job.Find(jobId); return _db.Job.Find(jobId);
} }
public Job GetJob(int jobId, bool tracking)
{
if (tracking)
{
return _db.Job.Find(jobId);
}
else
{
return _db.Job.AsNoTracking().FirstOrDefault(item => item.JobId == jobId);
}
}
public void DeleteJob(int jobId) public void DeleteJob(int jobId)
{ {
Job job = _db.Job.Find(jobId); Job job = _db.Job.Find(jobId);

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -264,7 +265,7 @@ namespace Oqtane.Repository
}.EncodePermissions(); }.EncodePermissions();
} }
Console.WriteLine($"Registering module: {moduledefinition.ModuleDefinitionName}"); Debug.WriteLine($"Oqtane Info: Registering Module {moduledefinition.ModuleDefinitionName}");
moduledefinitions.Add(moduledefinition); moduledefinitions.Add(moduledefinition);
index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType); index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -104,6 +105,8 @@ namespace Oqtane.Repository
{ {
theme.PackageName = Utilities.GetTypeName(theme.ThemeName); theme.PackageName = Utilities.GetTypeName(theme.ThemeName);
} }
Debug.WriteLine($"Oqtane Info: Registering Theme {theme.ThemeName}");
themes.Add(theme); themes.Add(theme);
index = themes.FindIndex(item => item.ThemeName == qualifiedThemeType); index = themes.FindIndex(item => item.ThemeName == qualifiedThemeType);
} }

View File

@ -1,36 +1,29 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
namespace Oqtane namespace Oqtane
{ {
public class Startup public class Startup
{ {
private Runtime _runtime; private readonly Runtime _runtime;
private bool _useSwagger; private readonly bool _useSwagger;
private IWebHostEnvironment _env; private readonly IWebHostEnvironment _env;
private string[] _supportedCultures; private readonly string[] _supportedCultures;
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
@ -61,77 +54,24 @@ namespace Oqtane
services.AddOptions<List<Database>>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection)); services.AddOptions<List<Database>>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection));
services.AddServerSideBlazor().AddCircuitOptions(options => services.AddServerSideBlazor()
{ .AddCircuitOptions(options =>
if (_env.IsDevelopment())
{ {
options.DetailedErrors = true; if (_env.IsDevelopment())
} {
}); options.DetailedErrors = true;
}
});
// setup HttpClient for server side in a client side compatible fashion ( with auth cookie ) // setup HttpClient for server side in a client side compatible fashion ( with auth cookie )
if (!services.Any(x => x.ServiceType == typeof(HttpClient))) services.TryAddHttpClientWithAuthenticationCookie();
{
services.AddScoped(s =>
{
// creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var navigationManager = s.GetRequiredService<NavigationManager>();
var client = new HttpClient(new HttpClientHandler { UseCookies = false });
client.BaseAddress = new Uri(navigationManager.Uri);
// set the cookies to allow HttpClient API calls to be authenticated
var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>();
foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies)
{
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value);
}
return client;
});
}
// register custom authorization policies // register custom authorization policies
services.AddAuthorizationCore(options => services.AddOqtaneAuthorizationPolicies();
{
options.AddPolicy(PolicyNames.ViewPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ListFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Browse)));
});
// register scoped core services // register scoped core services
services.AddScoped<SiteState>(); services.AddScoped<IAuthorizationHandler, PermissionHandler>()
services.AddScoped<IAuthorizationHandler, PermissionHandler>(); .AddOqtaneScopedServices();
services.AddScoped<IInstallationService, InstallationService>();
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
services.AddScoped<IThemeService, ThemeService>();
services.AddScoped<IAliasService, AliasService>();
services.AddScoped<ITenantService, TenantService>();
services.AddScoped<ISiteService, SiteService>();
services.AddScoped<IPageService, PageService>();
services.AddScoped<IModuleService, ModuleService>();
services.AddScoped<IPageModuleService, PageModuleService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IProfileService, ProfileService>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<IUserRoleService, UserRoleService>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IPackageService, PackageService>();
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<INotificationService, NotificationService>();
services.AddScoped<IFolderService, FolderService>();
services.AddScoped<IFileService, FileService>();
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
services.AddScoped<ISqlService, SqlService>();
services.AddScoped<ISystemService, SystemService>();
services.AddScoped<ILocalizationService, LocalizationService>();
services.AddScoped<ILanguageService, LanguageService>();
services.AddScoped<IDatabaseService, DatabaseService>();
services.AddScoped<ISyncService, SyncService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@ -141,44 +81,12 @@ namespace Oqtane
.AddDefaultTokenProviders() .AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<ClaimsPrincipalFactory<IdentityUser>>(); // role claims .AddClaimsPrincipalFactory<ClaimsPrincipalFactory<IdentityUser>>(); // role claims
services.Configure<IdentityOptions>(options => services.ConfigureOqtaneIdentityOptions();
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = false;
});
services.AddAuthentication(Constants.AuthenticationScheme) services.AddAuthentication(Constants.AuthenticationScheme)
.AddCookie(Constants.AuthenticationScheme); .AddCookie(Constants.AuthenticationScheme);
services.ConfigureApplicationCookie(options => services.ConfigureOqtaneCookieOptions();
{
options.Cookie.HttpOnly = false;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
};
options.Events.OnValidatePrincipal = PrincipalValidator.ValidateAsync;
});
services.AddAntiforgery(options => services.AddAntiforgery(options =>
{ {
@ -190,51 +98,18 @@ namespace Oqtane
}); });
// register singleton scoped core services // register singleton scoped core services
services.AddSingleton(Configuration); services.AddSingleton(Configuration)
services.AddSingleton<IInstallationManager, InstallationManager>(); .AddOqtaneSingletonServices();
services.AddSingleton<ISyncManager, SyncManager>();
services.AddSingleton<IDatabaseManager, DatabaseManager>();
services.AddSingleton<IConfigManager, ConfigManager>();
// install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain ) // install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain )
InstallationManager.InstallPackages(_env.WebRootPath, _env.ContentRootPath); InstallationManager.InstallPackages(_env.WebRootPath, _env.ContentRootPath);
// register transient scoped core services // register transient scoped core services
services.AddTransient<ITenantManager, TenantManager>(); services.AddOqtaneTransientServices();
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
services.AddTransient<IThemeRepository, ThemeRepository>();
services.AddTransient<IUserPermissions, UserPermissions>();
services.AddTransient<IAliasRepository, AliasRepository>();
services.AddTransient<ITenantRepository, TenantRepository>();
services.AddTransient<ISiteRepository, SiteRepository>();
services.AddTransient<IPageRepository, PageRepository>();
services.AddTransient<IModuleRepository, ModuleRepository>();
services.AddTransient<IPageModuleRepository, PageModuleRepository>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<IProfileRepository, ProfileRepository>();
services.AddTransient<IRoleRepository, RoleRepository>();
services.AddTransient<IUserRoleRepository, UserRoleRepository>();
services.AddTransient<IPermissionRepository, PermissionRepository>();
services.AddTransient<ISettingRepository, SettingRepository>();
services.AddTransient<ILogRepository, LogRepository>();
services.AddTransient<ILogManager, LogManager>();
services.AddTransient<ILocalizationManager, LocalizationManager>();
services.AddTransient<IJobRepository, JobRepository>();
services.AddTransient<IJobLogRepository, JobLogRepository>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddTransient<IFolderRepository, FolderRepository>();
services.AddTransient<IFileRepository, FileRepository>();
services.AddTransient<ISiteTemplateRepository, SiteTemplateRepository>();
services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<ILanguageRepository, LanguageRepository>();
// obsolete - replaced by ITenantManager
services.AddTransient<ITenantResolver, TenantResolver>();
// load the external assemblies into the app domain, install services // load the external assemblies into the app domain, install services
services.AddOqtane(_runtime, _supportedCultures); services.AddOqtane(_runtime, _supportedCultures);
services.AddDbContext<MasterDBContext>(options => { }); services.AddOqtaneDbContext();
services.AddDbContext<TenantDBContext>(options => { });
services.AddMvc() services.AddMvc()
@ -242,10 +117,7 @@ namespace Oqtane
.AddOqtaneApplicationParts() // register any Controllers from custom modules .AddOqtaneApplicationParts() // register any Controllers from custom modules
.ConfigureOqtaneMvc(); // any additional configuration from IStart classes. .ConfigureOqtaneMvc(); // any additional configuration from IStart classes.
if (_useSwagger) services.TryAddSwagger(_useSwagger);
{
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Oqtane", Version = "v1"}); });
}
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -6,25 +6,27 @@
@inherits ModuleBase @inherits ModuleBase
@inject I[Module]Service [Module]Service @inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td> <td>
<Label For="name" HelpText="Enter a name">Name: </Label> <Label For="name" HelpText="Enter a name" ResourceKey="Name">Name: </Label>
</td> </td>
<td> <td>
<input id="name" class="form-control" @bind="@_name" /> <input id="name" class="form-control" @bind="@_name" required />
</td> </td>
</tr> </tr>
</table> </table>
<button type="button" class="btn btn-success" @onclick="Save">Save</button> <button type="button" class="btn btn-success" @onclick="Save">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<br /> <br /><br />
<br />
@if (PageState.Action == "Edit") @if (PageState.Action == "Edit")
{ {
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo> <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
} }
</form>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
@ -38,12 +40,15 @@
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
}; };
int _id; private ElementReference form;
string _name; private bool validated = false;
string _createdby;
DateTime _createdon; private int _id;
string _modifiedby; private string _name;
DateTime _modifiedon; private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -66,7 +71,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading [Module] {[Module]Id} {Error}", _id, ex.Message); await logger.LogError(ex, "Error Loading [Module] {[Module]Id} {Error}", _id, ex.Message);
AddModuleMessage("Error Loading [Module]", MessageType.Error); AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
} }
} }
@ -74,7 +79,9 @@
{ {
try try
{ {
if (!string.IsNullOrEmpty(_name)) validated = true;
var interop = new Oqtane.UI.Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (PageState.Action == "Add") if (PageState.Action == "Add")
{ {
@ -95,13 +102,13 @@
} }
else else
{ {
AddModuleMessage("The Name Is Required", MessageType.Warning); AddModuleMessage(Localizer["Message.SaveValidation"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message); await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message);
AddModuleMessage("Error Saving [Module]", MessageType.Error); AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error);
} }
} }
} }

View File

@ -5,6 +5,7 @@
@inherits ModuleBase @inherits ModuleBase
@inject I[Module]Service [Module]Service @inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IStringLocalizer<Index> Localizer
@if (_[Module]s == null) @if (_[Module]s == null)
{ {
@ -12,7 +13,7 @@
} }
else else
{ {
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add [Module]" /> <ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add [Module]" ResourceKey="Add" />
<br /> <br />
<br /> <br />
@if (@_[Module]s.Count != 0) @if (@_[Module]s.Count != 0)
@ -21,18 +22,18 @@ else
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>Name</th> <th>@Localizer["Name"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.[Module]Id.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 The " + context.Name + " [Module]?")" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" /></td> <td><ActionDialog Header="Delete [Module]" Message="@("Are You Sure You Wish To Delete The " + context.Name + " [Module]?")" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" /></td>
<td>@context.Name</td> <td>@context.Name</td>
</Row> </Row>
</Pager> </Pager>
} }
else else
{ {
<p>No [Module]s To Display</p> <p>@Localizer["Message.DisplayNone"]</p>
} }
} }
@ -54,7 +55,7 @@ else
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading [Module] {Error}", ex.Message); await logger.LogError(ex, "Error Loading [Module] {Error}", ex.Message);
AddModuleMessage("Error Loading [Module]", MessageType.Error); AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
} }
} }
@ -70,7 +71,7 @@ else
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting [Module] {[Module]} {Error}", [Module], ex.Message); await logger.LogError(ex, "Error Deleting [Module] {[Module]} {Error}", [Module], ex.Message);
AddModuleMessage("Error Deleting [Module]", MessageType.Error); AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
} }
} }
} }

View File

@ -1,11 +1,12 @@
@namespace [Owner].[Module] @namespace [Owner].[Module]
@inherits ModuleBase @inherits ModuleBase
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<Settings> Localizer
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td> <td>
<Label For="value" HelpText="Enter a value">Name: </Label> <Label For="value" HelpText="Enter a value" ResourceKey="SettingName">Name: </Label>
</td> </td>
<td> <td>
<input id="value" type="text" class="form-control" @bind="@_value" /> <input id="value" type="text" class="form-control" @bind="@_value" />

View File

@ -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>

View File

@ -0,0 +1,138 @@
<?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="Edit" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Delete" xml:space="preserve">
<value>Delete</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>

View File

@ -0,0 +1,126 @@
<?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="SettingName.Text" xml:space="preserve">
<value>Name: </value>
</data>
<data name="SettingName.HelpText" xml:space="preserve">
<value>Enter a value</value>
</data>
</root>

View File

@ -11,14 +11,9 @@ namespace [Owner].[Module].Services
{ {
public class [Module]Service : ServiceBase, I[Module]Service, IService public class [Module]Service : ServiceBase, I[Module]Service, IService
{ {
private readonly SiteState _siteState; public [Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { }
public [Module]Service(HttpClient http, SiteState siteState) : base(http) private string Apiurl => CreateApiUrl("[Module]");
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("[Module]", _siteState.Alias);
public async Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId) public async Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId)
{ {

View File

@ -4,8 +4,10 @@
@using System.Net.Http @using System.Net.Http
@using System.Net.Http.Json @using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@using Microsoft.Extensions.Localization
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using Oqtane.Models @using Oqtane.Models
@ -18,4 +20,5 @@
@using Oqtane.Themes @using Oqtane.Themes
@using Oqtane.Themes.Controls @using Oqtane.Themes.Controls
@using Oqtane.UI @using Oqtane.UI
@using Oqtane.Enums @using Oqtane.Enums
@using Oqtane.Interfaces

View File

@ -7,6 +7,7 @@ using Oqtane.Enums;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using [Owner].[Module].Repository; using [Owner].[Module].Repository;
using Oqtane.Controllers; using Oqtane.Controllers;
using System.Net;
namespace [Owner].[Module].Controllers namespace [Owner].[Module].Controllers
{ {
@ -25,12 +26,15 @@ namespace [Owner].[Module].Controllers
[Authorize(Policy = PolicyNames.ViewModule)] [Authorize(Policy = PolicyNames.ViewModule)]
public IEnumerable<Models.[Module]> Get(string moduleid) public IEnumerable<Models.[Module]> Get(string moduleid)
{ {
if (int.Parse(moduleid) == _authEntityId[EntityNames.Module]) int ModuleId;
if (int.TryParse(moduleid, out ModuleId) && ModuleId == AuthEntityId(EntityNames.Module))
{ {
return _[Module]Repository.Get[Module]s(int.Parse(moduleid)); return _[Module]Repository.Get[Module]s(ModuleId);
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {ModuleId}", moduleid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null; return null;
} }
} }
@ -41,50 +45,75 @@ namespace [Owner].[Module].Controllers
public Models.[Module] Get(int id) public Models.[Module] Get(int id)
{ {
Models.[Module] [Module] = _[Module]Repository.Get[Module](id); Models.[Module] [Module] = _[Module]Repository.Get[Module](id);
if ([Module] != null && [Module].ModuleId != _authEntityId[EntityNames.Module]) if ([Module] != null && [Module].ModuleId != AuthEntityId(EntityNames.Module))
{ {
return [Module];
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {[Module]Id}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// POST api/<controller>
[ValidateAntiForgeryToken]
[HttpPost]
[Authorize(Policy = PolicyNames.EditModule)]
public Models.[Module] Post([FromBody] Models.[Module] [Module])
{
if (ModelState.IsValid && [Module].ModuleId == AuthEntityId(EntityNames.Module))
{
[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 [Module] Post Attempt {[Module]}", [Module]);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
[Module] = null; [Module] = null;
} }
return [Module]; return [Module];
} }
// POST api/<controller>
[HttpPost]
[Authorize(Policy = PolicyNames.EditModule)]
public Models.[Module] Post([FromBody] Models.[Module] [Module])
{
if (ModelState.IsValid && [Module].ModuleId == _authEntityId[EntityNames.Module])
{
[Module] = _[Module]Repository.Add[Module]([Module]);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "[Module] Added {[Module]}", [Module]);
}
return [Module];
}
// PUT api/<controller>/5 // PUT api/<controller>/5
[ValidateAntiForgeryToken]
[HttpPut("{id}")] [HttpPut("{id}")]
[Authorize(Policy = PolicyNames.EditModule)] [Authorize(Policy = PolicyNames.EditModule)]
public Models.[Module] Put(int id, [FromBody] Models.[Module] [Module]) public Models.[Module] Put(int id, [FromBody] Models.[Module] [Module])
{ {
if (ModelState.IsValid && [Module].ModuleId == _authEntityId[EntityNames.Module]) if (ModelState.IsValid && [Module].ModuleId == AuthEntityId(EntityNames.Module) && _[Module]Repository.Get[Module]([Module].[Module]Id, false) != null)
{ {
[Module] = _[Module]Repository.Update[Module]([Module]); [Module] = _[Module]Repository.Update[Module]([Module]);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "[Module] Updated {[Module]}", [Module]); _logger.Log(LogLevel.Information, this, LogFunction.Update, "[Module] Updated {[Module]}", [Module]);
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Put Attempt {[Module]}", [Module]);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
[Module] = null;
}
return [Module]; return [Module];
} }
// DELETE api/<controller>/5 // DELETE api/<controller>/5
[ValidateAntiForgeryToken]
[HttpDelete("{id}")] [HttpDelete("{id}")]
[Authorize(Policy = PolicyNames.EditModule)] [Authorize(Policy = PolicyNames.EditModule)]
public void Delete(int id) public void Delete(int id)
{ {
Models.[Module] [Module] = _[Module]Repository.Get[Module](id); Models.[Module] [Module] = _[Module]Repository.Get[Module](id);
if ([Module] != null && [Module].ModuleId == _authEntityId[EntityNames.Module]) if ([Module] != null && [Module].ModuleId == AuthEntityId(EntityNames.Module))
{ {
_[Module]Repository.Delete[Module](id); _[Module]Repository.Delete[Module](id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "[Module] Deleted {[Module]Id}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "[Module] Deleted {[Module]Id}", id);
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Delete Attempt {[Module]Id}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
} }
} }
} }

View File

@ -7,6 +7,7 @@ namespace [Owner].[Module].Repository
{ {
IEnumerable<Models.[Module]> Get[Module]s(int ModuleId); IEnumerable<Models.[Module]> Get[Module]s(int ModuleId);
Models.[Module] Get[Module](int [Module]Id); 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] Add[Module](Models.[Module] [Module]);
Models.[Module] Update[Module](Models.[Module] [Module]); Models.[Module] Update[Module](Models.[Module] [Module]);
void Delete[Module](int [Module]Id); void Delete[Module](int [Module]Id);

View File

@ -22,7 +22,19 @@ namespace [Owner].[Module].Repository
public Models.[Module] Get[Module](int [Module]Id) public Models.[Module] Get[Module](int [Module]Id)
{ {
return _db.[Module].Find([Module]Id); return Get[Module]([Module]Id, true);
}
public Models.[Module] Get[Module](int [Module]Id, bool tracking)
{
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]) public Models.[Module] Add[Module](Models.[Module] [Module])

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Loader; using System.Runtime.Loader;
@ -100,7 +101,7 @@ namespace System.Reflection
} }
catch catch
{ {
Console.WriteLine($"Not Assembly : {dll.Name}"); Debug.WriteLine($"Oqtane Error: Cannot Get Assembly Name For {dll.Name}");
} }
loadContext.LoadOqtaneAssembly(dll, assemblyName); loadContext.LoadOqtaneAssembly(dll, assemblyName);
@ -122,11 +123,11 @@ namespace System.Reflection
{ {
assembly = loadContext.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName))); assembly = loadContext.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)));
} }
Console.WriteLine($"Loaded : {assemblyName}"); Debug.WriteLine($"Oqtane Info: Loaded Assembly {assemblyName}");
} }
catch (Exception e) catch (Exception ex)
{ {
Console.WriteLine($"Failed : {assemblyName}\n{e}"); Debug.WriteLine($"Oqtane Error: Unable To Load Assembly {assemblyName} - {ex}");
} }
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Oqtane.Models namespace Oqtane.Models
@ -13,6 +13,7 @@ namespace Oqtane.Models
{ {
public string Name { get; set; } public string Name { get; set; }
public string Parent { get; set; } public string Parent { get; set; }
public int Order { get; set; }
public string Path { get; set; } public string Path { get; set; }
public string Icon { get; set; } public string Icon { get; set; }
public bool IsNavigation { get; set; } public bool IsNavigation { get; set; }

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Localization;
using Xunit;
namespace Oqtane.Oqtane.Client.Tests
{
public class LocalizationCookieTests
{
[Theory]
[InlineData("c=ar|uic=ar", "ar")]
[InlineData("c=ar", null)]
[InlineData("", null)]
[InlineData(null, null)]
public void ParseCookie(string localizationCookie, string expectedCulture)
{
// Arrange
var localizationCookieValue = CookieRequestCultureProvider.ParseCookieValue(localizationCookie);
// Act
var culture = localizationCookieValue?.UICultures?[0].Value;
// Assert
Assert.Equal(expectedCulture, culture);
}
}
}

View File

@ -18,7 +18,8 @@
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="bunit.web" Version="1.0.0-beta-10" /> <PackageReference Include="bunit.web" Version="1.0.0-beta-10" />
<PackageReference Include="bunit.xunit" Version="1.0.0-beta-10" /> <PackageReference Include="bunit.xunit" Version="1.0.0-beta-10" />

View File

@ -65,7 +65,6 @@ V.2.1.0 ( Jun 4, 2021 )
- [x] Centralize package installation and uninstall - [x] Centralize package installation and uninstall
- [x] Enable pre-rendering support for Blazor Server - [x] Enable pre-rendering support for Blazor Server
- [x] Allow run-time installation of Language packages - [x] Allow run-time installation of Language packages
- [x] Add support for Shared localization resources
V.2.0.2 ( Apr 19, 2021 ) V.2.0.2 ( Apr 19, 2021 )
- [x] Assorted fixes and user experience improvements - [x] Assorted fixes and user experience improvements