Merge branch 'dev' into dev

This commit is contained in:
gjwalk
2021-06-18 15:35:20 -04:00
committed by GitHub
67 changed files with 1925 additions and 1092 deletions

View File

@ -40,7 +40,14 @@
var interop = new Interop(JSRuntime);
SiteState.AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken);
_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;
StateHasChanged();
}

View File

@ -1,3 +1,5 @@
using Microsoft.Extensions.Localization;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Localization;
[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
@inject IPageService PageService
@inject IUserService UserService
@inject IStringLocalizer<SharedResources> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row">
@foreach (var p in _pages)
@ -12,7 +12,7 @@
string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center">
<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>
</div>
}

View File

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

View File

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

View File

@ -51,12 +51,11 @@ else
private void Edit(string name)
{
NavigationManager.NavigateTo(_scheme + name + "/admin/site", true);
NavigationManager.NavigateTo(_scheme + name + "/admin/site/?reload");
}
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);
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "reload"));
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
catch (Exception ex)
{

View File

@ -84,11 +84,21 @@ namespace Oqtane.Modules
return NavigateUrl(path, "");
}
public string NavigateUrl(bool refresh)
{
return NavigateUrl(PageState.Page.Path, refresh);
}
public string NavigateUrl(string path, string 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)
{
return EditUrl(ModuleState.ModuleId, action);

View File

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

View File

@ -8,13 +8,11 @@ using System.Net.Http;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Oqtane.Modules;
using Oqtane.Providers;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
@ -27,7 +25,8 @@ namespace Oqtane.Client
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
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.AddOptions();
@ -36,40 +35,10 @@ namespace Oqtane.Client
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<IdentityAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
builder.Services.AddOqtaneAuthorization();
// register scoped core services
builder.Services.AddScoped<SiteState>();
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>();
builder.Services.AddOqtaneScopedServices();
await LoadClientAssemblies(httpClient);
@ -77,38 +46,15 @@ namespace Oqtane.Client
foreach (var assembly in assemblies)
{
// dynamically register module 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}"));
builder.Services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
RegisterModuleServices(assembly, builder.Services);
// register client startup services
var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(builder.Services);
}
RegisterClientStartups(assembly, builder.Services);
}
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)))
{
culture = cultures.Single(c => c.IsDefault).Name;
}
SetCulture(culture);
await SetCultureFromLocalizationCookie(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)
{
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.Threading.Tasks;
@ -9,11 +9,5 @@ namespace Oqtane.Services
Task<List<JobLog>> GetJobLogsAsync();
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>
/// <returns></returns>
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}");
}
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}");
}
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.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane);
return NavigateUrl(url, "reload");
return NavigateUrl(url, true);
}
private async Task<string> DeleteModule(string url, PageModule pagemodule)
@ -123,7 +123,7 @@ namespace Oqtane.Themes.Controls
pagemodule.IsDeleted = true;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(url, "reload");
return NavigateUrl(url, true);
}
private async Task<string> Settings(string url, PageModule pagemodule)
@ -148,7 +148,7 @@ namespace Oqtane.Themes.Controls
}
pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions);
await ModuleService.UpdateModuleAsync(pagemodule.Module);
return NavigateUrl(s, "reload");
return NavigateUrl(s, true);
}
private async Task<string> Unpublish(string s, PageModule pagemodule)
@ -166,7 +166,7 @@ namespace Oqtane.Themes.Controls
}
pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions);
await ModuleService.UpdateModuleAsync(pagemodule.Module);
return NavigateUrl(s, "reload");
return NavigateUrl(s, true);
}
private async Task<string> MoveTop(string s, PageModule pagemodule)
@ -174,7 +174,7 @@ namespace Oqtane.Themes.Controls
pagemodule.Order = 0;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, "reload");
return NavigateUrl(s, true);
}
private async Task<string> MoveBottom(string s, PageModule pagemodule)
@ -182,7 +182,7 @@ namespace Oqtane.Themes.Controls
pagemodule.Order = int.MaxValue;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, "reload");
return NavigateUrl(s, true);
}
private async Task<string> MoveUp(string s, PageModule pagemodule)
@ -190,7 +190,7 @@ namespace Oqtane.Themes.Controls
pagemodule.Order -= 3;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, "reload");
return NavigateUrl(s, true);
}
private async Task<string> MoveDown(string s, PageModule pagemodule)
@ -198,7 +198,7 @@ namespace Oqtane.Themes.Controls
pagemodule.Order += 3;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, "reload");
return NavigateUrl(s, true);
}
public class ActionViewModel

View File

@ -10,6 +10,7 @@
@inject ILogService logger
@inject ISettingService SettingService
@inject IStringLocalizer<ControlPanel> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_moduleDefinitions != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{
@ -41,13 +42,13 @@
</div>
<div class="row">
<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 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 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>
<br />
@ -81,8 +82,8 @@
<p>Are You Sure You Want To Delete This Page?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" @onclick="DeletePage">@Localizer["Delete"]</button>
<button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@Localizer["Cancel"]</button>
<button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button>
<button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
@ -537,7 +538,7 @@
}
page.Permissions = UserSecurity.SetPermissionStrings(permissions);
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
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
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, "");
}
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)
{
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,
Page,

View File

@ -77,7 +77,7 @@
var action = Constants.DefaultAction;
var urlparameters = string.Empty;
var editmode = false;
var reload = Reload.None;
var refresh = UI.Refresh.None;
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
var runtime = GetRuntime();
@ -89,10 +89,19 @@
// parse querystring
var querystring = ParseQueryString(uri.Query);
// the reload parameter is used to reload the PageState
if (querystring.ContainsKey("reload"))
// the refresh parameter is used to refresh the PageState
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)
@ -104,23 +113,24 @@
// process any sync events
var sync = await SyncService.GetSyncAsync(lastsyncdate);
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 (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))
{
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);
reload = Reload.Site;
refresh = UI.Refresh.Site;
}
else
{
@ -129,7 +139,7 @@
if (site != null)
{
if (PageState == null || reload == Reload.Site)
if (PageState == null || refresh == UI.Refresh.Site)
{
// get user
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
@ -144,15 +154,15 @@
}
// 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))
{
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);
}
@ -169,7 +179,7 @@
path += "/";
}
if (SiteState.Alias.Path != "")
if (SiteState.Alias.Path != "" && path.StartsWith(SiteState.Alias.Path))
{
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
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));
}
@ -264,7 +274,7 @@
{
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);
}

View File

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