Compare commits

..

4 Commits

Author SHA1 Message Date
a6228f3704 Moduleinstall: finalized 2025-05-30 16:22:15 +02:00
572f2f6be1 Add Module 2025-05-30 16:20:08 +02:00
db63447052 Install 2025-05-30 16:18:51 +02:00
37c418a869 Publish directory 2025-05-30 16:15:48 +02:00
1653 changed files with 132543 additions and 13157 deletions

View File

@@ -0,0 +1,25 @@
name: build-docker-imge
on:
- push
jobs:
build:
name: Build the docker container
runs-on: ubuntu-latest
steps:
- name: "Git clone"
run: git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
- name: "Git checkout"
run: git checkout "${{ gitea.sha }}"
- uses: aevea/action-kaniko@master
name: Run Kaniko to build our api docker container.
with:
image: kocoded/oqtane.framework
tag: ${{ git.workflow_sha }}
tag_with_latest: github.ref == 'refs/heads/master'
registry: git.kocoder.xyz
username: ${{ secrets.CI_RUNNER_USER }}
password: ${{ secrets.CI_RUNNER_TOKEN }}
build_file: Dockerfile
target: deploy

View File

@@ -1,23 +0,0 @@
name: build-oqtane
on:
- push
jobs:
build:
name: Build the oqtane
runs-on: mcr.microsoft.com/dotnet/sdk:10.0-noble-amd64
steps:
- name: "Git clone"
run: git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
- name: "Git checkout"
run: git checkout "${{ gitea.sha }}"
- name: "Oqtane Framework bauen"
run: dotnet build -c Release ./oqtane.framework/Oqtane.slnx
- name: "Oqtane Framework publish"
run: dotnet publish -c Release ./oqtane.framework/Oqtane.slnx -o ./out
- name: Upload Package
uses: actions/upload-artifact@v4
with:
include-hidden-files: true
name: oqtane.framework-amd64
path: ./out

2
.gitignore vendored
View File

@@ -35,4 +35,4 @@ Oqtane.Server/wwwroot/Themes/*
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
!Oqtane.Server/wwwroot/Themes/Templates
Oqtane.Server/wwwroot/Themes/Templates/*
!Oqtane.Server/wwwroot/Themes/Templates/External
Oqtane.Server/wwwroot/Themes/Templates/External

View File

@@ -1,17 +0,0 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>10.0.3</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
<Description>CMS and Application Framework for Blazor and .NET MAUI</Description>
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.3</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
</PropertyGroup>
</Project>

View File

@@ -17,11 +17,8 @@ RUN dotnet publish "Oqtane.Server/Oqtane.Server.csproj" -c Release -o /source/pu
# Deploy
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS deploy
WORKDIR /codefiles
WORKDIR /app
COPY --from=publish /source/publish/ /codefiles/
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]
COPY --from=publish /source/publish/ /app/
ENTRYPOINT ["dotnet", "Oqtane.Server.dll"]

View File

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

View File

@@ -1,81 +0,0 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "Shaun Walker",
"classifications": [
"Web",
"ASP.NET",
"Blazor",
"Oqtane"
],
"name": "Oqtane Application Template",
"shortName": "oqtane-app",
"defaultName": "MyCompany.MyProject",
"identity": "Oqtane.Application.Template",
"tags": {
"language": "C#",
"type": "solution",
"editorTreatAs":"solution"
},
"sourceName": "Oqtane.Application",
"preferNameDirectory": true,
"symbols": {
"Framework": {
"type": "parameter",
"description": "The target framework for the project",
"datatype": "choice",
"choices": [
{
"choice": "net10.0",
"description": "Target net10.0"
}
],
"replaces": "net10.0",
"defaultValue": "net10.0"
},
"HttpPort": {
"type": "parameter",
"datatype": "integer",
"description": "Port number to use for the HTTP endpoint in launchSettings.json."
},
"HttpPortGenerated": {
"type": "generated",
"generator": "port"
},
"HttpPortReplacer": {
"type": "generated",
"generator": "coalesce",
"parameters": {
"sourceVariableName": "HttpPort",
"fallbackVariableName": "HttpPortGenerated"
},
"replaces": "44358"
},
"HttpsPort": {
"type": "parameter",
"datatype": "integer",
"description": "Port number to use for the HTTPS endpoint in launchSettings.json."
},
"HttpsPortGenerated": {
"type": "generated",
"generator": "port",
"parameters": {
"low": 44300,
"high": 44399
}
},
"HttpsPortReplacer": {
"type": "generated",
"generator": "coalesce",
"parameters": {
"sourceVariableName": "HttpsPort",
"fallbackVariableName": "HttpsPortGenerated"
},
"replaces": "44359"
}
},
"primaryOutputs": [
{
"path": "Oqtane.Application.slnx"
}
]
}

View File

@@ -1,3 +0,0 @@
using Microsoft.Extensions.Localization;
[assembly: RootNamespace("Oqtane.Application.Client")]

View File

@@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
<PublishTrimmed>false</PublishTrimmed>
<BlazorEnableCompression>false</BlazorEnableCompression>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Client" Version="10.0.3" />
</ItemGroup>
</Project>

View File

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

View File

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

View File

@@ -1,5 +0,0 @@
<Solution>
<Project Path="Server\Oqtane.Application.Server.csproj" DefaultStartup="true" />
<Project Path="Client\Oqtane.Application.Client.csproj" />
<Project Path="Shared\Oqtane.Application.Shared.csproj" />
</Solution>

View File

@@ -1,22 +0,0 @@
# Oqtane Application Template
This is a Visual Studio Project Template designed for Oqtane development projects. This template relies on the native templating capabilities of the .NET Command Line Interface (CLI):
```
dotnet new install Oqtane.Application.Template
dotnet new oqtane-app -o MyCompany.MyProject
cd MyCompany.MyProject
dotnet build
cd Server
dotnet run
browse to Url
```
When using this approach you do not need to have a local copy of the oqtane.framework source code - you simply utilize Oqtane as a standard application dependency.
The solution also contains Client, Server, and Shared folders which is where you you would implement your custom functionality. An example module and theme are included for reference, and you can add additional modules and themes within the same projects by following the standard Oqtane folder/namespace conventions.
*Known Issues*
- do not use the term "Oqtane" or "Module" in your output name or else you will experience namespace conflicts

View File

@@ -1,3 +0,0 @@
using Microsoft.Extensions.Localization;
[assembly: RootNamespace("Oqtane.Application.Server")]

View File

@@ -1,39 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName>
<PreserveCompilationContext>true</PreserveCompilationContext>
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
<CompressionEnabled>false</CompressionEnabled>
<StaticWebAssetsFingerprintContent>false</StaticWebAssetsFingerprintContent>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>
</PropertyGroup>
<ItemGroup>
<Compile Remove="wwwroot\Modules\Templates\**" />
<Compile Remove="wwwroot\Themes\Templates\**" />
<Content Remove="wwwroot\Modules\Templates\**" />
<Content Remove="wwwroot\Themes\Templates\**" />
<EmbeddedResource Remove="wwwroot\Modules\Templates\**" />
<EmbeddedResource Remove="wwwroot\Themes\Templates\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\Oqtane.Application.Client.csproj" />
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Server" Version="10.0.3" />
</ItemGroup>
</Project>

View File

@@ -1,55 +0,0 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Shared;
namespace Oqtane.Application.Server
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(builder.Environment.ContentRootPath, "Data"));
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(builder.Environment.ContentRootPath)
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true, true)
.AddEnvironmentVariables();
var configuration = configurationBuilder.Build();
builder.Services.AddOqtane(configuration, builder.Environment);
var app = builder.Build();
var corsService = app.Services.GetRequiredService<ICorsService>();
var corsPolicyProvider = app.Services.GetRequiredService<ICorsPolicyProvider>();
var syncManager = app.Services.GetRequiredService<ISyncManager>();
app.UseOqtane(configuration, builder.Environment, corsService, corsPolicyProvider, syncManager);
var databaseManager = app.Services.GetService<IDatabaseManager>();
var install = databaseManager.Install();
if (!string.IsNullOrEmpty(install.Message))
{
var filelogger = app.Services.GetRequiredService<ILogger<Program>>();
if (filelogger != null)
{
filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}");
}
}
else
{
app.Run();
}
}
}
}

View File

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

View File

@@ -1,27 +0,0 @@
using System.Collections.Generic;
using Oqtane.Models;
using Oqtane.Themes;
using Oqtane.Shared;
namespace [Owner].Theme.[Theme]
{
public class ThemeInfo : ITheme
{
public Oqtane.Models.Theme Theme => new Oqtane.Models.Theme
{
Name = "[Owner] [Theme]",
Version = "1.0.0",
PackageName = "[Owner].Theme.[Theme]",
ThemeSettingsType = "[Owner].Theme.[Theme].ThemeSettings, [Owner].Theme.[Theme].Client.Oqtane",
ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane",
Resources = new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
new Stylesheet("~/Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
}
};
}
}

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@
{
string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center my-3">
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All" @attributes="_attributes">
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
</NavLink>
@@ -24,19 +24,13 @@
}
@code {
Dictionary<string, object> _attributes { get; set; } = new();
private List<Page> _pages;
private List<Page> _pages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override string RenderMode => RenderModes.Static;
protected override void OnInitialized()
{
if (PageState.RenderMode == RenderModes.Static && !PageState.Site.EnhancedNavigation)
{
_attributes.Add("data-enhance-nav", "true"); // Admin Dashboard utilizes enhanced navigation
}
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null)
{

View File

@@ -56,7 +56,7 @@
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
</div>
<div class="col">
<input id="starting" type="time" class="form-control" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" />
<input id="starting" type="time" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
</div>
</div>
</div>
@@ -69,7 +69,7 @@
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
</div>
<div class="col">
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" required="@(_endDate.HasValue)" />
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
</div>
</div>
</div>
@@ -82,7 +82,7 @@
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
</div>
<div class="col">
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" required="@(_nextDate.HasValue)" />
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
</div>
</div>
</div>
@@ -176,18 +176,10 @@
{
job.Interval = int.Parse(_interval);
}
job.StartDate = _startDate.HasValue && _startTime.HasValue
? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay))
: null;
job.EndDate = _endDate.HasValue && _endTime.HasValue
? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay))
: null;
job.NextExecution = _nextDate.HasValue && _nextTime.HasValue
? LocalToUtc(_nextDate.GetValueOrDefault().Date.Add(_nextTime.GetValueOrDefault().TimeOfDay))
: null;
job.RetentionHistory = int.Parse(_retentionHistory);
job.StartDate = LocalToUtc(_startDate.Value.Date.Add(_startTime.Value.TimeOfDay));
job.EndDate = LocalToUtc(_endDate.Value.Date.Add(_endTime.Value.TimeOfDay));
job.RetentionHistory = int.Parse(_retentionHistory);
job.NextExecution = LocalToUtc(_nextDate.Value.Date.Add(_nextTime.Value.TimeOfDay));
try
{
@@ -206,4 +198,5 @@
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
}
}
}

View File

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

View File

@@ -14,133 +14,85 @@
}
else
{
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
@switch (_action)
{
case "Login":
@if (_allowexternallogin)
{
<button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
<hr class="app-rule mt-3 mb-2" />
}
@if (_allowsitelogin)
{
<div class="form-group text-center">
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
</div>
<div class="form-group text-center mt-2">
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
<div class="input-group">
<input id="password" type="@_passwordtype" @ref="password" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
@if (!twofactor)
{
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
@if (_allowexternallogin)
{
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
<br />
<br />
}
@if (_allowsitelogin)
{
<div class="form-group">
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
</div>
<div class="form-group mt-2">
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
<div class="input-group">
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
<div class="form-group mt-2">
@if (!_alwaysremember)
{
<div class="form-group text-center mt-2">
<div>
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Stay Signed In?</Label>
</div>
<div class="form-check">
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
</div>
}
</div>
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<br />
<div class="btn-group mt-2 col-12" role="group">
<button type="button" class="btn btn-primary col-6" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary col-6" @onclick="CancelLogin">@SharedLocalizer["Cancel"]</button>
</div>
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotPassword"))">@Localizer["ForgotPassword"]</button>
}
@if (_allowloginlink)
{
<hr class="app-rule mt-3" />
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="@(() => SetAction("LoginLink"))">@Localizer["UseLoginLink"]</button>
}
@if (_allowpasskeys)
{
<hr class="app-rule mt-3" />
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="PasskeyLogin">@Localizer["Passkey"]</button>
}
<br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
@if (PageState.Site.AllowRegistration)
{
<hr class="app-rule mt-3" />
<div class="text-center mt-2">
<NavLink href="@_registerurl">@Localizer["Register"]</NavLink>
</div>
<br />
<br />
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
}
break;
case "ForgotPassword":
<div class="form-group text-center">
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
<input id="username" type="text" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
</div>
<div class="btn-group mt-4 col-12" role="group">
<button type="button" class="btn btn-primary col-6" @onclick="ForgotPassword">@SharedLocalizer["Send"]</button>
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
</div>
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotUsername"))">@Localizer["ForgotUsername"]</button>
break;
case "ForgotUsername":
<div class="form-group text-center">
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
</div>
<div class="btn-group mt-4 col-12" role="group">
<button type="button" class="btn btn-primary col-6" @onclick="ForgotUsername">@SharedLocalizer["Send"]</button>
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
</div>
break;
case "LoginLink":
<div class="form-group text-center">
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
</div>
<div class="btn-group mt-4 col-12" role="group">
<button type="button" class="btn btn-primary col-6" @onclick="LoginLink">@SharedLocalizer["Send"]</button>
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
</div>
break;
case "TwoFactor":
<div class="form-group">
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
</div>
<div class="btn-group mt-4 col-12" role="group">
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
</div>
break;
}
</div>
</form>
}
</div>
</form>
}
else
{
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container Oqtane-Modules-Admin-Login">
<div class="form-group">
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
</div>
<br />
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
</div>
</form>
}
}
@code {
private string _action = "Login";
private bool _allowexternallogin = false;
private bool _allowsitelogin = true;
private bool _allowloginlink = false;
private bool _allowpasskeys = false;
private string _returnurl = string.Empty;
private bool _allowexternallogin = false;
private ElementReference login;
private bool validated = false;
private bool twofactor = false;
private string _username = string.Empty;
private ElementReference username;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private ElementReference password;
private bool _remember = false;
private bool _alwaysremember = false;
private string _registerurl = string.Empty;
private string _email = string.Empty;
private string _code = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
@@ -148,7 +100,7 @@ else
public override List<Resource> Resources => new List<Resource>()
{
new Stylesheet(ModulePath() + "Module.css")
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
protected override async Task OnInitializedAsync()
@@ -157,22 +109,8 @@ else
{
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
_allowloginlink = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:LoginLink", "false"));
_allowpasskeys = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false"));
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
if (!string.IsNullOrEmpty(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "")))
{
_registerurl = SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "");
}
else
{
_registerurl = NavigateUrl("register");
}
// PageState.ReturnUrl is not specified if user navigated directly to login page
_returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
_togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.QueryString.ContainsKey("name"))
@@ -188,7 +126,7 @@ else
if (PageState.QueryString.ContainsKey("key"))
{
user = await UserService.AddLoginAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
@@ -220,7 +158,7 @@ else
{
if (PageState.QueryString.ContainsKey("status"))
{
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Warning);
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
}
}
}
@@ -231,45 +169,20 @@ else
}
}
private async Task KeyPressed(KeyboardEventArgs e)
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
if (firstRender && PageState.User == null && _allowsitelogin)
{
switch (_action)
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
{
case "Login":
await Login();
break;
await username.FocusAsync();
}
}
}
private void SetAction(string action)
{
_action = action;
_username = "";
_password = "";
_email = "";
ClearModuleMessage();
StateHasChanged();
}
private void ExternalLogin()
{
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(_returnurl)), true);
}
private void TogglePassword()
{
if (_passwordtype == "password")
// redirect logged in user to specified page
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
}
@@ -284,7 +197,7 @@ else
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (_action == "Login")
if (!twofactor)
{
_remember = _alwaysremember || _remember;
user = await UserService.LoginUserAsync(user, hybrid, _remember);
@@ -298,17 +211,20 @@ else
{
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
// return url is not specified if user navigated directly to login page
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
if (hybrid)
{
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(_returnurl, true));
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
}
else
{
// post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(_returnurl) };
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields);
}
@@ -317,13 +233,13 @@ else
{
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
{
_action = "TwoFactor";
twofactor = true;
validated = false;
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
}
else
{
if (_action != "TwoFactor")
if (!twofactor)
{
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
@@ -348,30 +264,23 @@ else
}
}
private void CancelLogin()
private void Cancel()
{
NavigationManager.NavigateTo(_returnurl);
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
private async Task PasskeyLogin()
{
// post back to the Passkey page so that the cookies are set correctly
var interop = new Interop(JSRuntime);
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = _returnurl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
await interop.SubmitForm(url, fields);
}
private async Task ForgotPassword()
private async Task Forgot()
{
try
{
if (!string.IsNullOrEmpty(_username))
if (_username != string.Empty)
{
if (await UserService.ForgotPasswordAsync(_username))
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
}
else
{
@@ -380,8 +289,10 @@ else
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
}
StateHasChanged();
}
catch (Exception ex)
{
@@ -390,114 +301,40 @@ else
}
}
private async Task ForgotUsername()
private void Reset()
{
try
twofactor = false;
_username = "";
_password = "";
ClearModuleMessage();
StateHasChanged();
}
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
if (!string.IsNullOrEmpty(_email))
{
if (await UserService.ForgotUsernameAsync(_email))
{
AddModuleMessage(Localizer["Message.ForgotUsername"], MessageType.Info);
await logger.LogInformation(LogFunction.Security, "Username Reminder Notification Sent For Email {Email}", _email);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Sending Username Reminder {Error}", ex.Message);
AddModuleMessage(Localizer["Error.ForgotUsername"], MessageType.Error);
await Login();
}
}
private async Task LoginLink()
private void TogglePassword()
{
try
if (_passwordtype == "password")
{
if (!string.IsNullOrEmpty(_email))
{
if (await UserService.SendLoginLinkAsync(_email, _returnurl))
{
AddModuleMessage(Localizer["Message.SendLoginLink"], MessageType.Info);
await logger.LogInformation(LogFunction.Security, "Login Link Sent To Email {Email}", _email);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Sending Login Link {Error}", ex.Message);
AddModuleMessage(Localizer["Error.SendLoginLink"], MessageType.Error);
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
private void ExternalLogin()
{
if (firstRender && PageState.QueryString.ContainsKey("options"))
{
// user has initiated a passkey login
try
{
var interop = new Interop(JSRuntime);
var credential = await interop.RequestCredential(WebUtility.UrlDecode(PageState.QueryString["options"]));
if (!string.IsNullOrEmpty(credential))
{
// post back to the Passkey page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = _returnurl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
await interop.SubmitForm(url, fields);
}
else
{
await logger.LogError("Passkey Login Was Not Successful");
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Passkey Login Was Not Successful");
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
}
return;
}
if (firstRender && PageState.User == null && _allowsitelogin && _action == "Login")
{
if (string.IsNullOrEmpty(_username))
{
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
{
await username.FocusAsync();
}
}
else
{
if (!string.IsNullOrEmpty(password.Id)) // ensure password is visible in UI
{
await password.FocusAsync();
}
}
}
// redirect logged in user to specified page
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
NavigationManager.NavigateTo(_returnurl);
}
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
}
}

View File

@@ -101,20 +101,13 @@
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<br />
@if (_moduledefinitions.Exists(item => item.PackageName == context.PackageId))
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
else
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
@@ -178,7 +171,6 @@
@code {
private bool _initialized = false;
private List<ModuleDefinition> _moduledefinitions;
private int _page = 1;
private List<Package> _packages;
private string _price = "free";
@@ -195,8 +187,7 @@
{
try
{
_moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
await LoadPackages();
await LoadModuleDefinitions();
_initialized = true;
}
catch (Exception ex)
@@ -206,10 +197,24 @@
}
}
private async Task LoadPackages()
private async Task LoadModuleDefinitions()
{
ShowProgressIndicator();
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
HideProgressIndicator();
}
@@ -217,25 +222,25 @@
{
_price = price;
_sort = "popularity";
await LoadPackages();
await LoadModuleDefinitions();
StateHasChanged();
}
private async Task Search()
{
await LoadPackages();
await LoadModuleDefinitions();
}
private async Task Reset()
{
_page = 1;
_search = "";
await LoadPackages();
await LoadModuleDefinitions();
}
private async Task Refresh()
{
await LoadPackages();
await LoadModuleDefinitions();
}
private void OnPageChange(int page)
@@ -246,7 +251,7 @@
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadPackages();
await LoadModuleDefinitions();
}
private void HideModal()
@@ -305,6 +310,6 @@
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Module.Upload"], NavigateUrl("admin/system")), MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Admin.ModuleDefinitions
@inherits ModuleBase
@using System.Text.RegularExpressions
@using System.Reflection
@inject NavigationManager NavigationManager
@inject IModuleDefinitionService ModuleDefinitionService
@inject IModuleService ModuleService
@@ -43,32 +42,29 @@
</select>
</div>
</div>
@if (_type == "External")
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<div class="col-sm-9">
<select id="reference" class="form-select" @bind="@_reference" required>
@foreach (string version in _versions)
{
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
{
<option value="@(version)">@(version)</option>
}
}
<option value="local">@SharedLocalizer["LocalVersion"]</option>
</select>
</div>
</div>
@if (!string.IsNullOrEmpty(_location))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<select id="reference" class="form-select" @bind="@_reference" required>
@foreach (string version in _versions)
{
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
{
<option value="@(version)">@(version)</option>
}
}
<option value="local">@SharedLocalizer["LocalVersion"]</option>
</select>
<input id="module" class="form-control" @bind="@_location" readonly />
</div>
</div>
@if (!string.IsNullOrEmpty(_location))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_location" readonly />
</div>
</div>
}
}
</div>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
@@ -84,10 +80,9 @@
private string _description = string.Empty;
private List<Template> _templates;
private string _template = "-";
private string _minversion = "2.0.0";
private string _type = "";
private string[] _versions;
private string _reference = "local";
private string _minversion = "2.0.0";
private string _location = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@@ -98,16 +93,6 @@
{
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
}
else
{
var entryAssemblyName = Assembly.GetEntryAssembly().GetName().Name;
if (entryAssemblyName.EndsWith(".Oqtane"))
{
// Oqtane Application assemblies end with .Server.Oqtane or .Client.Oqtane
string[] segments = entryAssemblyName.Split('.');
_owner = string.Join(".", segments, 0, segments.Length - 2);
}
}
}
protected override async Task OnParametersSetAsync()
@@ -138,18 +123,11 @@
if (string.IsNullOrEmpty(_description)) _description = _module;
if (IsValidXML(_description))
{
if (_type == "Internal")
{
AddModuleMessage(Localizer["Success.Module.Create.Internal"], MessageType.Success);
}
var template = _templates.FirstOrDefault(item => item.Name == _template);
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
if (_type == "External")
{
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
}
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
@@ -175,7 +153,7 @@
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$");
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private bool IsValidXML(string description)
@@ -187,16 +165,11 @@
private void TemplateChanged(ChangeEventArgs e)
{
_template = (string)e.Value;
_minversion = "2.0.0";
if (_template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
_type = template.Type;
}
else
{
_minversion = "2.0.0";
_type = "";
}
GetLocation();
}

View File

@@ -14,7 +14,7 @@
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Module" ResourceKey="Module" Heading="Module">
<TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
@@ -236,11 +236,12 @@
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private PermissionGrid _permissionGrid;
private List<Page> _pagesWithModules;
#pragma warning disable 649
private PermissionGrid _permissionGrid;
#pragma warning restore 649
private List<Package> _packages;
private List<Language> _languages;
private Package _package;

View File

@@ -17,8 +17,8 @@ else
<div class="row mb-3 align-items-center">
<div class="col-sm-6">
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ms-1" />
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
</div>
<div class="col-sm-6">
<select class="form-select" @onchange="(e => CategoryChanged(e))">

View File

@@ -16,7 +16,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
</div>
</div>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
@@ -81,9 +81,21 @@
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
<div class="col-sm-9">
<select id="clickable" class="form-select" @bind="@_isclickable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
@@ -98,6 +110,27 @@
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-8">
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
</div>
<div class="col-sm-1">
<i class="@_icon"></i>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<div class="col-sm-9">
@@ -108,8 +141,15 @@
</div>
</div>
</div>
<Section Name="Theme" Heading="Theme" ResourceKey="Theme">
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
@@ -141,49 +181,6 @@
</div>
</div>
</Section>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-8">
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
</div>
<div class="col-sm-1">
<i class="@_icon"></i>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<div class="col-sm-9">
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
<div class="col-sm-9">
<select id="clickable" class="form-select" @bind="@_isclickable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
</div>
</div>
</div>
</Section>
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
<div class="container">
<div class="row mb-1 align-items-center">
@@ -272,16 +269,8 @@
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
{
_themetype = PageState.Site.DefaultThemeType;
var themes = new List<Theme>();
foreach (var theme in PageState.Site.Themes)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
{
themes.Add(theme);
}
}
_themes = ThemeService.GetThemeControls(themes);
_containers = ThemeService.GetContainerControls(themes, _themetype);
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_children = new List<Page>();
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))

View File

@@ -22,7 +22,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
</div>
</div>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
@@ -98,9 +98,21 @@
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
<div class="col-sm-9">
<select id="clickable" class="form-select" @bind="@_isclickable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
@@ -115,6 +127,27 @@
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-8">
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
</div>
<div class="col-sm-1">
<i class="@_icon"></i>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<div class="col-sm-9">
@@ -125,8 +158,14 @@
</div>
</div>
</div>
<Section Name="Theme" ResourceKey="Theme" Heading="Theme">
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
@@ -161,49 +200,6 @@
</div>
</div>
</Section>
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-8">
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
</div>
<div class="col-sm-1">
<i class="@_icon"></i>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<div class="col-sm-9">
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
<div class="col-sm-9">
<select id="clickable" class="form-select" @bind="@_isclickable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
</div>
</div>
</div>
</Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
<div class="container">
<div class="row mb-1 align-items-center">
@@ -447,16 +443,8 @@
{
_themetype = PageState.Site.DefaultThemeType;
}
var themes = new List<Theme>();
foreach (var theme in PageState.Site.Themes)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
{
themes.Add(theme);
}
}
_themes = ThemeService.GetThemeControls(themes);
_containers = ThemeService.GetContainerControls(themes, _themetype);
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _page.DefaultContainerType;
if (string.IsNullOrEmpty(_containertype))
{

View File

@@ -2,7 +2,6 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IProfileService ProfileService
@inject ISettingService SettingService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@@ -57,25 +56,9 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from Settings." ResourceKey="Options">Options: </Label>
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options the user can select from" ResourceKey="Options">Options: </Label>
<div class="col-sm-9">
<div class="input-group">
@if (_optiontype == "Settings")
{
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
}
else
{
<select id="entityName" class="form-select" @bind="@_options">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var entityname in _entitynames)
{
<option value="@entityname">@entityname</option>
}
</select>
}
<button type="button" class="btn btn-secondary" @onclick="ToggleOptionType">@Localizer[_optiontype]</button>
</div>
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
</div>
</div>
<div class="row mb-1 align-items-center">
@@ -112,7 +95,7 @@
<br />
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@if (PageState.QueryString.ContainsKey("id"))
@if (PageState.QueryString.ContainsKey("id"))
{
<br />
<br />
@@ -133,8 +116,6 @@
private string _rows = "1";
private string _defaultvalue = string.Empty;
private string _options = string.Empty;
private string _optiontype = "Settings";
private List<string> _entitynames;
private string _validation = string.Empty;
private string _autocomplete = string.Empty;
private string _isrequired = "False";
@@ -152,8 +133,6 @@
{
try
{
_entitynames = await SettingService.GetEntityNamesAsync();
if (PageState.QueryString.ContainsKey("id"))
{
_profileid = Int32.Parse(PageState.QueryString["id"]);
@@ -169,11 +148,6 @@
_rows = profile.Rows.ToString();
_defaultvalue = profile.DefaultValue;
_options = profile.Options;
if (_options.StartsWith("EntityName:"))
{
_optiontype = "Options";
_options = _options.Substring(11);
}
_validation = profile.Validation;
_autocomplete = profile.Autocomplete;
_isrequired = profile.IsRequired.ToString();
@@ -192,18 +166,6 @@
}
}
private void ToggleOptionType()
{
if (_optiontype == "Options")
{
_optiontype = "Settings";
}
else
{
_optiontype = "Options";
}
}
private async Task SaveProfile()
{
validated = true;
@@ -231,14 +193,7 @@
profile.MaxLength = int.Parse(_maxlength);
profile.Rows = int.Parse(_rows);
profile.DefaultValue = _defaultvalue;
if (_optiontype == "Options" && !string.IsNullOrEmpty(_options))
{
profile.Options = "EntityName:" + _options;
}
else
{
profile.Options = _options;
}
profile.Options = _options;
profile.Validation = _validation;
profile.Autocomplete = _autocomplete;
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));

View File

@@ -3,10 +3,10 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject ITimeZoneService TimeZoneService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject ISettingService SettingService
@inject ITimeZoneService TimeZoneService
@if (_initialized)
{
@@ -115,7 +115,7 @@
{
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
_timezones = TimeZoneService.GetTimeZones();
_timezones = await TimeZoneService.GetTimeZonesAsync();
_timezoneid = PageState.Site.TimeZoneId;
_initialized = true;
}

View File

@@ -40,16 +40,13 @@
{
<Pager Items="@_searchResults?.Results"
Format="Grid"
PageSize="@_pageSize"
DisplayPages="@_displayPages"
CurrentPage="@_currentPage"
Columns="1"
Toolbar="Bottom"
Parameters="@($"q={_keywords}")">
<Row>
<div class="search-item mb-2">
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
<p class="mb-0 text-body-secondary">@((MarkupString)context.Snippet)</p>
<p class="mb-0 text-muted">@((MarkupString)context.Snippet)</p>
</div>
</Row>
</Pager>
@@ -69,7 +66,6 @@
@code {
public override string RenderMode => RenderModes.Static;
private const string SearchDefaultPageSize = "10";
private string _includeEntities;
private string _excludeEntities;
@@ -79,8 +75,6 @@
private string _sortField;
private string _sortOrder;
private string _bodyLength;
private string _currentPage = "0";
private string _displayPages = "7";
private string _keywords;
private SearchResults _searchResults;
@@ -95,16 +89,11 @@
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString());
_toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString());
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", SearchDefaultPageSize);
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", int.MaxValue.ToString());
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
if (PageState.QueryString.ContainsKey("p"))
{
_currentPage = PageState.QueryString["p"];
}
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
{
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
@@ -133,7 +122,7 @@
ExcludeEntities = _excludeEntities,
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
PageSize = int.MaxValue,
PageSize = (!string.IsNullOrEmpty(_pageSize)) ? int.Parse(_pageSize) : int.MaxValue,
PageIndex = 0,
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending,

View File

@@ -1,226 +0,0 @@
@namespace Oqtane.Modules.Admin.Settings
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ISettingService SettingService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
<div class="col-sm-9">
<div class="input-group">
@if (_entityNameElement == "input")
{
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" required />
}
else
{
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
<option value="-">&lt;@Localizer["Select Entity"]&gt;</option>
@foreach (var entityName in _entityNames)
{
<option value="@entityName">@entityName</option>
}
</select>
}
<button type="button" class="btn btn-secondary" @onclick="@EntityNameClicked" tabindex="-1">@_entityNameTitle</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
<div class="col-sm-9">
<div class="input-group">
@if (_entityIdElement == "input")
{
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" required />
}
else
{
<select class="form-select custom-select" @bind="@_entityId">
<option value="-">&lt;@Localizer["Select Id"]&gt;</option>
@foreach (var entityId in _entityIds)
{
<option value="@entityId">@entityId</option>
}
</select>
}
<button type="button" class="btn btn-secondary" @onclick="@EntityIdClicked" tabindex="-1">@_entityIdTitle</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
<div class="col-sm-9">
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
<div class="col-sm-9">
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
<div class="col-sm-9">
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
</div>
</form>
@code {
private ElementReference form;
private bool validated = false;
private string _entityName = "-";
private List<string> _entityNames = new List<string>();
private string _entityNameElement = "select";
private string _entityNameTitle = "";
private string _entityId = "-";
private List<int> _entityIds = new List<int>();
private string _entityIdElement = "select";
private string _entityIdTitle = "";
private string _settingName = "";
private string _settingValue = "";
private string _isPrivate = "True";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_entityNameTitle = Localizer["Input"];
_entityIdTitle = Localizer["Input"];
// default entity names
_entityNames.Add(EntityNames.Host);
_entityNames.Add(EntityNames.Job);
_entityNames.Add(EntityNames.ModuleDefinition);
_entityNames.Add(EntityNames.Theme);
_entityNames.Add(EntityNames.Tenant);
_entityNames.Add(EntityNames.Site);
_entityNames.Add(EntityNames.Role);
_entityNames.Add(EntityNames.Page);
_entityNames.Add(EntityNames.Module);
_entityNames.Add(EntityNames.Folder);
_entityNames.Add(EntityNames.User);
_entityNames.Add(EntityNames.Visitor);
// custom entity names
var entityNames = await SettingService.GetEntityNamesAsync();
foreach (var entityName in entityNames)
{
if (!_entityNames.Contains(entityName))
{
_entityNames.Add(entityName);
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Setting {Error}", ex.Message);
AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error);
}
}
private void EntityNameClicked()
{
if (_entityNameElement == "select")
{
_entityName = "";
_entityNameElement = "input";
_entityNameTitle = Localizer["Select"];
_entityId = "";
_entityIdElement = "input";
_entityIdTitle = Localizer["Select"];
}
else
{
_entityName = "-";
_entityNameElement = "select";
_entityNameTitle = Localizer["Input"];
}
}
private void EntityIdClicked()
{
if (_entityIdElement == "select")
{
_entityId = "";
_entityIdElement = "input";
_entityIdTitle = Localizer["Select"];
}
else
{
_entityId = "-";
_entityIdElement = "select";
_entityIdTitle = Localizer["Input"];
}
}
private async void EntityNameChanged(ChangeEventArgs e)
{
try
{
_entityName = e.Value.ToString();
_entityId = "-";
_entityIdElement = "select";
_entityIdTitle = Localizer["Input"];
if (_entityName != "-")
{
_entityIds = await SettingService.GetEntityIdsAsync(_entityName);
}
else
{
_entityIds = new List<int>();
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On EntityNameChanged");
}
}
private async Task SaveSetting()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form) && _entityName != "-" && int.TryParse(_entityId, out int entityId))
{
var setting = new Setting();
setting.EntityName = _entityName;
setting.EntityId = entityId;
setting.SettingName = _settingName;
setting.SettingValue = _settingValue;
setting.IsPrivate = (bool.Parse(_isPrivate));
try
{
setting = await SettingService.AddSettingAsync(setting);
await logger.LogInformation("Setting Saved {Setting}", setting);
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message);
AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@@ -1,122 +0,0 @@
@namespace Oqtane.Modules.Admin.Settings
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ISettingService SettingService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
<div class="col-sm-9">
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
<div class="col-sm-9">
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
<div class="col-sm-9">
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
<div class="col-sm-9">
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
<div class="col-sm-9">
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
<br /><br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</div>
</form>
@code {
private ElementReference form;
private bool validated = false;
private int _settingId;
private string _entityName;
private string _entityId;
private string _settingName;
private string _settingValue;
private string _isPrivate;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
_settingId = int.Parse(PageState.QueryString["id"]);
_entityName = PageState.QueryString["entity"];
try
{
var setting = await SettingService.GetSettingAsync(_entityName, _settingId);
if (setting != null)
{
_entityId = setting.EntityId.ToString();
_settingName = setting.SettingName;
_settingValue = setting.SettingValue;
_isPrivate = setting.IsPrivate.ToString();
_createdby = setting.CreatedBy;
_createdon = setting.CreatedOn;
_modifiedby = setting.ModifiedBy;
_modifiedon = setting.ModifiedOn;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Setting {SettingId} {Error}", _settingId, ex.Message);
AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error);
}
}
private async Task SaveSetting()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
var setting = await SettingService.GetSettingAsync(_entityName, _settingId);
setting.SettingValue = _settingValue;
setting.IsPrivate = (_isPrivate != null && Boolean.Parse(_isPrivate));
try
{
setting = await SettingService.UpdateSettingAsync(setting);
await logger.LogInformation("Setting Saved {Setting}", setting);
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message);
AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

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

View File

@@ -1,146 +0,0 @@
@namespace Oqtane.Modules.Admin.Settings
@inherits ModuleBase
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-4">
<ActionLink Action="Add" Text="Add Setting" Security="SecurityAccessLevel.Host" ResourceKey="AddSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
<ActionLink Action="ImportSettings" Text="Import" Class="btn btn-secondary ms-1" Security="SecurityAccessLevel.Host" ResourceKey="ImportSettings" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
</div>
<div class="col-sm-4">
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
<option value="-">&lt;@Localizer["Select Entity"]&gt;</option>
@foreach (var entityName in _entityNames)
{
<option value="@entityName">@entityName</option>
}
</select>
</div>
<div class="col-sm-4">
<select class="form-select custom-select" value="@_entityId" @onchange="(e => EntityIdChanged(e))">
<option value="-">&lt;@Localizer["Select Id"]&gt;</option>
@foreach (var entityId in _entityIds)
{
<option value="@entityId">@entityId</option>
}
</select>
</div>
</div>
</div>
<br />
<Pager Items="@_settings" SearchProperties="SettingName,SettingValue">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@Localizer["Value"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"entity={context.EntityName}&id={context.SettingId}")" Security="SecurityAccessLevel.Host" ResourceKey="EditSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" /></td>
<td><ActionDialog Header="Delete Setting" Message="@string.Format(Localizer["Confirm.DeleteSetting"], context.SettingName)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSetting(context))" ResourceKey="DeleteSetting" /></td>
<td>@context.SettingName</td>
<td>@context.SettingValue</td>
</Row>
</Pager>
@code {
private string _entityName = "-";
private List<string> _entityNames = new List<string>();
private string _entityId = "-";
private List<int> _entityIds = new List<int>();
private List<Setting> _settings = new List<Setting>();
public override string UrlParametersTemplate => "/{entityname}/{entityid}";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnParametersSetAsync()
{
_entityNames = await SettingService.GetEntityNamesAsync();
if (UrlParameters.ContainsKey("entityname"))
{
_entityName = UrlParameters["entityname"];
await GetEntityIds();
}
if (UrlParameters.ContainsKey("entityid"))
{
_entityId = UrlParameters["entityid"];
await GetSettings();
}
}
private async Task GetEntityIds()
{
if (_entityName != "-")
{
_entityIds = await SettingService.GetEntityIdsAsync(_entityName);
}
else
{
_entityIds = new List<int>();
}
}
private async Task GetSettings()
{
if (_entityName != "-" && _entityId != "-")
{
_settings = await SettingService.GetSettingsAsync(_entityName, int.Parse(_entityId), "");
_settings = _settings.OrderBy(item => item.SettingName).ToList();
}
else
{
_settings = new List<Setting>();
}
}
private async void EntityNameChanged(ChangeEventArgs e)
{
try
{
_entityName = e.Value.ToString();
_entityId = "-";
await GetEntityIds();
await GetSettings();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On EntityNameChanged");
}
}
private async void EntityIdChanged(ChangeEventArgs e)
{
try
{
_entityId = e.Value.ToString();
await GetSettings();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On EntityIdChanged");
}
}
private async Task DeleteSetting(Setting setting)
{
try
{
await SettingService.DeleteSettingAsync(setting.EntityName, setting.EntityId, setting.SettingName);
await logger.LogInformation("Setting Deleted {Setting}", setting);
await GetSettings();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Setting {Setting} {Error}", setting, ex.Message);
AddModuleMessage(Localizer["Error.DeleteSetting"], MessageType.Error);
}
}
}

View File

@@ -54,18 +54,15 @@
</select>
</div>
</div>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
}
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing. The sitemap is cached for 5 minutes and the cache can be manually cleared." ResourceKey="SiteMap">Site Map: </Label>
<div class="col-sm-9">
@@ -197,128 +194,80 @@
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SmtpEnabled">Enabled? </Label>
<div class="col-sm-3">
</div>
<div class="col-sm-9">
<select id="smtpenabled" class="form-select" value="@_smtpenabled" @onchange="(e => SMTPEnabledChanged(e))">
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
<div class="col-sm-9">
<input id="host" class="form-control" @bind="@_smtphost" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
<div class="col-sm-9">
<input id="port" class="form-control" @bind="@_smtpport" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
<div class="col-sm-9">
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
@if (_smtpenabled == "True" && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<div class="row mb-1 align-items-center">
<div class="col-sm-3">
</div>
<div class="col-sm-9">
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off"/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off"/>
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
<div class="col-sm-9">
<input id="host" class="form-control" @bind="@_smtphost" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmtpSender">Email Sender: </Label>
<div class="col-sm-9">
<input id="sender" class="form-control" @bind="@_smtpsender" />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
<div class="col-sm-9">
<input id="port" class="form-control" @bind="@_smtpport" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above." ResourceKey="SmtpRelay">Relay Configured? </Label>
<div class="col-sm-9">
<select id="relay" class="form-select" @bind="@_smtprelay" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify the type of SSL connection for your SMTP server" ResourceKey="SmtpSSL">SSL Options: </Label>
<div class="col-sm-9">
<select id="smtpssl" class="form-select" @bind="@_smtpssl">
<option value="None">@Localizer["None"]</option>
<option value="Auto">@Localizer["Auto"]</option>
<option value="StartTls">@Localizer["StartTls"]</option>
<option value="SslOnConnect">@Localizer["SslOnConnect"]</option>
<option value="StartTlsWhenAvailable">@Localizer["StartTlsWhenAvailable"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SMTPEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="smtpenabled" class="form-select" @bind="@_smtpenabled">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpauthentication" HelpText="Specify the SMTP authentication type" ResourceKey="SMTPAuthentication">Authentication: </Label>
<div class="col-sm-9">
<select id="smtpauthentication" class="form-select" value="@_smtpauthentication" @onchange="(e => SMTPAuthenticationChanged(e))">
<option value="Basic">@Localizer["Basic"]</option>
<option value="OAuth2">@Localizer["OAuth2"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
</div>
@if (_smtpauthentication == "Basic")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified below." ResourceKey="SmtpRelay">Relay Configured? </Label>
<div class="col-sm-9">
<select id="relay" class="form-select" @bind="@_smtprelay" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpauthority" HelpText="The Authority Url for the SMTP provider" ResourceKey="SmtpAuthority">Authority Url:</Label>
<div class="col-sm-9">
<input id="smtpauthority" class="form-control" @bind="@_smtpauthority" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpclientid" HelpText="The Client ID for the SMTP provider" ResourceKey="SmtpClientID">Client ID:</Label>
<div class="col-sm-9">
<input id="smtpclientid" class="form-control" @bind="@_smtpclientid" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpclientsecret" HelpText="The Client Secret for the SMTP provider" ResourceKey="SmtpClientSecret">Client Secret:</Label>
<div class="col-sm-9">
<div class="input-group">
<input type="@_smtpclientsecrettype" id="smtpclientsecret" class="form-control" @bind="@_smtpclientsecret" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleSmtpClientSecret">@_togglesmtpclientsecret</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpscopes" HelpText="A list of Scopes for the SMTP provider (separated by commas)" ResourceKey="SmtpScopes">Scopes:</Label>
<div class="col-sm-9">
<input id="smtpscopes" class="form-control" @bind="@_smtpscopes" />
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sender" HelpText="Enter the email address which emails will be sent from. Please note that this email address usually needs to be authorized with the SMTP provider." ResourceKey="SmtpSender">Email Sender: </Label>
<div class="col-sm-9">
<input id="sender" class="form-control" @bind="@_smtpsender" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
</div>
</div>
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
<br /><br />
}
</div>
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
<br /><br />
</div>
</Section>
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
@@ -411,18 +360,6 @@
</select>
</div>
</div>
@if (_rendermode == RenderModes.Static)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="enhancednavigation" HelpText="Indicates if enhanced navigation should be used with static rendering" ResourceKey="EnhancedNavigation">Enhanced Navigation: </Label>
<div class="col-sm-9">
<select id="enhancednavigation" class="form-select" @bind="@_enhancednavigation" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
<div class="col-sm-9">
@@ -518,23 +455,16 @@
private string _headcontent = string.Empty;
private string _bodycontent = string.Empty;
private string _smtpenabled = "False";
private string _smtpauthentication = "Basic";
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = "Auto";
private string _smtpssl = "False";
private string _smtpusername = string.Empty;
private string _smtppassword = string.Empty;
private string _smtppasswordtype = "password";
private string _togglesmtppassword = string.Empty;
private string _smtpauthority = string.Empty;
private string _smtpclientid = string.Empty;
private string _smtpclientsecret = string.Empty;
private string _smtpclientsecrettype = "password";
private string _togglesmtpclientsecret = string.Empty;
private string _smtpscopes = string.Empty;
private string _smtpsender = string.Empty;
private string _smtprelay = "False";
private string _smtpenabled = "True";
private int _retention = 30;
private string _pwaisenabled;
@@ -549,7 +479,6 @@
private string _defaultalias;
private string _rendermode = RenderModes.Interactive;
private string _enhancednavigation = "True";
private string _runtime = Runtimes.Server;
private string _prerender = "True";
private string _hybrid = "False";
@@ -579,7 +508,7 @@
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
_timezones = TimeZoneService.GetTimeZones();
_timezones = await TimeZoneService.GetTimeZonesAsync();
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
@@ -605,17 +534,9 @@
{
_faviconfileid = site.FaviconFileId.Value;
}
var themes = new List<Theme>();
foreach (var theme in PageState.Site.Themes)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
{
themes.Add(theme);
}
}
_themes = ThemeService.GetThemeControls(themes);
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(themes, _themetype);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
_cookieconsent = SettingService.GetSetting(settings, "CookieConsent", string.Empty);
@@ -635,27 +556,16 @@
_bodycontent = site.BodyContent;
// SMTP
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "False");
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "Auto");
if (_smtpssl == "True") _smtpssl = "SslOnConnect";
if (_smtpssl == "False") _smtpssl = "StartTlsWhenAvailable";
_smtpauthentication = SettingService.GetSetting(settings, "SMTPAuthentication", "Basic");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpauthority = SettingService.GetSetting(settings, "SMTPAuthority", string.Empty);
_smtpclientid = SettingService.GetSetting(settings, "SMTPClientId", string.Empty);
_smtpclientsecret = SettingService.GetSetting(settings, "SMTPClientSecret", string.Empty);
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
_smtpscopes = SettingService.GetSetting(settings, "SMTPScopes", string.Empty);
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
}
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
// PWA
_pwaisenabled = site.PwaIsEnabled.ToString();
@@ -673,7 +583,6 @@
// hosting model
_rendermode = site.RenderMode;
_enhancednavigation = site.EnhancedNavigation.ToString();
_runtime = site.Runtime;
_prerender = site.Prerender.ToString();
_hybrid = site.Hybrid.ToString();
@@ -683,12 +592,11 @@
{
var tenants = await TenantService.GetTenantsAsync();
var _databases = await DatabaseService.GetDatabasesAsync();
var tenant = tenants.Find(item => item.TenantId == PageState.Alias.TenantId);
var tenant = tenants.Find(item => item.TenantId == site.TenantId);
if (tenant != null)
{
_tenant = tenant.Name;
// hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order
_database = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType && item.Name != "LocalDB")?.Name;
_connectionstring = tenant.DBConnectionString;
}
}
@@ -821,34 +729,29 @@
// hosting model
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
site.RenderMode = _rendermode;
site.EnhancedNavigation = bool.Parse(_enhancednavigation);
site.Runtime = _runtime;
site.Prerender = bool.Parse(_prerender);
site.Hybrid = bool.Parse(_hybrid);
if (site.RenderMode != _rendermode || site.Runtime != _runtime || site.Prerender != bool.Parse(_prerender) || site.Hybrid != bool.Parse(_hybrid))
{
site.RenderMode = _rendermode;
site.Runtime = _runtime;
site.Prerender = bool.Parse(_prerender);
site.Hybrid = bool.Parse(_hybrid);
}
}
site = await SiteService.UpdateSiteAsync(site);
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPAuthority", _smtpauthority, true);
settings = SettingService.SetSetting(settings, "SMTPClientId", _smtpclientid, true);
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
}
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
//cookie consent
settings = SettingService.SetSetting(settings, "CookieConsent", _cookieconsent);
@@ -856,7 +759,6 @@
// functionality
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Settings Saved {Site}", site);
@@ -886,17 +788,17 @@
try
{
var aliases = await AliasService.GetAliasesAsync();
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Alias.TenantId))
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId))
{
await SiteService.DeleteSiteAsync(PageState.Site.SiteId);
await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId);
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Alias.TenantId))
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
}
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Alias.TenantId);
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId);
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
}
else
@@ -911,46 +813,6 @@
}
}
private void SMTPAuthenticationChanged(ChangeEventArgs e)
{
_smtpauthentication = (string)e.Value;
StateHasChanged();
}
private void SMTPEnabledChanged(ChangeEventArgs e)
{
_smtpenabled = (string)e.Value;
StateHasChanged();
}
private void ToggleSMTPPassword()
{
if (_smtppasswordtype == "password")
{
_smtppasswordtype = "text";
_togglesmtppassword = SharedLocalizer["HidePassword"];
}
else
{
_smtppasswordtype = "password";
_togglesmtppassword = SharedLocalizer["ShowPassword"];
}
}
private void ToggleSmtpClientSecret()
{
if (_smtpclientsecrettype == "password")
{
_smtpclientsecrettype = "text";
_togglesmtpclientsecret = SharedLocalizer["HidePassword"];
}
else
{
_smtpclientsecrettype = "password";
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
}
}
private async Task SendEmail()
{
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
@@ -961,13 +823,8 @@
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPAuthority", _smtpauthority, true);
settings = SettingService.SetSetting(settings, "SMTPClientId", _smtpclientid, true);
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await logger.LogInformation("Site SMTP Settings Saved");
@@ -988,12 +845,26 @@
}
}
private void ToggleSMTPPassword()
{
if (_smtppasswordtype == "password")
{
_smtppasswordtype = "text";
_togglesmtppassword = SharedLocalizer["HidePassword"];
}
else
{
_smtppasswordtype = "password";
_togglesmtppassword = SharedLocalizer["ShowPassword"];
}
}
private async Task GetAliases()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_aliases = await AliasService.GetAliasesAsync();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Alias.TenantId).OrderBy(item => item.AliasId).ToList();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
}
}
@@ -1046,7 +917,7 @@
{
if (_aliasid == 0)
{
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Alias.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Site.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
await AliasService.AddAliasAsync(alias);
}
else

View File

@@ -118,42 +118,42 @@ else
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type" ResourceKey="DatabaseType">Type: </Label>
<div class="col-sm-9">
@if (_databases != null)
{
<div class="input-group">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
@if (!_showConnectionString)
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
}
</div>
}
</div>
{
<div class="input-group">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
@if (!_showConnectionString)
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
}
</div>
}
</div>
</div>
@if (!_showConnectionString)
{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent
}
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
</div>
</div>
}
@if (!_showConnectionString)
{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent
}
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of an existing host user" ResourceKey="HostUsername">Host Username:</Label>
<div class="col-sm-9">
@@ -216,7 +216,7 @@ else
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
}
_urls = PageState.Alias.Name;
_themeList = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
{
@@ -237,7 +237,7 @@ else
}
else
{
_databaseName = Constants.DefaultDBName;
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
}
@@ -390,8 +390,6 @@ else
config.DatabaseType = tenant.DBType;
config.ConnectionString = tenant.DBConnectionString;
config.IsNewTenant = false;
config.HostEmail = PageState.User.Email;
config.HostName = PageState.User.DisplayName;
}
}
@@ -405,7 +403,6 @@ else
config.SiteTemplate = _sitetemplatetype;
config.RenderMode = _rendermode;
config.Runtime = _runtime;
config.Register = false;
ShowProgressIndicator();

View File

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

View File

@@ -2,280 +2,241 @@
@inherits ModuleBase
@inject ISystemService SystemService
@inject IInstallationService InstallationService
@inject IMigrationHistoryService MigrationHistoryService
@inject ITenantService TenantService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
<div class="col-sm-9">
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
<div class="col-sm-9">
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
<div class="col-sm-9">
<input id="process" class="form-control" @bind="@_process" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
<div class="col-sm-9">
<input id="machinename" class="form-control" @bind="@_machinename" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="ipaddress" HelpText="Server IP Address" ResourceKey="IPAddress">IP Address: </Label>
<div class="col-sm-9">
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
<div class="col-sm-9">
<input id="environment" class="form-control" @bind="@_environment" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
<div class="col-sm-9">
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="webrootpath" HelpText="Web Path" ResourceKey="WebRootPath">Web Path: </Label>
<div class="col-sm-9">
<input id="webrootpath" class="form-control" @bind="@_webrootpath" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="servertime" HelpText="Server Date/Time (in UTC)" ResourceKey="ServerTime">Server Date/Time: </Label>
<div class="col-sm-9">
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
<div class="col-sm-9">
<input id="workingset" class="form-control" @bind="@_workingset" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
<div class="col-sm-9">
<input id="installationid" class="form-control" @bind="@_installationid" readonly />
</div>
<TabStrip>
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" readonly />
</div>
</div>
<br /><br />
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
</TabPanel>
<TabPanel Name="Options" Heading="Options" ResourceKey="Options">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="detailederrors" HelpText="Specify If Detailed Errors Are Enabled For Blazor. This Option Should Not Not Be Enabled In Production." ResourceKey="DetailedErrors">Detailed Errors? </Label>
<div class="col-sm-9">
<select id="detailederrors" class="form-select" @bind="@_detailederrors">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logginglevel" HelpText="The Minimum Logging Level For The Event Log. This Option Can Be Used To Control The Volume Of Items Stored In Your Event Log." ResourceKey="LoggingLevel">Logging Level: </Label>
<div class="col-sm-9">
<select id="logginglevel" class="form-select" @bind="@_logginglevel">
<option value="Trace">@Localizer["Trace"]</option>
<option value="Debug">@Localizer["Debug"]</option>
<option value="Information">@Localizer["Information"]</option>
<option value="Warning">@Localizer["Warning"]</option>
<option value="Error">@Localizer["Error"]</option>
<option value="Critical">@Localizer["Critical"]</option>
<option value="None">@Localizer["None"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="notificationlevel" HelpText="The Minimum Logging Level For Which Notifications Should Be Sent To Host Users." ResourceKey="NotificationLevel">Notification Level: </Label>
<div class="col-sm-9">
<select id="notificationlevel" class="form-select" @bind="@_notificationlevel">
<option value="Trace">@Localizer["Trace"]</option>
<option value="Debug">@Localizer["Debug"]</option>
<option value="Information">@Localizer["Information"]</option>
<option value="Warning">@Localizer["Warning"]</option>
<option value="Error">@Localizer["Error"]</option>
<option value="Critical">@Localizer["Critical"]</option>
<option value="None">@Localizer["None"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
<div class="col-sm-9">
<select id="swagger" class="form-select" @bind="@_swagger">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cachecontrol" HelpText="Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled." ResourceKey="CacheControl">Static Asset Caching: </Label>
<div class="col-sm-9">
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager Url: </Label>
<div class="col-sm-9">
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
<div class="col-sm-9">
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
<div class="col-sm-9">
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>&nbsp;
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
<br /><br />
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>&nbsp;
<a class="btn btn-secondary" href="api/endpoint" target="_new">@Localizer["Endpoints"]</a>
</TabPanel>
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
<div class="col-sm-9">
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
<div class="col-sm-9">
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
</div>
</div>
<br /><br />
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
</TabPanel>
<TabPanel Name="Migrations" Heading="Migrations" ResourceKey="Migrations">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="The name of the current database. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database." ResourceKey="Tenant">Database: </Label>
<div class="col-sm-9">
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
<div class="col-sm-9">
<input id="process" class="form-control" @bind="@_process" readonly />
</div>
</div>
<br />
<Pager Items="@_history" SearchProperties="MigrationId">
<Header>
<th>@Localizer["Migration"]</th>
<th>@Localizer["Date"]</th>
<th>@Localizer["Version"]</th>
</Header>
<Row>
<td>@context.MigrationId</td>
<td>@UtcToLocal(context.AppliedDate)</td>
<td>@context.AppliedVersion</td>
</Row>
</Pager>
</TabPanel>
</TabStrip>
<br /><br />
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
<div class="col-sm-9">
<input id="machinename" class="form-control" @bind="@_machinename" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="ipaddress" HelpText="Server IP Address" ResourceKey="IPAddress">IP Address: </Label>
<div class="col-sm-9">
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
<div class="col-sm-9">
<input id="environment" class="form-control" @bind="@_environment" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
<div class="col-sm-9">
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="webrootpath" HelpText="Web Path" ResourceKey="WebRootPath">Web Path: </Label>
<div class="col-sm-9">
<input id="webrootpath" class="form-control" @bind="@_webrootpath" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="servertime" HelpText="Server Date/Time (in UTC)" ResourceKey="ServerTime">Server Date/Time: </Label>
<div class="col-sm-9">
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
<div class="col-sm-9">
<input id="workingset" class="form-control" @bind="@_workingset" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
<div class="col-sm-9">
<input id="installationid" class="form-control" @bind="@_installationid" readonly />
</div>
</div>
</div>
<br /><br />
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
</TabPanel>
<TabPanel Name="Options" Heading="Options" ResourceKey="Options">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="detailederrors" HelpText="Specify If Detailed Errors Are Enabled For Blazor. This Option Should Not Not Be Enabled In Production." ResourceKey="DetailedErrors">Detailed Errors? </Label>
<div class="col-sm-9">
<select id="detailederrors" class="form-select" @bind="@_detailederrors">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logginglevel" HelpText="The Minimum Logging Level For The Event Log. This Option Can Be Used To Control The Volume Of Items Stored In Your Event Log." ResourceKey="LoggingLevel">Logging Level: </Label>
<div class="col-sm-9">
<select id="logginglevel" class="form-select" @bind="@_logginglevel">
<option value="Trace">@Localizer["Trace"]</option>
<option value="Debug">@Localizer["Debug"]</option>
<option value="Information">@Localizer["Information"]</option>
<option value="Warning">@Localizer["Warning"]</option>
<option value="Error">@Localizer["Error"]</option>
<option value="Critical">@Localizer["Critical"]</option>
<option value="None">@Localizer["None"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="notificationlevel" HelpText="The Minimum Logging Level For Which Notifications Should Be Sent To Host Users." ResourceKey="NotificationLevel">Notification Level: </Label>
<div class="col-sm-9">
<select id="notificationlevel" class="form-select" @bind="@_notificationlevel">
<option value="Trace">@Localizer["Trace"]</option>
<option value="Debug">@Localizer["Debug"]</option>
<option value="Information">@Localizer["Information"]</option>
<option value="Warning">@Localizer["Warning"]</option>
<option value="Error">@Localizer["Error"]</option>
<option value="Critical">@Localizer["Critical"]</option>
<option value="None">@Localizer["None"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
<div class="col-sm-9">
<select id="swagger" class="form-select" @bind="@_swagger">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cachecontrol" HelpText="Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled." ResourceKey="CacheControl">Static Asset Caching: </Label>
<div class="col-sm-9">
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager Url: </Label>
<div class="col-sm-9">
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
<div class="col-sm-9">
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
</div>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>&nbsp;
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
<br /><br />
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>&nbsp;
<a class="btn btn-secondary" href="api/endpoint" target="_new">@Localizer["Endpoints"]</a>
</TabPanel>
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
<div class="col-sm-9">
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
</div>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
</TabPanel>
</TabStrip>
<br /><br />
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
private bool _initialized = false;
private string _version = string.Empty;
private string _clrversion = string.Empty;
private string _osversion = string.Empty;
private string _version = string.Empty;
private string _clrversion = string.Empty;
private string _osversion = string.Empty;
private string _process = string.Empty;
private string _machinename = string.Empty;
private string _ipaddress = string.Empty;
private string _environment = string.Empty;
private string _contentrootpath = string.Empty;
private string _webrootpath = string.Empty;
private string _servertime = string.Empty;
private string _workingset = string.Empty;
private string _installationid = string.Empty;
private string _ipaddress = string.Empty;
private string _environment = string.Empty;
private string _contentrootpath = string.Empty;
private string _webrootpath = string.Empty;
private string _servertime = string.Empty;
private string _workingset = string.Empty;
private string _installationid = string.Empty;
private string _detailederrors = string.Empty;
private string _logginglevel = string.Empty;
private string _notificationlevel = string.Empty;
private string _swagger = string.Empty;
private string _detailederrors = string.Empty;
private string _logginglevel = string.Empty;
private string _notificationlevel = string.Empty;
private string _swagger = string.Empty;
private string _cachecontrol = string.Empty;
private string _packageregistryurl = string.Empty;
private string _packageregistryemail = string.Empty;
private string _log = string.Empty;
private string _tenant = string.Empty;
private List<MigrationHistory> _history;
protected override async Task OnInitializedAsync()
{
_version = Constants.Version;
protected override async Task OnInitializedAsync()
{
_version = Constants.Version;
var systeminfo = await SystemService.GetSystemInfoAsync("environment");
if (systeminfo != null)
{
_clrversion = systeminfo["CLRVersion"].ToString();
_osversion = systeminfo["OSVersion"].ToString();
var systeminfo = await SystemService.GetSystemInfoAsync("environment");
if (systeminfo != null)
{
_clrversion = systeminfo["CLRVersion"].ToString();
_osversion = systeminfo["OSVersion"].ToString();
_process = systeminfo["Process"].ToString();
_machinename = systeminfo["MachineName"].ToString();
_ipaddress = systeminfo["IPAddress"].ToString();
_environment = systeminfo["Environment"].ToString();
_contentrootpath = systeminfo["ContentRootPath"].ToString();
_webrootpath = systeminfo["WebRootPath"].ToString();
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
}
_ipaddress = systeminfo["IPAddress"].ToString();
_environment = systeminfo["Environment"].ToString();
_contentrootpath = systeminfo["ContentRootPath"].ToString();
_webrootpath = systeminfo["WebRootPath"].ToString();
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
}
systeminfo = await SystemService.GetSystemInfoAsync("configuration");
if (systeminfo != null)
{
_installationid = systeminfo["InstallationId"].ToString();
_detailederrors = systeminfo["DetailedErrors"].ToString();
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
systeminfo = await SystemService.GetSystemInfoAsync("configuration");
if (systeminfo != null)
{
_installationid = systeminfo["InstallationId"].ToString();
_detailederrors = systeminfo["DetailedErrors"].ToString();
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
_swagger = systeminfo["UseSwagger"].ToString();
_cachecontrol = systeminfo["CacheControl"].ToString();
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
_packageregistryemail = systeminfo["PackageRegistryEmail"].ToString();
}
systeminfo = await SystemService.GetSystemInfoAsync("log");
if (systeminfo != null)
{
_log = systeminfo["Log"].ToString();
}
var tenants = await TenantService.GetTenantsAsync();
_tenant = tenants.Find(item => item.TenantId == PageState.Alias.TenantId).Name;
_history = await MigrationHistoryService.GetMigrationHistoryAsync();
_initialized = true;
}
systeminfo = await SystemService.GetSystemInfoAsync("log");
if (systeminfo != null)
{
_log = systeminfo["Log"].ToString();
}
}
private async Task SaveConfig()
{

View File

@@ -101,20 +101,13 @@
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<br />
@if (_themes.Exists(item => item.PackageName == context.PackageId))
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
else
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
@@ -178,7 +171,6 @@
@code {
private bool _initialized = false;
private List<Theme> _themes;
private int _page = 1;
private List<Package> _packages;
private string _price = "free";
@@ -195,8 +187,7 @@
{
try
{
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
await LoadPackages();
await LoadThemes();
_initialized = true;
}
catch (Exception ex)
@@ -206,10 +197,24 @@
}
}
private async Task LoadPackages()
private async Task LoadThemes()
{
ShowProgressIndicator();
var themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (themes.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
HideProgressIndicator();
}
@@ -217,25 +222,25 @@
{
_price = price;
_sort = "popularity";
await LoadPackages();
await LoadThemes();
StateHasChanged();
}
private async Task Search()
{
await LoadPackages();
await LoadThemes();
}
private async Task Reset()
{
_page = 1;
_search = "";
await LoadPackages();
await LoadThemes();
}
private async Task Refresh()
{
await LoadPackages();
await LoadThemes();
}
private void OnPageChange(int page)
@@ -246,7 +251,7 @@
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadPackages();
await LoadThemes();
}
private void HideModal()
@@ -305,6 +310,6 @@
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Theme.Upload"], NavigateUrl("admin/system")), MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Admin.Themes
@inherits ModuleBase
@using System.Text.RegularExpressions
@using System.Reflection
@inject NavigationManager NavigationManager
@inject IThemeService ThemeService
@inject IModuleService ModuleService
@@ -37,33 +36,30 @@
</select>
</div>
</div>
@if (_type == "External")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<div class="col-sm-9">
<select id="reference" class="form-select" @bind="@_reference">
@foreach (string version in _versions)
{
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
{
<option value="@(version)">@(version)</option>
}
}
<option value="local">@SharedLocalizer["LocalVersion"]</option>
</select>
</div>
</div>
@if (!string.IsNullOrEmpty(_location))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_location" readonly />
</div>
</div>
}
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<div class="col-sm-9">
<select id="reference" class="form-select" @bind="@_reference">
@foreach (string version in _versions)
{
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
{
<option value="@(version)">@(version)</option>
}
}
<option value="local">@SharedLocalizer["LocalVersion"]</option>
</select>
</div>
</div>
@if (!string.IsNullOrEmpty(_location))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_location" readonly />
</div>
</div>
}
</div>
<br />
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
@@ -75,10 +71,9 @@
private string _theme = string.Empty;
private List<Template> _templates;
private string _template = "-";
private string _minversion = "2.0.0";
private string _type = "";
private string[] _versions;
private string _reference = "local";
private string _minversion = "2.0.0";
private string _location = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@@ -89,19 +84,9 @@
{
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
}
else
{
var entryAssemblyName = Assembly.GetEntryAssembly().GetName().Name;
if (entryAssemblyName.EndsWith(".Oqtane"))
{
// Oqtane Application assemblies end with .Server.Oqtane or .Client.Oqtane
string[] segments = entryAssemblyName.Split('.');
_owner = string.Join(".", segments, 0, segments.Length - 2);
}
}
}
protected override async Task OnParametersSetAsync()
protected override async Task OnParametersSetAsync()
{
try
{
@@ -120,18 +105,11 @@
{
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
{
if (_type == "Internal")
{
AddModuleMessage(Localizer["Success.Theme.Create.Internal"], MessageType.Success);
}
var template = _templates.FirstOrDefault(item => item.Name == _template);
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
theme = await ThemeService.CreateThemeAsync(theme);
if (_type == "External")
{
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Theme.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
}
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
@@ -147,22 +125,17 @@
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$");
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private void TemplateChanged(ChangeEventArgs e)
{
_template = (string)e.Value;
_minversion = "2.0.0";
if (_template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
_type = template.Type;
}
else
{
_minversion = "2.0.0";
_type = "";
}
GetLocation();
}

View File

@@ -9,98 +9,84 @@
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Theme" ResourceKey="Theme" Heading="Theme">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information" Heading="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="themename" class="form-control" @bind="@_themeName" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9">
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
{
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
}
else
{
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
}
</div>
</div>
</div>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Theme" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" />
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
</TabStrip>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information" Heading="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="themename" class="form-control" @bind="@_themeName" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9">
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
{
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
}
else
{
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
}
</div>
</div>
</div>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
}
@code {
@@ -117,14 +103,11 @@
private string _url = "";
private string _contact = "";
private string _license = "";
private List<Permission> _permissions = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private PermissionGrid _permissionGrid;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
@@ -143,7 +126,6 @@
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
_permissions = theme.PermissionList;
_createdby = theme.CreatedBy;
_createdon = theme.CreatedOn;
_modifiedby = theme.ModifiedBy;
@@ -170,7 +152,6 @@
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
theme.Name = _name;
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
theme.PermissionList = _permissionGrid.GetPermissionList();
await ThemeService.UpdateThemeAsync(theme);
await logger.LogInformation("Theme Saved {Theme}", theme);
NavigationManager.NavigateTo(NavigateUrl());

View File

@@ -15,11 +15,12 @@
else
{
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ms-1" />
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ps-2" />
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
<Pager Items="@_themes">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
@@ -37,6 +38,7 @@ else
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
}
</td>
<td><NavLink class="btn btn-secondary" href="@NavigateUrl("admin/site")">@Localizer["Assign"]</NavLink></td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@@ -78,7 +80,7 @@ else
{
try
{
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
_themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackageUpdatesAsync("theme");
}
catch (Exception ex)
@@ -161,7 +163,7 @@ else
{
try
{
await ThemeService.DeleteThemeAsync(Theme.ThemeId, PageState.Site.SiteId);
await ThemeService.DeleteThemeAsync(Theme.ThemeName);
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}

View File

@@ -36,7 +36,6 @@ else
<th>@Localizer["Url"]</th>
<th>@Localizer["Requests"]</th>
<th>@Localizer["Requested"]</th>
<th>@Localizer["Referrer"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
@@ -50,8 +49,7 @@ else
</td>
<td>@context.Requests</td>
<td>@UtcToLocal(context.RequestedOn)</td>
<td>@context.Referrer</td>
</Row>
</Row>
</Pager>
</TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">

View File

@@ -26,7 +26,8 @@
<br />
}
<TabStrip>
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
<TabPanel Name="Identity" ResourceKey="Identity">
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
@@ -34,6 +35,36 @@
<input id="username" class="form-control" @bind="@_username" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@if (_allowtwofactor)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
<div class="col-sm-9">
<select id="twofactor" class="form-select" @bind="@_twofactor" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
@@ -68,113 +99,9 @@
<br />
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</TabPanel>
<TabPanel Name="Security" Heading="Security" ResourceKey="Security">
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<br /><br />
@if (_allowtwofactor)
{
<Section Name="MFA" Heading="Multi-Factor Authentication" ResourceKey="MFA">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
<div class="col-sm-9">
<select id="twofactor" class="form-select" @bind="@_twofactor" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</Section>
<br />
}
@if (_allowpasskeys)
{
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
<button type="button" class="btn btn-primary" @onclick="AddPasskey">@SharedLocalizer["Add"]</button>
@if (_passkeys.Count > 0)
{
<Pager Items="@_passkeys">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Passkey"]</th>
</Header>
<Row>
@if (context.CredentialId != _passkeyId)
{
<td><button type="button" class="btn btn-primary" @onclick="@(() => EditPasskey(context))">@SharedLocalizer["Edit"]</button></td>
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeletePasskey(context))" ResourceKey="DeletePasskey" Class="btn btn-danger" Header="Delete Passkey" Message="@string.Format(Localizer["Confirm.Passkey.Delete", context.Name])" /></td>
<td>@context.Name</td>
}
else
{
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SavePasskey())">@SharedLocalizer["Save"]</button></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelPasskey())">@SharedLocalizer["Cancel"]</button></td>
<td><input id="passkeyname" class="form-control" @bind="@_passkeyName" /></td>
}
</Row>
</Pager>
}
else
{
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
}
</Section>
<br />
}
@if (_allowexternallogin)
{
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
@if (_logins.Count > 0)
{
<Pager Items="@_logins">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Login"]</th>
</Header>
<Row>
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeleteLogin(context))" ResourceKey="DeleteLogin" Class="btn btn-danger" Header="Delete Login" Message="@string.Format(Localizer["Confirm.Login.Delete", context.Name])" /></td>
<td>@context.Name</td>
</Row>
</Pager>
}
else
{
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
}
</Section>
<br />
}
<br />
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
<br />
</TabPanel>
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
<TabPanel Name="Profile" ResourceKey="Profile">
<div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in _profiles)
@@ -199,16 +126,13 @@
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
var values = option.Split(':');
var name = values[0];
var value = values.Length > 1 ? values[1] : values[0];
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
{
<option value="@name" selected>@value</option>
<option value="@option" selected>@option</option>
}
else
{
<option value="@name">@value</option>
<option value="@option">@option</option>
}
}
</select>
@@ -218,16 +142,13 @@
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
var values = option.Split(':');
var name = values[0];
var value = values.Length > 1 ? values[1] : values[0];
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
{
<option value="@name" selected>@value</option>
<option value="@option" selected>@option</option>
}
else
{
<option value="@name">@value</option>
<option value="@option">@option</option>
}
}
</select>
@@ -303,11 +224,11 @@
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</TabPanel>
<TabPanel Name="Notifications" Heading="Notifications" ResourceKey="Notifications">
<TabPanel Name="Notifications" ResourceKey="Notifications">
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
<br />
<br />
<select class="form-select" @onchange="(e => FilterNotifications(e))">
<select class="form-select" @onchange="(e => FilterChanged(e))">
<option value="to">@Localizer["Inbox"]</option>
<option value="from">@Localizer["Items.Sent"]</option>
</select>
@@ -326,7 +247,7 @@
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await DeleteNotification(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
@if (context.IsRead)
{
@@ -371,7 +292,7 @@
else
{
<div class="no-notifications-text">
@Localizer["NoNotificationsReceived"]
@Localizer["NoNotificationsReceived.Text"]
</div>
}
}
@@ -389,7 +310,7 @@
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await DeleteNotification(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
@if (context.IsRead)
{
@@ -435,7 +356,7 @@
else
{
<div class="no-notifications-text">
@Localizer["NoNotificationsSent"]
@Localizer["NoNotificationsSent.Text"]
</div>
}
}
@@ -446,34 +367,24 @@
}
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private bool _initialized = false;
private bool _allowtwofactor = false;
private bool _allowpasskeys = false;
private bool _allowexternallogin = false;
private string _username = string.Empty;
private string _email = string.Empty;
private string _displayname = string.Empty;
private FileManager _filemanager;
private int _folderid = -1;
private List<Models.TimeZone> _timezones;
private string _timezoneid = string.Empty;
private int _photofileid = -1;
private File _photo = null;
private string _imagefiles = string.Empty;
private bool _initialized = false;
private string _passwordrequirements;
private string _username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
private bool _allowtwofactor = false;
private string _twofactor = "False";
private List<UserPasskey> _passkeys;
private byte[] _passkeyId;
private string _passkeyName = string.Empty;
private List<UserLogin> _logins;
private string _email = string.Empty;
private string _displayname = string.Empty;
private FileManager _filemanager;
private int _folderid = -1;
private string _timezoneid = string.Empty;
private int _photofileid = -1;
private File _photo = null;
private string _imagefiles = string.Empty;
private List<Profile> _profiles;
private Dictionary<string, string> _userSettings;
@@ -483,29 +394,38 @@
private List<Notification> _notifications;
private string _notificationSummary = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnInitializedAsync()
{
try
{
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
_timezones = await TimeZoneService.GetTimeZonesAsync();
if (PageState.User != null)
{
// identity section
_username = PageState.User.Username;
_twofactor = PageState.User.TwoFactorRequired.ToString();
_email = PageState.User.Email;
_displayname = PageState.User.DisplayName;
_timezones = TimeZoneService.GetTimeZones();
_timezoneid = PageState.User.TimeZoneId;
if (string.IsNullOrEmpty(_email))
{
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
}
// get user folder
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
if (folder != null)
{
_folderid = folder.FolderId;
}
_imagefiles = SettingService.GetSetting(PageState.Site.Settings, "ImageFiles", Constants.ImageFiles);
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
if (PageState.User.PhotoFileId != null)
{
_photofileid = PageState.User.PhotoFileId.Value;
@@ -517,27 +437,11 @@
_photo = null;
}
// security section
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
_twofactor = PageState.User.TwoFactorRequired.ToString();
await GetPasskeys();
await GetLogins();
// profile section
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
foreach (var profile in _profiles)
{
if (profile.Options.ToLower().StartsWith("entityname:"))
{
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
}
}
_userSettings = PageState.User.Settings;
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles);
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
// notification section
await LoadNotificationsAsync();
_initialized = true;
@@ -554,7 +458,22 @@
}
}
// identity methods
private async Task LoadNotificationsAsync()
{
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task Save()
{
try
@@ -629,124 +548,6 @@
}
}
private void Cancel()
{
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
// security methods
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
private async Task GetPasskeys()
{
if (_allowpasskeys)
{
_passkeys = await UserService.GetPasskeysAsync(PageState.User.UserId);
}
}
private async Task AddPasskey()
{
// post back to the Passkey page so that the cookies are set correctly
var interop = new Interop(JSRuntime);
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "create", returnurl = NavigateUrl(PageState.Page.Path, "tab=Security") };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
await interop.SubmitForm(url, fields);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// user has initiated a passkey addition
if (PageState.QueryString.ContainsKey("options"))
{
try
{
var interop = new Interop(JSRuntime);
var credential = await interop.CreateCredential(WebUtility.UrlDecode(PageState.QueryString["options"]));
if (!string.IsNullOrEmpty(credential))
{
// post back to the Passkey page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "validate", credential = credential, returnurl = NavigateUrl(PageState.Page.Path, "tab=Security") };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
await interop.SubmitForm(url, fields);
}
else
{
await logger.LogError("Passkey Could Not Be Created");
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Passkey Could Not Be Created");
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
}
}
}
}
private void EditPasskey(UserPasskey passkey)
{
_passkeyId = passkey.CredentialId;
_passkeyName = passkey.Name;
StateHasChanged();
}
private async Task DeletePasskey(UserPasskey passkey)
{
await UserService.DeletePasskeyAsync(PageState.User.UserId, passkey.CredentialId);
await GetPasskeys();
StateHasChanged();
}
private async Task SavePasskey()
{
if (!string.IsNullOrEmpty(_passkeyName))
{
await UserService.UpdatePasskeyAsync(new UserPasskey { CredentialId = _passkeyId, Name = _passkeyName, UserId = PageState.User.UserId });
await GetPasskeys();
_passkeyName = "";
StateHasChanged();
}
}
private async Task CancelPasskey()
{
await GetPasskeys();
_passkeyName = "";
StateHasChanged();
}
private async Task GetLogins()
{
if (_allowexternallogin)
{
_logins = await UserService.GetLoginsAsync(PageState.User.UserId);
}
}
private async Task DeleteLogin(UserLogin login)
{
await UserService.DeleteLoginAsync(PageState.User.UserId, login.Provider, login.Key);
await GetLogins();
StateHasChanged();
}
private async Task Logout()
{
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
@@ -773,24 +574,6 @@
}
}
// profile methods
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private void ProfileChanged(ChangeEventArgs e, string SettingName)
{
var value = (string)e.Value;
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
}
private bool ValidateProfiles()
{
foreach (Profile profile in _profiles)
@@ -822,22 +605,18 @@
return true;
}
// notification methods
private async Task LoadNotificationsAsync()
private void Cancel()
{
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
private async void FilterNotifications(ChangeEventArgs e)
private void ProfileChanged(ChangeEventArgs e, string SettingName)
{
_filter = (string)e.Value;
await LoadNotificationsAsync();
StateHasChanged();
var value = (string)e.Value;
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
}
private async Task DeleteNotification(Notification Notification)
private async Task Delete(Notification Notification)
{
try
{
@@ -862,6 +641,13 @@
}
}
private async void FilterChanged(ChangeEventArgs e)
{
_filter = (string)e.Value;
await LoadNotificationsAsync();
StateHasChanged();
}
private async Task DeleteAllNotifications()
{
try
@@ -893,4 +679,18 @@
HideProgressIndicator();
}
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@@ -28,15 +28,6 @@
<input id="email" class="form-control" @bind="@_email" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
<div class="col-sm-9">
<select id="confirmed" class="form-select" @bind="@_confirmed">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
@@ -88,16 +79,13 @@
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
var values = option.Split(':');
var name = values[0];
var value = values.Length > 1 ? values[1] : values[0];
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
{
<option value="@name" selected>@value</option>
<option value="@option" selected>@option</option>
}
else
{
<option value="@name">@value</option>
<option value="@option">@option</option>
}
}
</select>
@@ -132,7 +120,6 @@
private bool _initialized = false;
private string _username = string.Empty;
private string _email = string.Empty;
private string _confirmed = "True";
private string _displayname = string.Empty;
private string _timezoneid = string.Empty;
private string _notify = "True";
@@ -146,17 +133,8 @@
{
try
{
_timezones = TimeZoneService.GetTimeZones();
_timezones = await TimeZoneService.GetTimeZonesAsync();
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
foreach (var profile in _profiles)
{
if (profile.Options.ToLower().StartsWith("entityname:"))
{
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
}
}
_settings = new Dictionary<string, string>();
_timezoneid = PageState.Site.TimeZoneId;
_initialized = true;
@@ -191,7 +169,6 @@
user.Username = _username;
user.Password = ""; // will be auto generated
user.Email = _email;
user.EmailConfirmed = bool.Parse(_confirmed);
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
user.TimeZoneId = _timezoneid;
user.PhotoFileId = null;

View File

@@ -14,7 +14,8 @@
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
<TabPanel Name="Identity" ResourceKey="Identity">
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
@@ -22,6 +23,24 @@
<input id="username" class="form-control" @bind="@_username" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
<div class="col-sm-9">
@@ -29,7 +48,7 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
<div class="col-sm-9">
<select id="confirmed" class="form-select" @bind="@_confirmed">
<option value="True">@SharedLocalizer["Yes"]</option>
@@ -81,77 +100,7 @@
</div>
</div>
</TabPanel>
<TabPanel Name="Security" Heading="Security" ResourceKey="Security">
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
</div>
<br /><br />
@if (_allowpasskeys)
{
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
@if (_passkeys.Count > 0)
{
<Pager Items="@_passkeys">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Passkey"]</th>
</Header>
<Row>
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeletePasskey(context))" ResourceKey="DeletePasskey" Class="btn btn-danger" Header="Delete Passkey" Message="@string.Format(Localizer["Confirm.Passkey.Delete", context.Name])" /></td>
<td>@context.Name</td>
</Row>
</Pager>
}
else
{
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
}
</Section>
<br />
}
@if (_allowexternallogin)
{
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
@if (_logins.Count > 0)
{
<Pager Items="@_logins">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Login"]</th>
</Header>
<Row>
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeleteLogin(context))" ResourceKey="DeleteLogin" Class="btn btn-danger" Header="Delete Login" Message="@string.Format(Localizer["Confirm.Login.Delete", context.Name])" /></td>
<td>@context.Name</td>
</Row>
</Pager>
}
else
{
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
}
</Section>
<br />
}
</TabPanel>
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
<TabPanel Name="Profile" ResourceKey="Profile">
<div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in _profiles)
@@ -172,16 +121,13 @@
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
var values = option.Split(':');
var name = values[0];
var value = values.Length > 1 ? values[1] : values[0];
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
{
<option value="@name" selected>@value</option>
<option value="@option" selected>@option</option>
}
else
{
<option value="@name">@value</option>
<option value="@option">@option</option>
}
}
</select>
@@ -207,43 +153,37 @@
<br />
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost && _isdeleted != "True")
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost)
{
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
}
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
{
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
}
<br /><br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
}
@code {
private List<Models.TimeZone> _timezones;
private bool _initialized = false;
private bool _allowpasskeys = false;
private bool _allowexternallogin = false;
private string _passwordrequirements;
private int _userid;
private string _username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
private string _email = string.Empty;
private string _confirmed = string.Empty;
private string _displayname = string.Empty;
private List<Models.TimeZone> _timezones;
private string _timezoneid = string.Empty;
private string _isdeleted;
private string _lastlogin;
private string _lastipaddress;
private bool _ishost = false;
private string _passwordrequirements;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
private List<UserPasskey> _passkeys;
private List<UserLogin> _logins;
private List<Profile> _profiles;
private Dictionary<string, string> _settings;
private string _category = string.Empty;
@@ -261,8 +201,10 @@
{
try
{
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
_timezones = await TimeZoneService.GetTimeZonesAsync();
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
{
@@ -274,30 +216,13 @@
_email = user.Email;
_confirmed = user.EmailConfirmed.ToString();
_displayname = user.DisplayName;
_timezones = TimeZoneService.GetTimeZones();
_timezoneid = PageState.User.TimeZoneId;
_isdeleted = user.IsDeleted.ToString();
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
_lastipaddress = user.LastIPAddress;
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
await GetPasskeys();
await GetLogins();
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
foreach (var profile in _profiles)
{
if (profile.Options.ToLower().StartsWith("entityname:"))
{
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
}
}
_settings = user.Settings;
_settings = user.Settings;
_createdby = user.CreatedBy;
_createdon = user.CreatedOn;
_modifiedby = user.ModifiedBy;
@@ -417,35 +342,6 @@
}
}
private async Task GetPasskeys()
{
if (_allowpasskeys)
{
_passkeys = await UserService.GetPasskeysAsync(_userid);
}
}
private async Task DeletePasskey(UserPasskey passkey)
{
await UserService.DeletePasskeyAsync(_userid, passkey.CredentialId);
await GetPasskeys();
StateHasChanged();
}
private async Task GetLogins()
{
if (_allowexternallogin)
{
_logins = await UserService.GetLoginsAsync(_userid);
}
}
private async Task DeleteLogin(UserLogin login)
{
await UserService.DeleteLoginAsync(_userid, login.Provider, login.Key);
await GetLogins();
StateHasChanged();
}
private bool ValidateProfiles()
{
foreach (Profile profile in _profiles)

File diff suppressed because it is too large Load Diff

View File

@@ -35,11 +35,11 @@
{
if (Disabled)
{
<button type="button" class="@Class" title="@AltText" disabled>@((MarkupString)_openIconSpan) @_openText</button>
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
}
else
{
<button type="button" class="@Class" title="@AltText" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
}
}
}
@@ -83,13 +83,13 @@ else
{
if (Disabled)
{
<button type="button" title="@AltText" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
}
else
{
<form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" title="@AltText" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
</form>
}
}
@@ -113,9 +113,6 @@ else
[Parameter]
public string Text { get; set; } // optional - defaults to Action if not specified
[Parameter]
public string AltText { get; set; } // optional
[Parameter]
public string Action { get; set; } // optional

View File

@@ -8,17 +8,17 @@
{
if (Disabled)
{
<NavLink class="@($"{_classname} disabled")" title="@AltText" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
<NavLink class="@($"{_classname} disabled")" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
}
else
{
if (OnClick == null)
{
<NavLink class="@_classname" title="@AltText" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
<NavLink class="@_classname" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
}
else
{
<button type="button" class="@_classname" title="@AltText" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
<button type="button" class="@_classname" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
}
}
}
@@ -42,9 +42,6 @@
[Parameter]
public string Text { get; set; } // optional - defaults to Action if not specified
[Parameter]
public string AltText { get; set; } // optional
[Parameter]
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid

View File

@@ -52,7 +52,7 @@
if (CreatedOn != null)
{
_text += $" {Localizer["On"]} <b>{UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}</b>";
_text += $" {Localizer["On"]} <b>{UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}</ b >";
}
_text += "</p>";
@@ -69,7 +69,7 @@
if (ModifiedOn != null)
{
_text += $" {Localizer["On"]} <b>{UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}</b>";
_text += $" {Localizer["On"]} <b>{UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}</ b >";
}
_text += "</p>";
@@ -86,7 +86,7 @@
if (DeletedOn != null)
{
_text += $" {Localizer["On"]} <b>{UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}</b>";
_text += $" {Localizer["On"]} <b>{UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}</ b >";
}
_text += "</p>";

View File

@@ -107,7 +107,7 @@
@code {
private bool _initialized = false;
private List<Folder> _folders = new List<Folder>();
private List<Folder> _folders;
private List<File> _files = new List<File>();
private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty;
@@ -157,9 +157,6 @@
[Parameter]
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter]
public bool AnonymizeUploadFilenames { get; set; } = false; // optional - indicate if file names should be anonymized on upload - default false
[Parameter]
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
@@ -198,22 +195,19 @@
Filter = "nupkg";
ShowSuccess = true;
}
else
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
{
// folder path specified rather than folderid
if (!string.IsNullOrEmpty(Folder))
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
if (folder != null)
{
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
if (folder != null)
{
FolderId = folder.FolderId;
}
else
{
FolderId = -1;
_message = "Folder Path " + Folder + " Does Not Exist";
_messagetype = MessageType.Error;
}
FolderId = folder.FolderId;
}
else
{
FolderId = -1;
_message = "Folder Path " + Folder + " Does Not Exist";
_messagetype = MessageType.Error;
}
}
@@ -248,24 +242,25 @@
}
}
await SetImage();
if (!string.IsNullOrEmpty(Filter))
{
_filter = "." + Filter.Replace(",", ",.");
}
GetFolderPermission();
await SetImage();
await GetFiles();
_initialized = true;
}
private void GetFolderPermission()
private async Task GetFiles()
{
_haseditpermission = false;
if (Folder == Constants.PackagesFolder)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = new List<File>();
}
else
{
@@ -273,44 +268,6 @@
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
}
else
{
_haseditpermission = false;
}
}
}
private async Task SetImage()
{
_image = string.Empty;
_file = null;
if (FileId != -1)
{
_file = await FileService.GetFileAsync(FileId);
if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
{
var maxwidth = 200;
var maxheight = 200;
var ratioX = (double)maxwidth / (double)_file.ImageWidth;
var ratioY = (double)maxheight / (double)_file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY;
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
}
}
}
private async Task GetFiles()
{
if (ShowFiles)
{
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
{
_files = await FileService.GetFilesAsync(FolderId);
@@ -322,6 +279,7 @@
}
else
{
_haseditpermission = false;
_files = new List<File>();
}
if (_filter != "*")
@@ -345,10 +303,11 @@
try
{
FolderId = int.Parse((string)e.Value);
FileId = -1;
GetFolderPermission();
await SetImage();
await GetFiles();
FileId = -1;
_file = null;
_image = string.Empty;
await OnSelectFolder.InvokeAsync(FolderId);
StateHasChanged();
}
@@ -372,6 +331,29 @@
StateHasChanged();
}
private async Task SetImage()
{
_image = string.Empty;
_file = null;
if (FileId != -1)
{
_file = await FileService.GetFileAsync(FileId);
if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
{
var maxwidth = 200;
var maxheight = 200;
var ratioX = (double)maxwidth / (double)_file.ImageWidth;
var ratioY = (double)maxheight / (double)_file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY;
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
}
}
}
private async Task UploadFiles()
{
_message = string.Empty;
@@ -426,7 +408,7 @@
}
// upload files
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, AnonymizeUploadFilenames, tokenSource.Token);
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token);
// reset progress indicators
if (ShowProgress)
@@ -448,28 +430,6 @@
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
FileId = -1;
if (Folder != Constants.PackagesFolder && !AnonymizeUploadFilenames)
{
// set FileId to first file in upload collection
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
if (file != null)
{
FileId = file.FileId;
}
}
await SetImage();
await OnUpload.InvokeAsync(FileId);
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
await GetFiles();
StateHasChanged();
}
else
{
@@ -477,6 +437,28 @@
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
if (Folder == Constants.PackagesFolder)
{
await OnUpload.InvokeAsync(-1);
}
else
{
// set FileId to first file in upload collection
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
if (file != null)
{
FileId = file.FileId;
await SetImage();
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
await OnUpload.InvokeAsync(FileId);
}
await GetFiles();
StateHasChanged();
}
}
catch (Exception ex)
{
@@ -489,6 +471,7 @@
finally {
tokenSource.Dispose();
}
}
else
{
@@ -518,50 +501,47 @@
_messagetype = MessageType.Success;
}
FileId = -1;
await SetImage();
await GetFiles();
FileId = -1;
await SetImage();
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
await GetFiles();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
_message = Localizer["Error.File.Delete"];
_messagetype = MessageType.Error;
}
}
_message = Localizer["Error.File.Delete"];
_messagetype = MessageType.Error;
}
}
public int GetFileId() => FileId;
public int GetFileId() => FileId;
public int GetFolderId() => FolderId;
public int GetFolderId() => FolderId;
public File GetFile() => _file;
public File GetFile() => _file;
public async Task Refresh()
{
await Refresh(-1);
}
public async Task Refresh()
{
await Refresh(-1);
}
public async Task Refresh(int fileId)
{
await GetFiles();
FileId = -1;
if (fileId != -1)
public async Task Refresh(int fileId)
{
await GetFiles();
if (fileId != -1)
{
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
}
}
await SetImage();
StateHasChanged();
StateHasChanged();
}
}

View File

@@ -4,43 +4,29 @@
@if (!string.IsNullOrEmpty(Message))
{
@if (_style == MessageStyle.Alert)
{
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
@((MarkupString)Message)
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
@((MarkupString)Message)
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
}
@if (ModuleState != null)
{
@if (ModuleState.RenderMode == RenderModes.Static)
{
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
}
<form method="post" class="app-form-inline" @formname="ModuleMessageForm" @onsubmit="CloseMessage" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" data-dismiss="alert" aria-label="close"></button>
</form>
</div>
}
@if (_style == MessageStyle.Toast)
{
<div class="app-modulemessage-toast bottom-0 end-0" @key="DateTime.UtcNow">
<div class="@_classname alert-dismissible fade show mb-3 rounded-end-0" role="alert">
@((MarkupString)Message)
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
}
<form method="post" class="app-form-inline" @formname="ModuleMessageForm" @onsubmit="CloseMessage" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" data-dismiss="alert" aria-label="close"></button>
</form>
</div>
</div>
}
else
{
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
}
}
</div>
}
@code {
private string _message = string.Empty;
private string _classname = string.Empty;
private MessageStyle _style;
[Parameter]
public string Message { get; set; }
@@ -48,9 +34,6 @@
[Parameter]
public MessageType Type { get; set; }
[Parameter]
public MessageStyle Style { get; set; } = MessageStyle.Alert;
[Parameter]
public RenderModeBoundary Parent { get; set; }
@@ -60,11 +43,6 @@
if (!string.IsNullOrEmpty(_message))
{
_classname = GetMessageType(Type);
_style = Style;
if (Type == MessageType.Error)
{
_style = MessageStyle.Alert; // errors should always be displayed as alerts
}
}
}
@@ -89,10 +67,9 @@
return classname;
}
private void CloseMessage()
private void CloseMessage(MouseEventArgs e)
{
if (Parent != null)
if(Parent != null)
{
Parent.DismissMessage();
}

View File

@@ -9,26 +9,62 @@
@if (_permissions != null)
{
<div class="container">
<div class="row">
<div class="col">
<table class="table table-borderless">
<tbody>
<div class="container">
<div class="row">
<div class="col">
<table class="table table-borderless">
<tbody>
<tr>
<th scope="col">@Localizer["Role"]</th>
@foreach (var permissionname in _permissionnames)
{
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
}
</tr>
@foreach (Role role in _roles)
{
<tr>
<th scope="col">@Localizer["Role"]</th>
<td>@role.Name</td>
@foreach (var permissionname in _permissionnames)
{
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
<td style="text-align: center;">
<TriStateCheckBox Value=@GetPermissionValue(permissionname, role.Name, -1) Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
</td>
}
</tr>
@foreach (Role role in _roles)
}
</tbody>
</table>
<br />
</div>
</div>
<div class="row">
<div class="col">
@if (_users.Count != 0)
{
<div class="row">
<div class="col">
</div>
</div>
<table class="table table-borderless">
<thead>
<tr>
<th scope="col">@Localizer["User"]</th>
@foreach (var permissionname in _permissionnames)
{
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
}
</tr>
</thead>
<tbody>
@foreach (User user in _users)
{
<tr>
<td>@role.Name</td>
<td>@user.DisplayName (@user.Username)</td>
@foreach (var permissionname in _permissionnames)
{
<td style="text-align: center;">
<TriStateCheckBox Value="@GetPermissionValue(permissionname, role.Name, -1)" Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
<td style="text-align: center; width: 1px;">
<TriStateCheckBox Value=@GetPermissionValue(permissionname, "", user.UserId) Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
</td>
}
</tr>
@@ -36,242 +72,200 @@
</tbody>
</table>
<br />
</div>
</div>
<div class="row">
<div class="col">
@if (_users.Count != 0)
{
<div class="row">
<div class="col">
</div>
</div>
<table class="table table-borderless">
<thead>
<tr>
<th scope="col">@Localizer["User"]</th>
@foreach (var permissionname in _permissionnames)
{
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
}
</tr>
</thead>
<tbody>
@foreach (User user in _users)
{
<tr>
<td>@user.DisplayName (@user.Username)</td>
@foreach (var permissionname in _permissionnames)
{
<td style="text-align: center; width: 1px;">
<TriStateCheckBox Value="@GetPermissionValue(permissionname, "", user.UserId)" Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
</td>
}
</tr>
}
</tbody>
</table>
<br />
}
</div>
</div>
<div class="row">
<div class="col-11">
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
</div>
<div class="col-1">
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
</div>
</div>
<div class="row">
<div class="col">
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
</div>
}
</div>
</div>
<div class="row">
<div class="col-11">
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
</div>
<div class="col-1">
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
</div>
</div>
<div class="row">
<div class="col">
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
</div>
</div>
</div>
}
@code {
private List<string> _permissionnames;
private List<Permission> _permissions;
private List<Role> _roles;
private List<User> _users = new List<User>();
private AutoComplete _user;
private string _message = string.Empty;
private List<string> _permissionnames;
private List<Permission> _permissions;
private List<Role> _roles;
private List<User> _users = new List<User>();
private AutoComplete _user;
private string _message = string.Empty;
[Parameter]
public string EntityName { get; set; }
[Parameter]
public string EntityName { get; set; }
[Parameter]
public string PermissionNames { get; set; }
[Parameter]
public string PermissionNames { get; set; }
[Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public List<Permission> PermissionList { get; set; }
[Parameter]
public List<Permission> PermissionList { get; set; }
protected override async Task OnInitializedAsync()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
_roles.RemoveAll(item => item.Name == RoleNames.Host); // remove host role
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_roles.RemoveAll(item => item.Name == RoleNames.Host);
}
// get permission names
if (string.IsNullOrEmpty(PermissionNames))
{
_permissionnames = new List<string>();
_permissionnames.Add(Shared.PermissionNames.View);
_permissionnames.Add(Shared.PermissionNames.Edit);
}
else
{
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
}
// get permission names
if (string.IsNullOrEmpty(PermissionNames))
{
_permissionnames = new List<string>();
_permissionnames.Add(Shared.PermissionNames.View);
_permissionnames.Add(Shared.PermissionNames.Edit);
}
else
{
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
}
// initialize permissions
_permissions = new List<Permission>();
if (PermissionList != null && PermissionList.Any())
{
foreach (var permission in PermissionList)
{
_permissions.Add(permission);
if (permission.UserId != null)
{
if (!_users.Any(item => item.UserId == permission.UserId.Value))
{
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
}
}
}
}
else
{
foreach (string permissionname in _permissionnames)
{
// permission names can be in the form of "EntityName:PermissionName:Roles"
if (permissionname.Contains(":"))
{
var segments = permissionname.Split(':');
if (segments.Length == 3)
{
foreach (var role in segments[2].Split(';'))
{
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
}
// ensure admin access
if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
{
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
}
}
}
else
{
_permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
}
}
}
}
// initialize permissions
_permissions = new List<Permission>();
if (PermissionList != null && PermissionList.Any())
{
foreach (var permission in PermissionList)
{
_permissions.Add(permission);
if (permission.UserId != null)
{
if (!_users.Any(item => item.UserId == permission.UserId.Value))
{
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
}
}
}
}
else
{
foreach (string permissionname in _permissionnames)
{
// permission names can be in the form of "EntityName:PermissionName:Roles"
if (permissionname.Contains(":"))
{
var segments = permissionname.Split(':');
if (segments.Length == 3)
{
foreach (var role in segments[2].Split(';'))
{
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
}
// ensure admin access
if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
{
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
}
}
}
else
{
_permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
}
}
}
}
private string GetPermissionName(string permissionName)
{
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
}
private string GetPermissionName(string permissionName)
{
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
}
private string GetEntityName(string permissionName)
{
return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
}
private string GetEntityName(string permissionName)
{
return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
}
private string DisplayPermissionName(string permissionName)
{
var name = Localizer[GetPermissionName(permissionName)].ToString();
name += " " + Localizer[GetEntityName(permissionName)].ToString();
return name;
}
private string DisplayPermissionName(string permissionName)
{
var name = Localizer[GetPermissionName(permissionName)].ToString();
name += " " + Localizer[GetEntityName(permissionName)].ToString();
return name;
}
private bool? GetPermissionValue(string permissionName, string roleName, int userId)
{
bool? isauthorized = null;
if (roleName != "")
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
if (permission != null)
{
isauthorized = permission.IsAuthorized;
}
}
else
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
if (permission != null)
{
isauthorized = permission.IsAuthorized;
}
}
return isauthorized;
}
private bool? GetPermissionValue(string permissionName, string roleName, int userId)
{
bool? isauthorized = null;
if (roleName != "")
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
if (permission != null)
{
isauthorized = permission.IsAuthorized;
}
}
else
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
if (permission != null)
{
isauthorized = permission.IsAuthorized;
}
}
return isauthorized;
}
private bool GetPermissionDisabled(string permissionName, string roleName)
{
var disabled = false;
private bool GetPermissionDisabled(string permissionName, string roleName)
{
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
return true;
}
else
{
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
return true;
}
else
{
return false;
}
}
}
// administrator role permissions can only be changed by a host
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
disabled = true;
}
// API permissions can only be changed by an administrator
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
disabled = true;
}
return disabled;
}
private bool? PermissionChanged(bool? value, string permissionName, string roleName, int userId)
{
if (roleName != "")
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
if (permission != null)
{
_permissions.Remove(permission);
}
// system roles cannot be denied - only custom roles can be denied
var role = _roles.FirstOrDefault(item => item.Name == roleName);
if (value != null && !value.Value && role.IsSystem)
{
value = null;
}
if (value != null)
{
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
}
}
else
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
if (permission != null)
{
_permissions.Remove(permission);
}
if (value != null)
{
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
}
}
return value;
}
private void PermissionChanged(bool? value, string permissionName, string roleName, int userId)
{
if (roleName != "")
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
if (permission != null)
{
_permissions.Remove(permission);
}
if (value != null)
{
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
}
}
else
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
if (permission != null)
{
_permissions.Remove(permission);
}
if (value != null)
{
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
}
}
}
private async Task<Dictionary<string, string>> GetUsers(string filter)
{
@@ -311,20 +305,29 @@
private void ValidatePermissions()
{
// remove deny all users, unauthenticated, and registered users
var permissions = _permissions.Where(item => !item.IsAuthorized &&
(item.RoleName == RoleNames.Everyone || item.RoleName == RoleNames.Unauthenticated || item.RoleName == RoleNames.Registered)).ToList();
foreach (var permission in permissions)
{
_permissions.Remove(permission);
}
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
// remove host role permissions
var permissions = _permissions.Where(item => item.RoleName == RoleNames.Host).ToList();
foreach (var permission in permissions)
{
_permissions.Remove(permission);
}
// add host role permissions if administrator role is not assigned (to prevent lockout)
foreach (var permissionname in _permissionnames)
// remove deny administrators and host users
permissions = _permissions.Where(item => !item.IsAuthorized &&
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)).ToList();
foreach (var permission in permissions)
{
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) && item.RoleName == RoleNames.Admin))
_permissions.Remove(permission);
}
foreach (var permissionname in _permissionnames)
{
// add administrators role if neither host or administrator is assigned
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) &&
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)))
{
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Host, null, true));
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Admin, null, true));
}
}
}

View File

@@ -4,11 +4,11 @@ using System.Threading.Tasks;
namespace Oqtane.Modules.Controls
{
public class QuillJSTextEditorInterop
public class QuillEditorInterop
{
private readonly IJSRuntime _jsRuntime;
public QuillJSTextEditorInterop(IJSRuntime jsRuntime)
public QuillEditorInterop(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}

View File

@@ -64,7 +64,7 @@
</span>
}
</div>
<div @ref="@_editorElement" class="app-editor-resizable"></div>
<div @ref="@_editorElement"></div>
</div>
</div>
</TabPanel>
@@ -177,14 +177,14 @@
</div>
@code {
public string Name => "QuillJS Text Editor";
public string Name => "QuillJS";
private string resourceType = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client";
private bool _settingsLoaded;
private bool _initialized = false;
private QuillJSTextEditorInterop _interop;
private QuillEditorInterop _interop;
private FileManager _fileManager;
private string _activetab = "Rich";
private bool _allowSettings = false;
@@ -246,14 +246,14 @@
public override List<Resource> Resources { get; set; } = new List<Resource>()
{
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/texteditors/quilljs/quill.min.js", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/texteditors/quilljs/quill-blot-formatter.min.js", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/texteditors/quilljs/quill-interop.js", Location = ResourceLocation.Body }
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body }
};
protected override void OnInitialized()
{
_interop = new QuillJSTextEditorInterop(JSRuntime);
_interop = new QuillEditorInterop(JSRuntime);
if (string.IsNullOrEmpty(Placeholder))
{
@@ -277,7 +277,7 @@
{
// include CSS theme
var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/texteditors/quilljs/quill.{_theme}.css", "text/css", "", "", "");
await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/quill/quill.{_theme}.css", "text/css", "", "", "");
}
await base.OnAfterRenderAsync(firstRender);

View File

@@ -7,7 +7,7 @@
@inject ISettingService SettingService
@inject IStringLocalizer<RichTextEditor> Localizer
<div class="row" style="@_style">
<div class="row" style="margin-bottom: 50px;">
<div class="col">
@_textEditorComponent
</div>
@@ -18,8 +18,6 @@
private RenderFragment _textEditorComponent;
private ITextEditor _textEditor;
private string _style = "margin-bottom: 50px;";
[Parameter]
public string Content { get; set; }
@@ -32,9 +30,6 @@
[Parameter]
public string Provider { get; set; }
[Parameter]
public string Style { get; set; } // optional
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
@@ -45,12 +40,6 @@
protected override void OnParametersSet()
{
if (!string.IsNullOrEmpty(Style))
{
_style = Style;
}
_textEditorComponent = (builder) =>
{
CreateTextEditor(builder);

View File

@@ -30,12 +30,6 @@ else
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to specify SecurityAccessLevel
[Parameter]
public string RoleName { get; set; } // optional - can be used to specify Role allowed to view this tab
[Parameter]
public string PermissionName { get; set; } // optional - can be used to specify Permission allowed to view this tab
protected override void OnParametersSet()
{
base.OnParametersSet();

View File

@@ -84,37 +84,12 @@
}
}
/// <summary>
/// Determines if a tab should be visible based on user permissions.
/// Authorization hierarchy:
/// 1. Host and Admin roles ALWAYS have access (bypass all checks)
/// 2. Check standard SecurityAccessLevel (View, Edit, etc.)
/// 3. If RoleName specified AND user is not Admin/Host, check RoleName
/// 4. If PermissionName specified AND user is not Admin/Host, check PermissionName
/// </summary>
/// <param name="tabPanel">The tab panel to check authorization for</param>
/// <returns>True if user is authorized to see this tab, false otherwise</returns>
private bool IsAuthorized(TabPanel tabPanel)
{
// Step 1: Check for Host-only restriction
if (tabPanel.Security == SecurityAccessLevel.Host)
{
// Only Host users can access Host-level security tabs (Admin users are excluded)
return UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
}
// Step 2: Admin bypass all other restrictions
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
return true;
}
var authorized = false;
// Step 3: Check standard SecurityAccessLevel
switch (tabPanel.Security)
{
case null:
case null: // security not specified - assume SecurityAccessLevel.Anonymous
authorized = true;
break;
case SecurityAccessLevel.Anonymous:
@@ -126,23 +101,13 @@
case SecurityAccessLevel.Edit:
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList);
break;
case SecurityAccessLevel.Admin:
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
break;
case SecurityAccessLevel.Host:
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
break;
}
// Step 4: Check RoleName if provided (additional requirement)
if (authorized && !string.IsNullOrEmpty(tabPanel.RoleName))
{
authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.RoleName);
}
// Step 5: Check PermissionName if provided (additional requirement)
if (authorized && !string.IsNullOrEmpty(tabPanel.PermissionName))
{
authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.PermissionName, ModuleState.PermissionList);
}
return authorized;
}
}

View File

@@ -7,7 +7,7 @@
</div>
@code {
public string Name => "Basic Text Editor";
public string Name => "TextArea";
private ElementReference _editor;
private string _content;

View File

@@ -1,44 +0,0 @@
@namespace Oqtane.Modules.Controls
@using Radzen
@using Radzen.Blazor
@inject DialogService DialogService
@inject IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer
@if (!string.IsNullOrEmpty(_message))
{
<div class="rz-html-editor-dialog-item">
<div class="alert alert-warning alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message)
</div>
</div>
}
<div class="rz-html-editor-dialog-item">
<FileManager @ref="_fileManager" Filter="@Filters" />
</div>
<div class="rz-html-editor-dialog-buttons">
<RadzenButton Text="@Localizer["InsertImage"]" Click="InsertImage" />
<RadzenButton Text="@Localizer["Cancel"]" Click="() => DialogService.Close()" ButtonStyle="ButtonStyle.Secondary" />
</div>
@code {
private FileManager _fileManager;
private string _message = string.Empty;
[Parameter]
public string Filters { get; set; }
private void InsertImage()
{
_message = string.Empty;
var file = _fileManager.GetFile();
if (file != null)
{
var result = $"<img src=\"{file.Url}\" style=\"max-width: 100%\" alt=\"{file.Name}\" />";
DialogService.Close(result);
}
else
{
_message = Localizer["Message.Require.Image"];
StateHasChanged();
}
}
}

View File

@@ -1,148 +0,0 @@
@namespace Oqtane.Modules.Controls
@using Radzen
@using Radzen.Blazor
@using System.Text
@inject DialogService DialogService
@inject IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer
@if (_linkAttributes != null)
{
@if (!string.IsNullOrWhiteSpace(_message))
{
<div class="rz-html-editor-dialog-item">
<div class="alert alert-warning alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message)
</div>
</div>
}
<div class="rz-html-editor-dialog-item">
<RadzenDropDown TValue="int" class="form-control" PopupStyle="color: var(--rz-input-value-color);" @bind-Value="_linkType" Data="_linkTypes" TextProperty="Value" ValueProperty="Key" />
</div>
@if (_linkType == 0)
{
<div class="rz-html-editor-dialog-item">
<RadzenTextBox class="form-control" @bind-Value="@_linkAttributes.Href" Placeholder="@Localizer["WebAddress"]" />
</div>
}
else
{
<div class="rz-html-editor-dialog-item">
<FileManager @ref="_fileManager" OnSelectFile="SelectFile" OnSelectFolder="SelectFile" />
</div>
}
@if (_linkTextEditable)
{
<div class="rz-html-editor-dialog-item">
<RadzenTextBox class="form-control" @bind-Value="@_linkAttributes.InnerText" Placeholder="@Localizer["LinkText"]" />
</div>
}
<div class="rz-html-editor-dialog-item">
<RadzenDropDown TValue="bool" class="form-control" PopupStyle="color: var(--rz-input-value-color);" @bind-Value="_blank" Data="_linkTargets" TextProperty="Value" ValueProperty="Key" />
</div>
}
<div class="rz-html-editor-dialog-buttons">
<RadzenButton Text=@Localizer["InsertLink"] Click="InsertLink" />
<RadzenButton Text=@Localizer["Cancel"] Click="() => DialogService.Close()" ButtonStyle="ButtonStyle.Secondary" />
</div>
@code {
class LinkAttributes
{
public string InnerText { get; set; }
public string InnerHtml { get; set; }
public string Href { get; set; }
public string Target { get; set; }
}
[Parameter]
public RadzenHtmlEditor Editor { get; set; }
private IDictionary<int, string> _linkTypes;
private IDictionary<bool, string> _linkTargets;
private LinkAttributes _linkAttributes;
private bool _blank;
private int _linkType;
private string _message;
private bool _linkTextEditable;
private FileManager _fileManager;
private File _previousFile;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
_linkAttributes = await Editor.GetSelectionAttributes<LinkAttributes>("a", new[] { "innerText", "href", "target" });
if (_linkAttributes.Target == "_blank")
{
_blank = true;
}
_linkTextEditable = string.IsNullOrWhiteSpace(_linkAttributes.InnerHtml) || _linkAttributes.InnerHtml == "<br>";
_linkTypes = new Dictionary<int, string>
{
{ 0, Localizer["WebLink"] },
{ 1, Localizer["FileLink"] }
};
_linkTargets = new Dictionary<bool, string>
{
{ false, Localizer["OpenInCurrentWindow"] },
{ true, Localizer["OpenInNewWindow"] }
};
}
private void SelectFile()
{
var file = _fileManager.GetFile();
if(file != null)
{
_linkAttributes.Href = file.Url;
if ((string.IsNullOrWhiteSpace(_linkAttributes.InnerText) || _linkAttributes.InnerText == _previousFile?.Name) && _linkTextEditable)
{
_linkAttributes.InnerText = file.Name;
}
}
else
{
_linkAttributes.Href = string.Empty;
if (_linkAttributes.InnerText == _previousFile?.Name)
{
_linkAttributes.InnerText = string.Empty;
}
}
_previousFile = file;
StateHasChanged();
}
private void InsertLink()
{
_message = string.Empty;
if (string.IsNullOrWhiteSpace(_linkAttributes.Href))
{
_message = _linkType == 1 ? Localizer["Message.Require.File"] : Localizer["Message.Require.WebAddress"];
}
else if (string.IsNullOrWhiteSpace(_linkAttributes.InnerText) && _linkTextEditable)
{
_message = Localizer["Message.Require.LinkText"];
}
if (string.IsNullOrWhiteSpace(_message))
{
var html = new StringBuilder();
html.AppendFormat("<a href=\"{0}\"", _linkAttributes.Href);
if (_blank)
{
html.Append(" target=\"_blank\"");
}
html.AppendFormat(">{0}</a>", string.IsNullOrWhiteSpace(_linkAttributes.InnerText) ? _linkAttributes.InnerHtml : _linkAttributes.InnerText);
DialogService.Close(html.ToString());
}
else
{
StateHasChanged();
}
}
}

View File

@@ -1,12 +0,0 @@
// This is just a placeholder file
// It is necessary for the documentation to successfully build this project.
// Reason is that docfx will run the .net compiler and find references
// to this class in the project.
// But since the real class is just a .razor file, ATM docfx will fail.
//
// Note added 2025-09-23 by @tvatavuk.
// We hope that as .net and docfx improve, the razor-compiler will work in that scenario
// as well, and this file can be removed.
namespace Oqtane.Modules.Controls;
public partial class RadzenTextEditor;

View File

@@ -1,246 +0,0 @@
@using Microsoft.Extensions.Configuration
@using Oqtane.Interfaces
@using System.Text.RegularExpressions
@using Radzen
@using Radzen.Blazor
@using System.Reflection
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@implements ITextEditor
@implements IDisposable
@inject Radzen.ThemeService ThemeService
@inject IRadzenEditorSettingService EditorSettingService
@inject DialogService DialogService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer
<RadzenTheme Theme="@RadzenEditorDefinitions.DefaultTheme" />
<RadzenComponents />
<RadzenHtmlEditor @ref="_editor" Visible="_visible" Placeholder="@Placeholder" style="@($"height: {Height}px;")"
@bind-Value="_value" Execute="OnExecute" class="rz-text-editor app-editor-resizable">
<ChildContent>
@_toolbar
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<RadzenHtmlEditorCustomTool CommandName="Settings" Icon="settings" Title="@Localizer["Settings"]" />
}
</ChildContent>
</RadzenHtmlEditor>
@code {
private Oqtane.Modules.Controls.RadzenTextEditorInterop _interop;
private RadzenHtmlEditor _editor;
private string _value;
private bool _visible = false;
private string _theme;
private string _background;
private IList<string> _toolbarItems;
private RenderFragment _toolbar;
[Parameter]
public string Placeholder { get; set; }
[Parameter]
public bool ReadOnly { get; set; }
[Parameter]
public int Height { get; set; } = 450;
public string Name => "Radzen HTML Editor";
public override List<Resource> Resources { get; set; } = new List<Resource>()
{
new Script("_content/Radzen.Blazor/Radzen.Blazor.js"),
new Script("js/texteditors/radzen/radzen-interop.js")
};
protected override void OnInitialized()
{
_interop = new Oqtane.Modules.Controls.RadzenTextEditorInterop(JSRuntime);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/texteditors/radzen/radzentexteditor.css", "text/css", "", "", "");
await LoadSettings();
_visible = true;
StateHasChanged();
await _interop.Initialize(_editor.Element);
if (!string.IsNullOrEmpty(_theme))
{
ThemeService.SetTheme(_theme);
}
if (!string.IsNullOrEmpty(_background))
{
var backgroundColor = RadzenEditorDefinitions.TransparentBackgroundColor;
switch (_background)
{
case "Light":
backgroundColor = RadzenEditorDefinitions.LightBackgroundColor;
break;
case "Dark":
backgroundColor = RadzenEditorDefinitions.DarkBackgroundColor;
break;
}
await _interop.SetBackgroundColor(_editor.Element, backgroundColor);
}
var subscribers = GetEventSubscribers(DialogService, "OnOpen");
var dialogSubscibers = subscribers?.Where(s => s.Method.DeclaringType == typeof(RadzenDialog)) ?? Enumerable.Empty<Delegate>();
if (dialogSubscibers.Count() > 1)
{
//clean the event to avoid multiple RadzenDialog instances subscribing to the event
dialogSubscibers.Skip(1).ToList().ForEach(s =>
{
DialogService.OnOpen -= s as Action<string, Type, Dictionary<string, object>, DialogOptions>;
});
}
}
}
public void Initialize(string content)
{
_value = !string.IsNullOrEmpty(content) ? content : string.Empty;
DialogService.OnOpen += OnDialogOpened;
}
public void Dispose()
{
if (DialogService != null)
{
DialogService.OnOpen -= OnDialogOpened;
}
}
public async Task<string> GetContent()
{
await Task.CompletedTask;
return _value;
}
private async Task LoadSettings()
{
var scope = await EditorSettingService.GetSettingScopeAsync(ModuleState.ModuleId);
var editorSetting = scope == 1
? await EditorSettingService.LoadSettingsFromModuleAsync(ModuleState.ModuleId)
: await EditorSettingService.LoadSettingsFromSiteAsync(PageState.Site.SiteId);
_theme = editorSetting.Theme;
_background = editorSetting.Background;
_toolbarItems = editorSetting.ToolbarItems.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
_toolbar = SetupToolbarItems();
}
private RenderFragment SetupToolbarItems()
{
return builder =>
{
var sequence = 0;
foreach (var item in _toolbarItems)
{
if (RadzenEditorDefinitions.ToolbarItems.ContainsKey(item))
{
sequence = RadzenEditorDefinitions.ToolbarItems[item](builder, sequence);
}
}
};
}
private async Task OnExecute(HtmlEditorExecuteEventArgs args)
{
switch(args.CommandName)
{
case "InsertImage":
await InsertImage(args.Editor);
break;
case "InsertLink":
await InsertLink(args.Editor);
break;
case "Settings":
await UpdateSettings(args.Editor);
break;
}
}
private async Task InsertImage(RadzenHtmlEditor editor)
{
await editor.SaveSelectionAsync();
var result = await DialogService.OpenAsync<RadzenFileManagerDialog>(Localizer["DialogTitle.SelectImage"], new Dictionary<string, object>
{
{ "Filters", PageState.Site.ImageFiles }
}, new DialogOptions { CssClass = "rz-text-editor-dialog" });
await editor.RestoreSelectionAsync();
if (result != null)
{
await editor.ExecuteCommandAsync(HtmlEditorCommands.InsertHtml, result);
}
}
private async Task InsertLink(RadzenHtmlEditor editor)
{
await editor.SaveSelectionAsync();
var result = await DialogService.OpenAsync<RadzenInsertLinkDialog>(Localizer["DialogTitle.InsertLink"], new Dictionary<string, object>
{
{ "Editor", editor }
}, new DialogOptions { CssClass = "rz-text-editor-dialog" });
await editor.RestoreSelectionAsync();
if (result != null)
{
await editor.ExecuteCommandAsync(HtmlEditorCommands.InsertHtml, result);
}
}
private async Task UpdateSettings(RadzenHtmlEditor editor)
{
await editor.SaveSelectionAsync();
var result = await DialogService.OpenAsync<RadzenTextEditorSettingsDialog>(Localizer["Settings"], null, new DialogOptions { Width = "650px" });
if (result == true)
{
NavigationManager.NavigateTo(NavigationManager.Uri);
}
await editor.RestoreSelectionAsync();
}
private async void OnDialogOpened(string title, Type componentType, Dictionary<string, object> parameters, DialogOptions options)
{
await _interop.UpdateDialogLayout(_editor.Element);
}
private Delegate[] GetEventSubscribers(object target, string eventName)
{
var type = target.GetType();
var eventField = type.GetField(eventName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (eventField == null)
{
return null;
}
var eventDelegate = eventField.GetValue(target) as Delegate;
if (eventDelegate == null)
{
return new Delegate[0];
}
return eventDelegate.GetInvocationList();
}
}

View File

@@ -1,86 +0,0 @@
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
namespace Oqtane.Modules.Controls
{
public sealed class RadzenEditorDefinitions
{
public static IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer { get; internal set; }
public const string TransparentBackgroundColor = "rgba(0, 0, 0, 0)";
public const string LightBackgroundColor = "rgba(255, 255, 255, 1)";
public const string DarkBackgroundColor = "rgba(0, 0, 0, 1)";
public const string DefaultTheme = "default";
public const string DefaultBackground = "Default";
public static readonly IDictionary<string, Func<RenderTreeBuilder, int, int>> ToolbarItems = new Dictionary<string, Func<RenderTreeBuilder, int, int>>()
{
{ "AlignCenter", (builder, sequence) => CreateFragment(builder, sequence, "AlignCenter", "RadzenHtmlEditorAlignCenter") },
{ "AlignLeft", (builder, sequence) => CreateFragment(builder, sequence, "AlignLeft", "RadzenHtmlEditorAlignLeft") },
{ "AlignRight", (builder, sequence) => CreateFragment(builder, sequence, "AlignRight", "RadzenHtmlEditorAlignRight") },
{ "Background", (builder, sequence) => CreateFragment(builder, sequence, "Background", "RadzenHtmlEditorBackground") },
{ "Color", (builder, sequence) => CreateFragment(builder, sequence, "Color", "RadzenHtmlEditorColor") },
{ "FontName", (builder, sequence) => CreateFragment(builder, sequence, "FontName", "RadzenHtmlEditorFontName") },
{ "FontSize", (builder, sequence) => CreateFragment(builder, sequence, "FontSize", "RadzenHtmlEditorFontSize") },
{ "FormatBlock", (builder, sequence) => CreateFragment(builder, sequence, "FormatBlock", "RadzenHtmlEditorFormatBlock") },
{ "Indent", (builder, sequence) => CreateFragment(builder, sequence, "Indent", "RadzenHtmlEditorIndent") },
{ "InsertImage", (builder, sequence) => CreateFragment(builder, sequence, "InsertImage", "RadzenHtmlEditorCustomTool", "InsertImage", "image") },
{ "Bold", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorBold") },
{ "Italic", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorItalic") },
{ "Justify", (builder, sequence) => CreateFragment(builder, sequence, "Justify", "RadzenHtmlEditorJustify") },
{ "Link", (builder, sequence) => CreateFragment(builder, sequence, "InsertLink", "RadzenHtmlEditorCustomTool", "InsertLink", "insert_link") },
{ "OrderedList", (builder, sequence) => CreateFragment(builder, sequence, "OrderedList", "RadzenHtmlEditorOrderedList") },
{ "Outdent", (builder, sequence) => CreateFragment(builder, sequence, "Outdent", "RadzenHtmlEditorOutdent") },
{ "Redo", (builder, sequence) => CreateFragment(builder, sequence, "Redo", "RadzenHtmlEditorRedo") },
{ "RemoveFormat", (builder, sequence) => CreateFragment(builder, sequence, "RemoveFormat", "RadzenHtmlEditorRemoveFormat") },
{ "Separator", (builder, sequence) => CreateFragment(builder, sequence, "Separator", "RadzenHtmlEditorSeparator") },
{ "Source", (builder, sequence) => CreateFragment(builder, sequence, "Source", "RadzenHtmlEditorSource") },
{ "StrikeThrough", (builder, sequence) => CreateFragment(builder, sequence, "StrikeThrough", "RadzenHtmlEditorStrikeThrough") },
{ "Subscript", (builder, sequence) => CreateFragment(builder, sequence, "Subscript", "RadzenHtmlEditorSubscript") },
{ "Superscript", (builder, sequence) => CreateFragment(builder, sequence, "Superscript", "RadzenHtmlEditorSuperscript") },
{ "Underline", (builder, sequence) => CreateFragment(builder, sequence, "Underline", "RadzenHtmlEditorUnderline") },
{ "Undo", (builder, sequence) => CreateFragment(builder, sequence, "Undo", "RadzenHtmlEditorUndo") },
{ "Unlink", (builder, sequence) => CreateFragment(builder, sequence, "Unlink", "RadzenHtmlEditorUnlink") },
{ "UnorderedList", (builder, sequence) => CreateFragment(builder, sequence, "UnorderedList", "RadzenHtmlEditorUnorderedList") },
};
public static readonly string DefaultToolbarItems = "Undo,Redo,Separator,FontName,FontSize,FormatBlock,Bold,Italic,Underline,StrikeThrough,Separator,AlignLeft,AlignCenter,AlignRight,Justify,Separator,Indent,Outdent,UnorderedList,OrderedList,Separator,Color,Background,RemoveFormat,Separator,Subscript,Superscript,Separator,Link,Unlink,InsertImage,Separator,Source";
private static int CreateFragment(RenderTreeBuilder builder, int sequence, string name, string typeName, string commaneName = "", string icon = "")
{
var fullTypeName = $"Radzen.Blazor.{typeName}, Radzen.Blazor";
var type = Type.GetType(fullTypeName);
if (type != null)
{
var title = Localizer[$"{name}.Title"];
var placeholder = Localizer[$"{name}.Placeholder"];
builder.OpenComponent(sequence++, type);
if (!string.IsNullOrEmpty(title) && title != $"{name}.Title" && type.GetProperty("Title") != null)
{
builder.AddAttribute(sequence++, "Title", title);
}
if (!string.IsNullOrEmpty(placeholder) && placeholder != $"{name}.Placeholder" && type.GetProperty("Placeholder") != null)
{
builder.AddAttribute(sequence++, "Placeholder", placeholder);
}
if (!string.IsNullOrEmpty(commaneName) && type.GetProperty("CommandName") != null)
{
builder.AddAttribute(sequence++, "CommandName", commaneName);
}
if (!string.IsNullOrEmpty(icon) && type.GetProperty("Icon") != null)
{
builder.AddAttribute(sequence++, "Icon", icon);
}
builder.CloseComponent();
}
return sequence;
}
}
}

View File

@@ -1,60 +0,0 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Threading.Tasks;
namespace Oqtane.Modules.Controls
{
public class RadzenTextEditorInterop
{
private readonly IJSRuntime _jsRuntime;
public RadzenTextEditorInterop(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public Task Initialize(ElementReference editor)
{
try
{
_jsRuntime.InvokeVoidAsync("Oqtane.RadzenTextEditor.initialize", editor);
}
catch
{
}
return Task.CompletedTask;
}
public Task SetBackgroundColor(ElementReference editor, string color)
{
try
{
_jsRuntime.InvokeVoidAsync(
"Oqtane.RadzenTextEditor.setBackgroundColor",
editor, color);
}
catch
{
}
return Task.CompletedTask;
}
public Task UpdateDialogLayout(ElementReference editor)
{
try
{
_jsRuntime.InvokeVoidAsync("Oqtane.RadzenTextEditor.updateDialogLayout", editor);
}
catch
{
}
return Task.CompletedTask;
}
}
}

View File

@@ -1,11 +0,0 @@
namespace Oqtane.Modules.Controls
{
public class RadzenEditorSetting
{
public string Theme { get; set; }
public string Background { get; set; }
public string ToolbarItems { get; set; }
}
}

View File

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

View File

@@ -16,7 +16,7 @@
public bool Disabled { get; set; }
[Parameter]
public Func<bool?, bool?> OnChange { get; set; }
public Action<bool?> OnChange { get; set; }
protected override void OnInitialized()
{
@@ -41,35 +41,27 @@
break;
}
_value = OnChange(_value);
SetImage();
OnChange(_value);
}
}
private void SetImage()
{
if (!Disabled)
switch (_value)
{
switch (_value)
{
case true:
_src = "images/checked.png";
_title = Localizer["PermissionGranted"];
break;
case false:
_src = "images/unchecked.png";
_title = Localizer["PermissionDenied"];
break;
case null:
_src = "images/null.png";
_title = string.Empty;
break;
}
}
else
{
_src = "images/disabled.png";
_title = Localizer["PermissionDisabled"];
case true:
_src = "images/checked.png";
_title = Localizer["PermissionGranted"];
break;
case false:
_src = "images/unchecked.png";
_title = Localizer["PermissionDenied"];
break;
case null:
_src = "images/null.png";
_title = string.Empty;
break;
}
StateHasChanged();

View File

@@ -1,8 +0,0 @@
namespace Oqtane.Modules
{
public enum MessageStyle
{
Alert,
Toast
}
}

View File

@@ -18,7 +18,7 @@ namespace Oqtane.Modules.HtmlText
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client",
Resources = new List<Resource>()
{
new Stylesheet("~/Module.css")
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" }
}
};
}

View File

@@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Oqtane.Enums;
using Oqtane.Models;
using Oqtane.Security;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
@@ -22,7 +21,7 @@ namespace Oqtane.Modules
private Dictionary<string, string> _urlparameters;
private bool _scriptsloaded = false;
public Logger logger => _logger ?? (_logger = new Logger(this));
protected Logger logger => _logger ?? (_logger = new Logger(this));
[Inject]
protected ILogService LoggingService { get; set; }
@@ -140,24 +139,14 @@ namespace Oqtane.Modules
}
}
// path methods
// path method
public string ModulePath()
{
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
}
public string StaticAssetPath
{
get
{
// requires module to have implemented IModule
return PageState?.Alias.BaseUrl + "_content/" + ModuleState.ModuleDefinition?.PackageName + "/";
}
}
// fingerprint hash code for static assets
public string Fingerprint
{
get
@@ -166,18 +155,6 @@ namespace Oqtane.Modules
}
}
// authorization methods
public bool IsAuthorizedRole(string roleName)
{
return UserSecurity.IsAuthorized(PageState.User, roleName);
}
public bool IsAuthorizedPermission(string permissionName)
{
return UserSecurity.IsAuthorized(PageState.User, permissionName, ModuleState.PermissionList);
}
// url methods
// navigate url
@@ -372,11 +349,6 @@ namespace Oqtane.Modules
}
// UI methods
private static readonly string RenderModeBoundaryErrorMessage =
"RenderModeBoundary is not available. This method requires a RenderModeBoundary parameter. " +
"If you are using child components, ensure you pass the RenderModeBoundary property to the child component: " +
"<ChildComponent RenderModeBoundary=\"RenderModeBoundary\" />";
public void AddModuleMessage(string message, MessageType type)
{
AddModuleMessage(message, type, "top");
@@ -384,47 +356,21 @@ namespace Oqtane.Modules
public void AddModuleMessage(string message, MessageType type, string position)
{
AddModuleMessage(message, type, position, MessageStyle.Alert);
}
public void AddModuleMessage(string message, MessageType type, MessageStyle style)
{
AddModuleMessage(message, type, "top", style);
}
public void AddModuleMessage(string message, MessageType type, string position, MessageStyle style)
{
if (RenderModeBoundary == null)
{
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
}
RenderModeBoundary.AddModuleMessage(message, type, position, style);
RenderModeBoundary.AddModuleMessage(message, type, position);
}
public void ClearModuleMessage()
{
if (RenderModeBoundary == null)
{
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
}
RenderModeBoundary.AddModuleMessage("", MessageType.Undefined);
}
public void ShowProgressIndicator()
{
if (RenderModeBoundary == null)
{
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
}
RenderModeBoundary.ShowProgressIndicator();
}
public void HideProgressIndicator()
{
if (RenderModeBoundary == null)
{
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
}
RenderModeBoundary.HideProgressIndicator();
}
@@ -471,9 +417,6 @@ namespace Oqtane.Modules
await interop.ScrollTo(0, 0, "smooth");
}
// token replace methods
public string ReplaceTokens(string content)
{
return ReplaceTokens(content, null);
@@ -481,11 +424,6 @@ namespace Oqtane.Modules
public string ReplaceTokens(string content, object obj)
{
// check for null or empty content
if (string.IsNullOrEmpty(content))
{
return content;
}
// Using StringBuilder avoids the performance penalty of repeated string allocations
// that occur with string.Replace or string concatenation inside loops.
var sb = new StringBuilder();
@@ -562,46 +500,33 @@ namespace Oqtane.Modules
};
}
// date conversion methods
// date methods
public DateTime? UtcToLocal(DateTime? datetime)
{
// Early return if input is null
if (datetime == null || datetime.Value == DateTime.MinValue || datetime.Value == DateTime.MaxValue)
return datetime;
string timezoneId = null;
TimeZoneInfo timezone = null;
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
{
timezoneId = PageState.User.TimeZoneId;
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId);
}
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
{
timezoneId = PageState.Site.TimeZoneId;
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId);
}
return Utilities.UtcAsLocalDateTime(datetime, timezoneId);
return Utilities.UtcAsLocalDateTime(datetime, timezone);
}
public DateTime? LocalToUtc(DateTime? datetime)
{
// Early return if input is null
if (datetime == null || datetime.Value == DateTime.MinValue || datetime.Value == DateTime.MaxValue)
return datetime;
string timezoneId = null;
TimeZoneInfo timezone = null;
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
{
timezoneId = PageState.User.TimeZoneId;
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId);
}
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
{
timezoneId = PageState.Site.TimeZoneId;
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId);
}
return Utilities.LocalDateAndTimeAsUtc(datetime, timezoneId);
return Utilities.LocalDateAndTimeAsUtc(datetime, timezone);
}
// logging methods

View File

@@ -1,24 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations>
<Version>6.1.3</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
<Description>CMS and Application Framework for Blazor and .NET MAUI</Description>
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
<IsPackable>true</IsPackable>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.1" />
<PackageReference Include="Radzen.Blazor" Version="8.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="Installer\Controls\AzureSqlConfig.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
</ItemGroup>
<PropertyGroup>
<PublishTrimmed>false</PublishTrimmed>
<BlazorEnableCompression>false</BlazorEnableCompression>

View File

@@ -183,7 +183,4 @@
<data name="Refresh.Text" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="Message.Job.Disabled" xml:space="preserve">
<value>The job cannot be started while in the disabled state.</value>
</data>
</root>

View File

@@ -118,13 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ForgotPassword" xml:space="preserve">
<value>Forgot Password?</value>
</data>
<data name="ForgotUsername" xml:space="preserve">
<value>Forgot Username?</value>
</data>
<data name="UseLoginLink" xml:space="preserve">
<value>Use Login Link</value>
<value>Forgot Password</value>
</data>
<data name="Success.Account.Verified" xml:space="preserve">
<value>User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password.</value>
@@ -133,13 +127,13 @@
<value>User Account Email Address Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="Success.Account.Linked" xml:space="preserve">
<value>External Login Linked Successfully. You Can Now Login.</value>
<value>User Account Linked Successfully. You Can Now Login With Your External Login Below.</value>
</data>
<data name="Message.Account.NotLinked" xml:space="preserve">
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="Error.Login.Fail" xml:space="preserve">
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That New User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification Containing Further Instructions.</value>
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification.</value>
</data>
<data name="Message.Required.UserInfo" xml:space="preserve">
<value>Please Provide All Required Fields</value>
@@ -148,19 +142,16 @@
<value>You Are Already Signed In</value>
</data>
<data name="Message.ForgotPassword" xml:space="preserve">
<value>Please Enter The Username Related To Your Account And Then Select The Forgot Password Option Again</value>
</data>
<data name="Message.ForgotUser" xml:space="preserve">
<value>Please Check The Email Address Associated To Your User Account For A Password Reset Notification</value>
</data>
<data name="Message.ForgotUsername" xml:space="preserve">
<value>Please Check Your Email For A Username Reminder Notification</value>
</data>
<data name="Message.SendLoginLink" xml:space="preserve">
<value>A Login Link Has Been Sent To Your Email Address. The Link Is Only Valid For A Limited Amount Of Time.</value>
</data>
<data name="Message.UserDoesNotExist" xml:space="preserve">
<value>User Does Not Exist For Criteria Specified</value>
<value>User Does Not Exist</value>
</data>
<data name="Code.HelpText" xml:space="preserve">
<value>Please enter the secure verification code which was sent to you by email</value>
<value>Please Enter The Secure Verification Code Which Was Sent To You By Email.</value>
</data>
<data name="Code.Placeholder" xml:space="preserve">
<value>Verification Code</value>
@@ -175,7 +166,7 @@
<value>A Secure Verification Code Has Been Sent To Your Email Address. Please Enter The Code That You Received. If You Do Not Receive The Code Or You Have Lost Access To Your Email, Please Contact Your Administrator.</value>
</data>
<data name="Password.HelpText" xml:space="preserve">
<value>Please enter the password related to your account. Remember that passwords are sase sensitive. If you attempt to login to your account multiple times unsuccessfully, you will be locked out for a period of time.</value>
<value>Please Enter The Password Related To Your Account. Remember That Passwords Are Case Sensitive. If You Attempt Unsuccessfully To Log In To Your Account Multiple Times, You Will Be Locked Out For A Period Of Time.</value>
</data>
<data name="Password.Placeholder" xml:space="preserve">
<value>Password</value>
@@ -184,13 +175,13 @@
<value>Password:</value>
</data>
<data name="Remember.HelpText" xml:space="preserve">
<value>Specify if you would like to be signed back in automatically the next time you visit this site</value>
<value>Specify If You Would Like To Be Signed Back In Automatically The Next Time You Visit This Site</value>
</data>
<data name="Remember.Text" xml:space="preserve">
<value>Stay Signed In?</value>
<value>Remember Me?</value>
</data>
<data name="Username.HelpText" xml:space="preserve">
<value>Please enter the username related to your account</value>
<value>Please Enter The Username Related To Your Account</value>
</data>
<data name="Username.Placeholder" xml:space="preserve">
<value>Username</value>
@@ -210,13 +201,7 @@
<data name="Error.ResetPassword" xml:space="preserve">
<value>Error Resetting Password</value>
</data>
<data name="Error.ForgotUsername" xml:space="preserve">
<value>Error Sending Username Reminder</value>
</data>
<data name="Error.SendLoginLink" xml:space="preserve">
<value>Error Sending Login Link</value>
</data>
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
<value>Multiple User Accounts Already Exist With The Email Address Of Your External Login. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.MissingClaims" xml:space="preserve">
@@ -243,28 +228,7 @@
<data name="ExternalLoginStatus.ReviewClaims" xml:space="preserve">
<value>The Review Claims Option Was Enabled In External Login Settings. Please Visit The Event Log To View The Claims Returned By The Provider.</value>
</data>
<data name="ExternalLoginStatus.LoginLinkFailed" xml:space="preserve">
<value>Login Links Are Time Sensitive. Please Request Another Login Link To Complete The Login Process.</value>
</data>
<data name="ExternalLoginStatus.PasskeyFailed" xml:space="preserve">
<value>Passkey Login Was Unsuccessful. Please Ensure You Selected The Correct Passkey For This Site.</value>
</data>
<data name="Register" xml:space="preserve">
<value>Register as new user?</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Use Passkey</value>
</data>
<data name="Error.Passkey.Fail" xml:space="preserve">
<value>Passkey Login Was Not Successful</value>
</data>
<data name="Email.HelpText" xml:space="preserve">
<value>Please enter the email address related to your account</value>
</data>
<data name="Email.Placeholder" xml:space="preserve">
<value>Email Address</value>
</data>
<data name="Email.Text" xml:space="preserve">
<value>Email:</value>
</data>
</root>

View File

@@ -126,9 +126,6 @@
<data name="Success.Module.Download" xml:space="preserve">
<value>Module Package Downloaded Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Success.Module.Upload" xml:space="preserve">
<value>Module Package Uploaded Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Error.Module.Download" xml:space="preserve">
<value>Error Downloading Module</value>
</data>

View File

@@ -136,7 +136,7 @@
<value>You Must Provide A Valid Description (ie. No Punctuation)</value>
</data>
<data name="OwnerName.HelpText" xml:space="preserve">
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation or contain the word "oqtane". If you are using an Internal template then make sure the owner matches the name of the project.</value>
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation or contain the word "oqtane".</value>
</data>
<data name="ModuleName.HelpText" xml:space="preserve">
<value>Enter a name for this module. It should not contain spaces or punctuation or contain the word "oqtane".</value>
@@ -168,10 +168,7 @@
<data name="Location.Text" xml:space="preserve">
<value>Location: </value>
</data>
<data name="Success.Module.Create.Internal" xml:space="preserve">
<value>The Source Code For Your Module Has Been Created In Your Solution And Must Be Compiled In Order To Make It Functional</value>
</data>
<data name="Success.Module.Create.External" xml:space="preserve">
<data name="Success.Module.Create" xml:space="preserve">
<value>The Source Code For Your Module Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Activate The Module.</value>
</data>
</root>

View File

@@ -183,8 +183,8 @@
<data name="Runtimes.Text" xml:space="preserve">
<value>Runtimes: </value>
</data>
<data name="Module.Heading" xml:space="preserve">
<value>Module</value>
<data name="Definition.Heading" xml:space="preserve">
<value>Definition</value>
</data>
<data name="Information.Heading" xml:space="preserve">
<value>Information</value>

View File

@@ -225,6 +225,9 @@
<data name="Personalizable.Text" xml:space="preserve">
<value>Personalizable? </value>
</data>
<data name="Appearance.Name" xml:space="preserve">
<value>Appearance</value>
</data>
<data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
@@ -250,7 +253,7 @@
<value>Permissions</value>
</data>
<data name="Theme.Heading" xml:space="preserve">
<value>Theme</value>
<value>Theme Settings</value>
</data>
<data name="EffectiveDate.HelpText" xml:space="preserve">
<value>The date that this page is active</value>
@@ -264,7 +267,4 @@
<data name="ExpiryDate.Text" xml:space="preserve">
<value>Expiry Date: </value>
</data>
<data name="Appearance.Heading" xml:space="preserve">
<value>Appearance</value>
</data>
</root>

View File

@@ -309,7 +309,4 @@
<data name="UpdateModulePermissions.HelpText" xml:space="preserve">
<value>Specify if changes made to page permissions should be propagated to the modules on this page</value>
</data>
<data name="Theme.Heading" xml:space="preserve">
<value>Theme</value>
</data>
</root>

View File

@@ -157,7 +157,7 @@
<value>The default value for this profile item</value>
</data>
<data name="Options.HelpText" xml:space="preserve">
<value>A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from Settings.</value>
<value>A comma delimited list of options the user can select from</value>
</data>
<data name="Required.HelpText" xml:space="preserve">
<value>Should a user be required to provide a value for this profile item?</value>
@@ -201,10 +201,4 @@
<data name="Autocomplete.Text" xml:space="preserve">
<value>Autocomplete: </value>
</data>
<data name="Options" xml:space="preserve">
<value>Options</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Settings</value>
</data>
</root>

View File

@@ -204,7 +204,7 @@
<data name="Success.Page.Delete" xml:space="preserve">
<value>Page Deleted Successfully</value>
</data>
<data name="Success.Pages.Delete" xml:space="preserve">
<data name="Success.Pages.Deleted" xml:space="preserve">
<value>All Pages Deleted Successfully</value>
</data>
<data name="Success.Module.Restore" xml:space="preserve">

View File

@@ -1,156 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="SettingName.Text" xml:space="preserve">
<value>Name:</value>
</data>
<data name="SettingName.HelpText" xml:space="preserve">
<value>Setting Name</value>
</data>
<data name="EntityId.HelpText" xml:space="preserve">
<value>Select an existing Id or input a new Id. For Entities which are global such as master data, use the Id value '-1'.</value>
</data>
<data name="EntityName.HelpText" xml:space="preserve">
<value>Select an existing Entity or input a custom Entity. Custom Entities with a prefix of 'Master:' will be stored in the master database.</value>
</data>
<data name="EntityId.Text" xml:space="preserve">
<value>Id:</value>
</data>
<data name="EntityName.Text" xml:space="preserve">
<value>Entity:</value>
</data>
<data name="Error.SaveSetting" xml:space="preserve">
<value>Error Saving Setting</value>
</data>
<data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value>
</data>
<data name="SettingValue.Text" xml:space="preserve">
<value>Value:</value>
</data>
<data name="SettingValue.HelpText" xml:space="preserve">
<value>Setting Value</value>
</data>
<data name="IsPrivate.Text" xml:space="preserve">
<value>Private?</value>
</data>
<data name="IsPrivate.HelpText" xml:space="preserve">
<value>Indicates if this setting is private ie. if it should only be maintained on the server and not sent to the client</value>
</data>
</root>

View File

@@ -1,159 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="SettingName.Text" xml:space="preserve">
<value>Name:</value>
</data>
<data name="SettingName.HelpText" xml:space="preserve">
<value>Setting Name</value>
</data>
<data name="EntityId.HelpText" xml:space="preserve">
<value>Entity Id</value>
</data>
<data name="EntityName.HelpText" xml:space="preserve">
<value>Entity Name</value>
</data>
<data name="EntityId.Text" xml:space="preserve">
<value>Id:</value>
</data>
<data name="EntityName.Text" xml:space="preserve">
<value>Entity:</value>
</data>
<data name="Error.LoadSetting" xml:space="preserve">
<value>Error Loading Setting</value>
</data>
<data name="Error.SaveSetting" xml:space="preserve">
<value>Error Saving Setting</value>
</data>
<data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value>
</data>
<data name="SettingValue.Text" xml:space="preserve">
<value>Value:</value>
</data>
<data name="SettingValue.HelpText" xml:space="preserve">
<value>Setting Value</value>
</data>
<data name="IsPrivate.Text" xml:space="preserve">
<value>Private?</value>
</data>
<data name="IsPrivate.HelpText" xml:space="preserve">
<value>Indicates if this setting is private ie. if it should only be maintained on the server and not sent to the client</value>
</data>
</root>

View File

@@ -1,141 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Settings.Text" xml:space="preserve">
<value>Settings:</value>
</data>
<data name="Settings.HelpText" xml:space="preserve">
<value>Provide settings in comma delimited format using the column template specified</value>
</data>
<data name="Import" xml:space="preserve">
<value>Import</value>
</data>
<data name="Message.Import.Success" xml:space="preserve">
<value>Setting Import Successful</value>
</data>
<data name="Message.Import.Validation" xml:space="preserve">
<value>You Must Provide Settings To Import</value>
</data>
<data name="Message.Import.Failure" xml:space="preserve">
<value>Setting Import Failed. Please Review Your Event Log For More Detailed Information.</value>
</data>
<data name="Error.Import" xml:space="preserve">
<value>Error Importing Settings</value>
</data>
</root>

View File

@@ -1,153 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Confirm.DeleteSetting" xml:space="preserve">
<value>Are You Sure You Wish To Delete The {0} Setting?</value>
</data>
<data name="Error.DeleteSetting" xml:space="preserve">
<value>Error Deleting Setting</value>
</data>
<data name="AddSetting.Text" xml:space="preserve">
<value>Add Setting</value>
</data>
<data name="DeleteSetting.Header" xml:space="preserve">
<value>Delete Setting</value>
</data>
<data name="DeleteSetting.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="EditSetting.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Select Entity" xml:space="preserve">
<value>Select Entity</value>
</data>
<data name="Select Id" xml:space="preserve">
<value>Select Id</value>
</data>
<data name="Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Value" xml:space="preserve">
<value>Value</value>
</data>
<data name="Import.SettingsText" xml:space="preserve">
<value>Import</value>
</data>
</root>

View File

@@ -192,8 +192,8 @@
<data name="Port.HelpText" xml:space="preserve">
<value>Enter the port number for the SMTP server. Please note this field is required if you provide a host name.</value>
</data>
<data name="SmtpSSL.HelpText" xml:space="preserve">
<value>Specify the type of SSL connection for your SMTP server</value>
<data name="UseSsl.HelpText" xml:space="preserve">
<value>Specify if SSL is required for your SMTP server</value>
</data>
<data name="SmtpUsername.HelpText" xml:space="preserve">
<value>Enter the username for your SMTP account</value>
@@ -202,7 +202,7 @@
<value>Enter the password for your SMTP account</value>
</data>
<data name="SmtpSender.HelpText" xml:space="preserve">
<value>Enter the email address which emails will be sent from. Please note that this email address usually needs to be authorized with the SMTP server.</value>
<value>Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server.</value>
</data>
<data name="EnablePWA.HelpText" xml:space="preserve">
<value>Select whether you would like this site to be available as a Progressive Web Application (PWA)</value>
@@ -240,8 +240,8 @@
<data name="Port.Text" xml:space="preserve">
<value>Port: </value>
</data>
<data name="SmtpSSL.Text" xml:space="preserve">
<value>SSL Options:</value>
<data name="UseSsl.Text" xml:space="preserve">
<value>SSL Enabled: </value>
</data>
<data name="SmtpUsername.Text" xml:space="preserve">
<value>Username: </value>
@@ -372,10 +372,10 @@
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="SmtpEnabled.HelpText" xml:space="preserve">
<data name="SMTPEnabled.HelpText" xml:space="preserve">
<value>Specify if SMTP is enabled for this site</value>
</data>
<data name="SmtpEnabled.Text" xml:space="preserve">
<data name="SMTPEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
<data name="Version.HelpText" xml:space="preserve">
@@ -453,55 +453,4 @@
<data name="TimeZone.HelpText" xml:space="preserve">
<value>The default time zone for the site</value>
</data>
<data name="Basic" xml:space="preserve">
<value>Basic</value>
</data>
<data name="OAuth2" xml:space="preserve">
<value>OAuth 2.0 (OAuth2)</value>
</data>
<data name="SmtpAuthentication.Text" xml:space="preserve">
<value>Authentication:</value>
</data>
<data name="SmtpAuthentication.HelpText" xml:space="preserve">
<value>Specify the SMTP authentication type</value>
</data>
<data name="SmtpClientID.Text" xml:space="preserve">
<value>Client ID:</value>
</data>
<data name="SmtpClientID.HelpText" xml:space="preserve">
<value>The Client ID for the SMTP provider</value>
</data>
<data name="SmtpClientSecret.Text" xml:space="preserve">
<value>Client Secret:</value>
</data>
<data name="SmtpClientSecret.HelpText" xml:space="preserve">
<value>The Client Secret for the SMTP provider</value>
</data>
<data name="SmtpScopes.Text" xml:space="preserve">
<value>Scopes:</value>
</data>
<data name="SmtpScopes.HelpText" xml:space="preserve">
<value>A list of Scopes for the SMTP provider (separated by commas)</value>
</data>
<data name="SmtpAuthority.Text" xml:space="preserve">
<value>Authority Url:</value>
</data>
<data name="SmtpAuthority.HelpText" xml:space="preserve">
<value>The Authority Url for the SMTP provider</value>
</data>
<data name="None" xml:space="preserve">
<value>None</value>
</data>
<data name="Auto" xml:space="preserve">
<value>Automatic</value>
</data>
<data name="StartTls" xml:space="preserve">
<value>Upgrade To TLS</value>
</data>
<data name="SslOnConnect" xml:space="preserve">
<value>Require SSL/TLS</value>
</data>
<data name="StartTlsWhenAvailable" xml:space="preserve">
<value>Use TLS When Available</value>
</data>
</root>

View File

@@ -309,19 +309,4 @@
<data name="Endpoints" xml:space="preserve">
<value>API Endpoints</value>
</data>
<data name="Migration" xml:space="preserve">
<value>Migration</value>
</data>
<data name="Date" xml:space="preserve">
<value>Date</value>
</data>
<data name="Version" xml:space="preserve">
<value>Framework Version</value>
</data>
<data name="Tenant.Text" xml:space="preserve">
<value>Database:</value>
</data>
<data name="Tenant.HelpText" xml:space="preserve">
<value>The name of the current database. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database.</value>
</data>
</root>

View File

@@ -126,9 +126,6 @@
<data name="Success.Theme.Download" xml:space="preserve">
<value>Theme Package Downloaded Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Success.Theme.Upload" xml:space="preserve">
<value>Theme Package Uploaded Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Error.Theme.Download" xml:space="preserve">
<value>Error Downloading Theme</value>
</data>

View File

@@ -141,17 +141,14 @@
<data name="Info.Theme.CreatorIntent" xml:space="preserve">
<value>Please Note That The Theme Creator Is Only Intended To Be Used In A Development Environment</value>
</data>
<data name="Success.Theme.Create.Internal" xml:space="preserve">
<value>The Source Code For Your Theme Has Been Created In Your Solution And Must Be Compiled In Order To Make It Functional</value>
</data>
<data name="Success.Theme.Create.External" xml:space="preserve">
<value>The Source Code For Your Theme Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Activate The Theme.</value>
<data name="Success.Theme.Create" xml:space="preserve">
<value>The Source Code For Your Theme Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Activate The Module.</value>
</data>
<data name="Message.Required.ValidName" xml:space="preserve">
<value>You Must Provide A Valid Owner Name And Theme Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value>
</data>
<data name="OwnerName.HelpText" xml:space="preserve">
<value>Enter the name of the organization who is developing this theme. It should not contain spaces or punctuation or contain the word "oqtane". If you are using an Internal template then make sure the owner matches the name of the project.</value>
<value>Enter the name of the organization who is developing this theme. It should not contain spaces or punctuation.</value>
</data>
<data name="ThemeName.HelpText" xml:space="preserve">
<value>Enter a name for this theme. It should not contain spaces or punctuation.</value>

View File

@@ -180,10 +180,4 @@
<data name="View License" xml:space="preserve">
<value>View License</value>
</data>
<data name="Theme.Heading" xml:space="preserve">
<value>Theme</value>
</data>
<data name="Permissions.Heading" xml:space="preserve">
<value>Permissions</value>
</data>
</root>

View File

@@ -156,6 +156,9 @@
<data name="Enabled" xml:space="preserve">
<value>Enabled?</value>
</data>
<data name="Assign" xml:space="preserve">
<value>Assign</value>
</data>
<data name="Synchronize" xml:space="preserve">
<value>Check For Updates</value>
</data>

View File

@@ -147,6 +147,9 @@
<data name="Message.User.NoLogIn" xml:space="preserve">
<value>Current User Is Not Logged In</value>
</data>
<data name="Message.User.NoEmail" xml:space="preserve">
<value>You Must Provide An Email Address For Your User Account</value>
</data>
<data name="Error.Profile.Load" xml:space="preserve">
<value>Error Loading User Profile</value>
</data>
@@ -168,6 +171,9 @@
<data name="DeleteNotification.Message" xml:space="preserve">
<value>Are You Sure You Wish To Delete This Notification?</value>
</data>
<data name="Identity.Name" xml:space="preserve">
<value>Identity</value>
</data>
<data name="Confirm.HelpText" xml:space="preserve">
<value>If you are changing your password you must enter it again to confirm it matches the value entered above</value>
</data>
@@ -208,7 +214,7 @@
<value>Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in.</value>
</data>
<data name="TwoFactor.Text" xml:space="preserve">
<value>Use Two Factor?</value>
<value>Two Factor?</value>
</data>
<data name="DeleteAllNotifications.Header" xml:space="preserve">
<value>Clear Notifications</value>
@@ -231,11 +237,11 @@
<data name="DeleteNotification.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="NoNotificationsReceived" xml:space="preserve">
<value>You Have Not Received Any Notifications</value>
<data name="NoNotificationsReceived.Text" xml:space="preserve">
<value>No notifications have been received</value>
</data>
<data name="NoNotificationsSent" xml:space="preserve">
<value>You Have Not Sent Any Notifications</value>
<data name="NoNotificationsSent.Text" xml:space="preserve">
<value>No notifications have been sent</value>
</data>
<data name="Logout Everywhere" xml:space="preserve">
<value>Logout Everywhere</value>
@@ -246,46 +252,4 @@
<data name="TimeZone.HelpText" xml:space="preserve">
<value>Your time zone</value>
</data>
<data name="Identity.Heading" xml:space="preserve">
<value>Identity</value>
</data>
<data name="Security.Heading" xml:space="preserve">
<value>Security</value>
</data>
<data name="MFA.Heading" xml:space="preserve">
<value>Multi-Factor Authenticationxxx</value>
</data>
<data name="Passkeys.Heading" xml:space="preserve">
<value>Passkeys</value>
</data>
<data name="Logins.Heading" xml:space="preserve">
<value>External Logins</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Passkey</value>
</data>
<data name="DeletePasskey.Header" xml:space="preserve">
<value>Delete Passkey</value>
</data>
<data name="Confirm.Passkey.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete {0}?</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="DeleteLogin.Header" xml:space="preserve">
<value>Delete Login</value>
</data>
<data name="Confirm.Login.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete {0}?</value>
</data>
<data name="Message.Passkeys.None" xml:space="preserve">
<value>You Have Not Created Any Passkeys</value>
</data>
<data name="Message.Logins.None" xml:space="preserve">
<value>You Do Not Have Any External Logins For This Site</value>
</data>
<data name="Error.Passkey.Fail" xml:space="preserve">
<value>Passkey Could Not Be Created</value>
</data>
</root>

View File

@@ -162,10 +162,4 @@
<data name="TimeZone.HelpText" xml:space="preserve">
<value>The user's time zone</value>
</data>
<data name="Confirmed.Text" xml:space="preserve">
<value>Verified?</value>
</data>
<data name="Confirmed.HelpText" xml:space="preserve">
<value>Indicates if the user's email is verified</value>
</data>
</root>

View File

@@ -123,6 +123,9 @@
<data name="Message.Password.Complexity" xml:space="preserve">
<value>Password Provided Does Not Meet The Complexity Policy</value>
</data>
<data name="Identity.Name" xml:space="preserve">
<value>Identity</value>
</data>
<data name="Confirm.Delete" xml:space="preserve">
<value>Is Deleted?</value>
</data>
@@ -214,42 +217,9 @@
<value>The user's time zone</value>
</data>
<data name="Confirmed.Text" xml:space="preserve">
<value>Verified?</value>
<value>Confirmed?</value>
</data>
<data name="Confirmed.HelpText" xml:space="preserve">
<value>Indicates if the user's email is verified</value>
</data>
<data name="Security.Heading" xml:space="preserve">
<value>Security</value>
</data>
<data name="Passkeys.Heading" xml:space="preserve">
<value>Passkeys</value>
</data>
<data name="Logins.Heading" xml:space="preserve">
<value>External Logins</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Passkey</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="DeletePasskey.Header" xml:space="preserve">
<value>Delete Passkey</value>
</data>
<data name="DeleteLogin.Header" xml:space="preserve">
<value>Delete Login</value>
</data>
<data name="Confirm.Passkey.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete {0}?</value>
</data>
<data name="Confirm.Login.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete {0}?</value>
</data>
<data name="Message.Passkeys.None" xml:space="preserve">
<value>You Have Not Created Any Passkeys</value>
</data>
<data name="Message.Logins.None" xml:space="preserve">
<value>You Do Not Have Any External Logins For This Site</value>
</data>
</root>

View File

@@ -217,7 +217,7 @@
<value>Unique Characters:</value>
</data>
<data name="AllowSiteLogin.HelpText" xml:space="preserve">
<value>Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already successfully configured an alternate login method, or else you may lock yourself out of the site.</value>
<value>Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site.</value>
</data>
<data name="AllowSiteLogin.Text" xml:space="preserve">
<value>Allow Local Login?</value>
@@ -370,13 +370,7 @@
<value>Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out.</value>
</data>
<data name="TwoFactor.Text" xml:space="preserve">
<value>Use 2FA?</value>
</data>
<data name="RequireConfirmedEmail.HelpText" xml:space="preserve">
<value>Do you want to require registered users to verify their email address before they are allowed to log in?</value>
</data>
<data name="RequireConfirmedEmail.Text" xml:space="preserve">
<value>Require Verified Email?</value>
<value>Two Factor?</value>
</data>
<data name="Disabled" xml:space="preserve">
<value>Disabled</value>
@@ -508,17 +502,11 @@
<value>Info</value>
</data>
<data name="OAuth2" xml:space="preserve">
<value>OAuth 2.0 (OAuth2)</value>
<value>OAuth 2.0</value>
</data>
<data name="OIDC" xml:space="preserve">
<value>OpenID Connect (OIDC)</value>
</data>
<data name="RequireNonce.Text" xml:space="preserve">
<value>Require Nonce?</value>
</data>
<data name="RequireNonce.HelpText" xml:space="preserve">
<value>Specify if Nonce validation is required for the ID token (the default is true)</value>
</data>
<data name="SaveTokens.Text" xml:space="preserve">
<value>Save Tokens?</value>
</data>
@@ -543,34 +531,4 @@
<data name="AllowHostRole.HelpText" xml:space="preserve">
<value>Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation.</value>
</data>
<data name="Active Users" xml:space="preserve">
<value>Active Users</value>
</data>
<data name="Deleted Users" xml:space="preserve">
<value>Deleted Users</value>
</data>
<data name="CookieDomain.Text" xml:space="preserve">
<value>Cookie Domain:</value>
</data>
<data name="CookieDomain.HelpText" xml:space="preserve">
<value>If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')</value>
</data>
<data name="SingleLogout.Text" xml:space="preserve">
<value>Allow Single Logout?</value>
</data>
<data name="SingleLogout.HelpText" xml:space="preserve">
<value>Specify if users should be logged out of both the application and provider (the default is false indicating they will only be logged out of the application)</value>
</data>
<data name="Passkeys.Text" xml:space="preserve">
<value>Allow Passkeys?</value>
</data>
<data name="Passkeys.HelpText" xml:space="preserve">
<value>Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)?</value>
</data>
<data name="LoginLink.Text" xml:space="preserve">
<value>Allow Login Link?</value>
</data>
<data name="LoginLink.HelpText" xml:space="preserve">
<value>Do you want to allow users to login using a time sensitive link sent by email?</value>
</data>
</root>

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