Compare commits

..

134 Commits

Author SHA1 Message Date
7b4d13b73e Merge pull request #2318 from oqtane/master
Merge pull request #2317 from oqtane/dev
2022-07-27 16:16:01 -04:00
2909aa1656 Merge pull request #2317 from oqtane/dev
3.1.4 release
2022-07-27 16:15:43 -04:00
64131b6764 Update README.md 2022-07-27 08:54:48 -04:00
0b78d75a21 Merge pull request #2314 from sbwalker/dev
prepare for 3.1.4 release
2022-07-26 17:22:27 -04:00
b35c342960 prepare for 3.1.4 release 2022-07-26 17:22:06 -04:00
24c858d379 Merge pull request #2313 from sbwalker/dev
support for module translation download/install
2022-07-26 14:44:25 -04:00
b8a31a8be9 support for module translation download/install 2022-07-26 14:44:06 -04:00
02c30d6454 Merge pull request #2312 from sbwalker/dev
add ability to supply connection string in Add Site
2022-07-26 10:13:14 -04:00
985f003e6d add ability to supply connection string in Add Site 2022-07-26 10:12:54 -04:00
98045e1e2e Merge pull request #2311 from sbwalker/dev
introduce ITransientService interface for auto registration of transient services (for DBContexts and Repositories)
2022-07-26 09:42:12 -04:00
5762ce58a4 introduce ITransientService interface for auto registration of transient services (for DBContexts and Repositories) 2022-07-26 09:41:42 -04:00
2787ee71fc Merge pull request #2310 from sbwalker/dev
Allow for entry of raw connection string during installation
2022-07-26 07:48:29 -04:00
e61a6df4d7 Allow for entry of raw connection string during installation 2022-07-26 07:48:04 -04:00
0a5c2ecbf5 Merge pull request #2304 from sbwalker/dev
optimize satellite assembly loading based on the new model where all cultures are available
2022-07-21 16:02:48 -04:00
6bfab696ad optimize satellite assembly loading based on the new model where all cultures are available 2022-07-21 16:02:23 -04:00
928f2dd496 Merge pull request #2302 from chlupac/FileServiceFix
FileService fix
2022-07-20 08:23:41 -04:00
bcf75892f7 FileService fix 2022-07-20 10:39:13 +02:00
594761385f Merge pull request #2301 from sbwalker/dev
add Environment to System Info
2022-07-19 14:34:10 -04:00
d05fba06ec add Environment to System Info 2022-07-19 14:33:51 -04:00
25155b1b38 Merge pull request #2295 from chlupac/AppSettingsFix
Fixed loading of alternative appsettings "appsettings.{env.EnvironmentName}.json"
2022-07-19 14:20:08 -04:00
ded6c9c199 Merge pull request #2299 from chlupac/InstallManFix
Fix - InstallationManager crash when package folders are missing
2022-07-19 13:12:43 -04:00
c62e6c0045 Merge pull request #2300 from sbwalker/dev
performance optimization for permissions
2022-07-19 10:49:52 -04:00
b3feda9fd1 performance optimization for permissions 2022-07-19 10:49:33 -04:00
7ef8e2c8b8 Fix - InstallationManager crash when package folders are missing 2022-07-19 09:42:12 +02:00
d5ff211871 Fixed loading of alternative appsettings "appsettings.{env.EnvironmentName}.json" 2022-07-18 21:34:59 +02:00
51c23e3842 Merge pull request #2294 from sbwalker/dev
use package name as a convention for identifying satellite assemblies
2022-07-18 13:14:53 -04:00
557b30815e use package name as a convention for identifying satellite assemblies 2022-07-18 13:14:34 -04:00
145459bfc3 Merge pull request #2292 from sbwalker/dev
Added version to Language Management, improved framework performance by loading languages into PageState, include all supported cultures and allow Administrator to add any language to a site regardless of translation availability, fix translation upgrade issue
2022-07-16 10:00:20 -04:00
f97a6a2bee Added version to Language Management, improved framework performance by loading languages into PageState, include all supported cultures and allow Administrator to add any language to a site regardless of translation availability, fix translation upgrade issue 2022-07-16 09:59:47 -04:00
1134422891 Merge pull request #2291 from sbwalker/dev
Fix #2282 - dynamically determine framework path when scaffolding project references
2022-07-15 16:00:23 -04:00
6012275c7b Fix #2282 - dynamically determine framework path when scaffolding project references 2022-07-15 15:59:55 -04:00
2f07063375 Merge pull request #2288 from sbwalker/dev
Fix #2285 - handle scenario where the module definition associated to a module instance does not exist
2022-07-14 16:58:43 -04:00
310d1ed485 Fix #2285 - handle scenario where the module definition associated to a module instance does not exist 2022-07-14 16:58:16 -04:00
a48edbb16e Merge pull request #2287 from sbwalker/dev
fixed issue in default site template where MIT License module was being created in invalid pane
2022-07-14 09:11:16 -04:00
d6258409fc fixed issue in default site template where MIT License module was being created in invalid pane 2022-07-14 09:10:51 -04:00
1b94b1247b Merge pull request #2284 from sbwalker/dev
Fix #2280 - add 404 page on upgrade, Fix #2279 add message indicating a restart is required to activate scheduled jobs after installation, add Package Name to Module and Theme management
2022-07-13 15:19:07 -04:00
9ef63ae60e Fix #2280 - add 404 page on upgrade, Fix #2279 add message indicating a restart is required to activate scheduled jobs after installation, add Package Name to Module and Theme management 2022-07-13 15:18:41 -04:00
f99de4be48 Merge pull request #2272 from sbwalker/dev
Support for module editors by exposing Edit Mode in the Control Panel
2022-07-06 17:25:43 -04:00
80fd1820c2 Support for module editors by exposing Edit Mode in the Control Panel 2022-07-06 17:25:08 -04:00
0a4a983d20 Merge pull request #2269 from leigh-pointer/Resx
Added Missing Resource from Oqtane Theme Settings
2022-07-06 08:37:25 -04:00
c2cc830691 Added Missing Resource from Oqtane Theme Settings
Missing Resource string added
2022-07-04 21:37:42 +02:00
4d0490d1c6 Merge pull request #2260 from sbwalker/dev
FIx issue with redirect after site delete and remove tenant if it is empty
2022-06-28 08:17:39 -04:00
02d1838547 FIx issue with redirect after site delete and remove tenant if it is empty 2022-06-28 08:17:06 -04:00
5c6edff778 Update README.md 2022-06-27 16:35:50 -04:00
7cbca32ddd Update README.md 2022-06-27 16:16:58 -04:00
8950e315f8 Merge pull request #2258 from oqtane/master
Merge pull request #2257 from oqtane/dev
2022-06-27 16:12:27 -04:00
a703df40c0 Merge pull request #2257 from oqtane/dev
3.1.3 release
2022-06-27 16:12:05 -04:00
225fce8810 Merge pull request #2256 from sbwalker/dev
3.1.3 data provider packages
2022-06-27 15:43:42 -04:00
bdc0f0fcdd 3.1.3 data provider packages 2022-06-27 15:43:24 -04:00
5bbb8c4858 Merge pull request #2255 from sbwalker/dev
Add schema support to BaseEntityBuilder
2022-06-27 13:55:08 -04:00
35b9551bfb Add schema support to BaseEntityBuilder 2022-06-27 13:54:52 -04:00
c8c5a05b39 Merge pull request #2254 from sbwalker/dev
Fix #2249 Fix #2250 - issues with site deletion
2022-06-27 13:46:02 -04:00
2771f0301a Fix #2249 Fix #2250 - issues with site deletion 2022-06-27 13:45:42 -04:00
c4f04edc59 Merge pull request #2253 from sbwalker/dev
Fix #2252 - unable to insert images into rich text editor
2022-06-27 12:24:16 -04:00
5530422846 Fix #2252 - unable to insert images into rich text editor 2022-06-27 12:23:55 -04:00
ce77c81fb5 Merge pull request #2248 from sbwalker/dev
prepare for 3.1.3
2022-06-21 09:25:33 -04:00
bc488d4ac2 prepare for 3.1.3 2022-06-21 09:25:14 -04:00
fe72a10346 Merge pull request #2247 from sbwalker/dev
fix #2239 - email notification encoding to support all cultures
2022-06-20 19:42:50 -04:00
0da88398b4 fix #2239 - email notification encoding to support all cultures 2022-06-20 19:42:32 -04:00
c42de3da20 Merge pull request #2246 from sbwalker/dev
fix #2245 - default database type not set correctly when adding new site for any DB other than LocalDB, added Source: info to all extension installation scenarios now that the Registry supports both Nuget and GitHub locations
2022-06-20 17:45:10 -04:00
4bf9f36baa fix #2245 - default database type not set correctly when adding new site for any DB other than LocalDB, added Source: info to all extension installation scenarios now that the Registry supports both Nuget and GitHub locations 2022-06-20 17:44:49 -04:00
380cb192c7 Merge pull request #2244 from sbwalker/dev
allow multiple aliases to be defined as default
2022-06-18 09:18:47 -04:00
8882e19ec5 allow multiple aliases to be defined as default 2022-06-18 09:18:23 -04:00
7f52059b98 Merge pull request #2243 from sbwalker/dev
added extension method for creating a LocalizerFactory using a type name, refactored Pager and LocalizableComponent to use LocalizerFactory
2022-06-15 16:19:51 -04:00
1ce3cc4d7c added extension method for creating a LocalizerFactory using a type name, refactored Pager and LocalizableComponent to use LocalizerFactory 2022-06-15 16:19:22 -04:00
657c71e94d Merge pull request #2242 from leigh-pointer/Issue#2234
Fix for Recycle bin not showing Deleted Date (Issue #2234)
2022-06-14 10:26:50 -04:00
c8cfb3c7b7 Fix for Recycle bin not showing Deleted Date (Issue #2234)
Added the Deleted data to Module from the PageModule
2022-06-14 09:22:12 +02:00
6e7e90acf4 Merge pull request #2241 from sbwalker/dev
additional changes for #2228
2022-06-13 09:10:20 -04:00
6d3a556d34 additional changes for #2228 2022-06-13 09:10:01 -04:00
7d9188b659 Merge pull request #2238 from chlupac/LogExceptions
Exception is not saved to log
2022-06-09 15:13:10 -04:00
4f0a805c79 Exception is not saved to log 2022-06-09 10:24:13 +02:00
53f3320492 Merge pull request #2228 from chlupac/Log_notification_improvement
Log notification improvement
2022-06-08 15:48:17 -04:00
f9ce51b4a5 Merge pull request #2222 from hishamco/pager
Pager should inherits from LocalizableComponent
2022-06-08 15:47:25 -04:00
f8bf432c0d Merge pull request #2237 from sbwalker/dev
Improvements for #2229 - relax userrole restrictions
2022-06-08 15:47:12 -04:00
a822482172 Improvements for #2229 - relax userrole restrictions 2022-06-08 15:46:36 -04:00
b22f8a0b02 Remove generic type suffix properly 2022-06-08 00:12:10 +03:00
227331bf24 Merge branch 'dev' into pager 2022-06-07 23:37:08 +03:00
744688cbe1 Merge pull request #2235 from sbwalker/dev
Fix #2230 - add support for an Unauthenticated User global role
2022-06-07 15:26:05 -04:00
79c8126c4a Fix #2230 - add support for an Unauthenticated User global role 2022-06-07 15:25:44 -04:00
0b7c8e4ef7 Merge pull request #2232 from ijaz-saeed/dev
missing translation keys for module names
2022-06-07 15:23:08 -04:00
35e00f61d8 Update README.md 2022-06-07 09:38:42 -04:00
aba3d58df8 missing translation keys for module names 2022-06-06 10:12:00 +05:00
45984a8166 Merge pull request #2231 from sbwalker/dev
Improvements for #2221 - validate file extensions client-side before initiating upload, validate file extension server-side before writing part to disk, optimize part cleanup logic, add error handling to JavaScript XMLHttpRequest, ensure FileInput gets initialized after upload
2022-06-04 15:41:38 -04:00
ea5655ae42 Improvements for #2221 - validate file extensions client-side before initiating upload, valid file extension server-side before writing part to disk, optimize cleanup logic, add error handling to JavaScript XMLHttpRequest, ensure FileInput gets initialized after upload 2022-06-04 15:40:26 -04:00
f06cb0dfbb Log notification improvement 2022-05-31 11:33:42 +02:00
1abae55976 Remove magic string 2022-05-31 11:59:17 +03:00
a83ed40ec4 Avoid breaking changes 2022-05-31 10:40:42 +03:00
75ecae4672 Merge pull request #2223 from hishamco/toggle-password
Avoid toggle password & confirm password as same time when one button is clicked
2022-05-28 21:59:40 -04:00
16a6f942c5 Avoid toggle password & confirm password as same time when one button is clicked 2022-05-27 16:20:35 +03:00
aa98508e57 Remove the entry from SharedResources 2022-05-27 15:37:13 +03:00
583383aee1 Pager should inherits from LocalizableComponent 2022-05-27 15:33:48 +03:00
13f69f81d7 Merge pull request #2219 from sbwalker/dev
fix #2213 - disabling show on all pages
2022-05-26 01:19:35 -04:00
43c34fcd64 fix #2213 - disabling show on all pages 2022-05-26 01:19:14 -04:00
c272238539 Merge pull request #2217 from leigh-pointer/MissingRes2215
Fix for missing Resx entries #2215
2022-05-25 07:23:01 -04:00
6e0d2706a8 Fix for missing Resx entries #2215
Updated the Resx File with the missing entries.  

However the UserProfile\Add cant be Localized at present as the IStringLocalizer is not loaded when that property assignment is executed.
2022-05-25 11:13:38 +02:00
25a7289ce8 Merge pull request #2210 from leigh-pointer/PageOf
Fix for #2209 Localization Pager component (Page @_page of @_pages)
2022-05-24 21:47:12 -04:00
1ba7d045e4 Merge pull request #2211 from elgransan/dev
* Collapse menu after click on a page (mobile version)
2022-05-24 21:46:11 -04:00
a4d75befe7 * Collapse menu after click on a page (mobile version) 2022-05-22 20:05:01 -03:00
88377529bc Fix for #2209 Localization Pager component (Page @_page of @_pages)
Added fixe for issue.  Added the resource "PageOfPages" to SharedResources as trying to inject IStringLocalizer<Pager<TableItem>> and adding Pager.resx the resource failed to load.
2022-05-22 20:23:17 +02:00
91b9a0280f Merge pull request #2207 from sbwalker/dev
Add filtering by Category to Module Management - default to Common
2022-05-21 10:11:24 -04:00
25173ae85c Ddd filtering by Category to Module Management - default to Common 2022-05-21 10:10:57 -04:00
ad3350705e Update README.md 2022-05-19 11:44:38 -04:00
a16bc5db28 Merge pull request #2200 from oqtane/master
Merge pull request #2199 from oqtane/dev
2022-05-14 09:49:24 -04:00
51657338f5 Merge pull request #2199 from oqtane/dev
3.1.2 release
2022-05-14 09:48:59 -04:00
0fe3ea25af Merge pull request #2198 from sbwalker/dev
remove columns from main user management view  and migrate them to edit view
2022-05-13 17:00:32 -04:00
806daaf7c9 remove columns from main user management view and migrate them to edit view 2022-05-13 17:00:10 -04:00
21ff4a83b5 Merge pull request #2197 from sbwalker/dev
resolve login issue related to 'LoginOptions:TwoFactor' and order list of files alphabetically
2022-05-13 12:03:57 -04:00
ecc9aa40d7 resolve login issue related to 'LoginOptions:TwoFactor' and order list of files alphabetically 2022-05-13 12:03:34 -04:00
c34ca2a59b Merge pull request #2196 from sbwalker/dev
prepare for 3.1.2
2022-05-12 20:55:26 -04:00
dde7094fe3 prepare for 3.1.2 2022-05-12 20:55:11 -04:00
105afdfefc Merge pull request #2195 from sbwalker/dev
fix #2192 - Adding a new site fails
2022-05-12 20:42:20 -04:00
4c254a8686 fix #2192 - Adding a new site fails 2022-05-12 20:42:05 -04:00
33ca203e57 Merge pull request #2194 from sbwalker/dev
updated resource file
2022-05-12 13:56:01 -04:00
2ff4133cd4 updated resource file 2022-05-12 13:55:47 -04:00
49ad85713e Merge pull request #2193 from sbwalker/dev
add support for external login parameters and improve diagnostic messages related to claims
2022-05-12 13:52:06 -04:00
1978bf151f add support for external login parameters and improve diagnostic messages related to claims 2022-05-12 13:51:46 -04:00
506378de82 Merge pull request #2191 from sbwalker/dev
fix #2185 - alias auto registration including trailing slash
2022-05-10 08:03:55 -04:00
53ead7a03f fix #2185 - alias auto registration including trailing slash 2022-05-10 08:03:38 -04:00
ab979fd63c Merge pull request #2189 from sbwalker/dev
fix #2180 - Error in Module Creator if the template is not set
2022-05-09 11:35:17 -04:00
1e84a2238b fix #2180 - Error in Module Creator if the template is not set 2022-05-09 11:35:01 -04:00
5618adf86c Merge pull request #2187 from sbwalker/dev
fix #2182 - modifications to address MySQL compatibility issues
2022-05-08 22:14:54 -04:00
345b0bc95f fix #2182 - modifications to address MySQL compatibility issues 2022-05-08 22:14:26 -04:00
b1d6c35e99 Merge pull request #2183 from leigh-pointer/UserEmail
Args not in sink
2022-05-06 11:43:59 -04:00
d767f1a101 Args not in sink
The Display name and email address  not is the correct order!
2022-05-06 12:43:40 +02:00
c15f2b9a12 Merge pull request #2178 from leigh-pointer/UserEmail
Added the User Email field to the List
2022-05-05 17:14:08 -04:00
a21a53662b Update for real-estate
Removed Name
Removed Seconds from DateTime fields
Added Name to the Email link
2022-05-05 16:35:43 +02:00
ebb5340019 Merge pull request #2177 from leigh-pointer/NotIficationDateFormat
Updated the CreatedOn date format
2022-05-05 10:13:24 -04:00
6108bd214e Merge pull request #2179 from sbwalker/dev
fix #2176 - update LastIPAddress correctly during login
2022-05-05 09:57:26 -04:00
eed27e101a fix #2176 - update LastIPAddress correctly during login 2022-05-05 09:57:09 -04:00
2767680bed Added the User Email field to the List
Added the formatted email address of the user to the list view.
2022-05-05 13:25:35 +02:00
4080e30b6f Updated the CreatedOn date format
Updated the format to a more readable format of dd-MMM-yyyy
2022-05-05 13:07:09 +02:00
e89257be62 Merge pull request #2174 from sbwalker/dev
fix #2172 - File Upload issue caused by JS Interop not passing AntiForgery token in POST method
2022-05-04 17:15:10 -04:00
d3c40a7e8b fix #2172 - File Upload issue caused by JS Interop not passing AntiForgery token in POST methid 2022-05-04 17:14:45 -04:00
60657d5d25 Update README.md 2022-05-03 08:13:34 -04:00
136 changed files with 2599 additions and 1655 deletions

View File

@ -1,3 +1,5 @@
using System;
namespace Microsoft.Extensions.Localization
{
public static class OqtaneLocalizationExtensions
@ -18,5 +20,42 @@ namespace Microsoft.Extensions.Localization
}
return localizedValue;
}
/// <summary>
/// Creates an IStringLocalizer based on a type name. This extension method is useful in scenarios where the default IStringLocalizer is unable to locate the resources.
/// </summary>
/// <param name="localizerFactory"></param>
/// <param name="fullTypeName">the full type name ie. GetType().FullName</param>
/// <returns></returns>
public static IStringLocalizer Create(this IStringLocalizerFactory localizerFactory, string fullTypeName)
{
var typename = fullTypeName;
// handle generic types
var type = Type.GetType(fullTypeName);
if (type.IsGenericType)
{
typename = type.GetGenericTypeDefinition().FullName;
typename = typename.Substring(0, typename.IndexOf("`")); // remove generic type info
}
// format typename
if (typename.Contains(","))
{
typename = typename.Substring(0, typename.IndexOf(",")); // remove assembly info
}
// remove rootnamespace
var rootnamespace = "";
var attributes = type.Assembly.GetCustomAttributes(typeof(RootNamespaceAttribute), false);
if (attributes.Length > 0)
{
rootnamespace = ((RootNamespaceAttribute)attributes[0]).RootNamespace;
}
typename = typename.Replace(rootnamespace + ".", "");
// create IStringLocalizer using factory
return localizerFactory.Create(typename, type.Assembly.GetName().Name);
}
}
}

View File

@ -4,7 +4,7 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service (ie. servername.database.windows.net,1433) " ResourceKey="Server">Server:</Label>
<div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" />
</div>
@ -51,7 +51,7 @@
@if (_encryption == "true")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption" ResourceKey="TrustServerCertificate">Certificate:</Label>
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True." ResourceKey="TrustServerCertificate">Certificate:</Label>
<div class="col-sm-9">
<select id="encryption" class="form-select custom-select" @bind="@_trustservercertificate">
<option value="true">@Localizer["Self Signed"]</option>

View File

@ -26,29 +26,42 @@
<div class="col-sm-9">
@if (_databases != null)
{
<select id="databasetype" class="form-select custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
@foreach (var database in _databases)
{
if (database.IsDefault)
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
}
</select>
<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 (_databaseConfigType != null)
{
@DatabaseConfigComponent;
}
}
</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">String:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
</div>
</div>
}
</div>
</div>
<div class="col text-center">
<h2>@Localizer["ApplicationAdmin"]</h2><br />
@ -63,8 +76,8 @@
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglePassword</button>
</div>
</div>
</div>
@ -72,8 +85,8 @@
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword">@_toggleConfirmPassword</button>
</div>
</div>
</div>
@ -107,11 +120,15 @@
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _passwordType = "password";
private string _confirmPasswordType = "password";
private string _togglePassword = string.Empty;
private string _toggleConfirmPassword = string.Empty;
private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty;
private bool _register = true;
@ -120,7 +137,9 @@
protected override async Task OnInitializedAsync()
{
_togglepassword = SharedLocalizer["ShowPassword"];
_togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
{
@ -138,7 +157,7 @@
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
@ -175,9 +194,16 @@
private async Task Install()
{
var connectionString = String.Empty;
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
if (_showConnectionString)
{
connectionString = databaseConfigControl.GetConnectionString();
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
}
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
@ -221,24 +247,48 @@
{
_message = Localizer["Message.Password.Invalid"];
}
}
else
{
_message = Localizer["Message.Require.DbInfo"];
}
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
_message = Localizer["Message.Require.DbInfo"];
}
}
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
private void ToggleConfirmPassword()
{
if (_confirmPasswordType == "password")
{
_confirmPasswordType = "text";
_toggleConfirmPassword = SharedLocalizer["HidePassword"];
}
else
{
_confirmPasswordType = "password";
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
}
}
private void ToggleConnectionString()
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
_connectionString = databaseConfigControl.GetConnectionString();
}
_showConnectionString = !_showConnectionString;
}
}

View File

@ -11,8 +11,7 @@
Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
string message = string.Format(Localizer["Error.Module.Load"], module.ModuleDefinitionName);
AddModuleMessage(message, MessageType.Error);
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], module.ModuleDefinitionName), MessageType.Error);
}
await logger.LogCritical("Error Loading Module {Module}", module);

View File

@ -56,6 +56,10 @@ else
protected override async Task OnParametersSetAsync()
{
_jobs = await JobService.GetJobsAsync();
if (_jobs.Count == 0)
{
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
}
}
private string DisplayStatus(bool isEnabled, bool isExecuting)

View File

@ -20,6 +20,7 @@ else
@if (_availableCultures.Count() == 0)
{
<ModuleMessage Type="MessageType.Info" Message="@_message"></ModuleMessage>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
}
else
{
@ -31,7 +32,7 @@ else
<select id="_code" class="form-select" @bind="@_code" required>
@foreach (var culture in _availableCultures)
{
<option value="@culture.Name">@culture.DisplayName</option>
<option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option>
}
</select>
</div>
@ -47,11 +48,11 @@ else
</div>
</div>
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
<TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host">
<TabPanel Name="Translations" Heading="Translations" ResourceKey="Download" Security="SecurityAccessLevel.Host">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="input-group">
@ -78,6 +79,7 @@ else
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@ -113,7 +115,7 @@ else
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Language: </Label>
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Translation: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
</div>
@ -159,33 +161,32 @@ else
}
@code {
private ElementReference form;
private bool validated = false;
private ElementReference form;
private bool validated = false;
private string _code = string.Empty;
private string _isDefault = "False";
private string _message;
private IEnumerable<Culture> _supportedCultures;
private IEnumerable<Culture> _availableCultures;
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
private string _code = string.Empty;
private string _isDefault = "False";
private string _message;
private IEnumerable<Culture> _supportedCultures;
private IEnumerable<Culture> _availableCultures;
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var languagesCodes = languages.Select(l => l.Code).ToList();
protected override async Task OnParametersSetAsync()
{
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var languagesCodes = languages.Select(l => l.Code).ToList();
_supportedCultures = await LocalizationService.GetCulturesAsync();
_availableCultures = _supportedCultures
.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
await LoadTranslations();
_supportedCultures = await LocalizationService.GetCulturesAsync();
_availableCultures = _supportedCultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
await LoadTranslations();
if (_supportedCultures.Count() == 1)
{

View File

@ -19,15 +19,17 @@ else
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th>
<th>@Localizer["Translation"]</th>
<th>@Localizer["Default"]</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
<td>@context.Name</td>
<td>@context.Code</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td>
<td>@context.Version</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td>
@if (UpgradeAvailable(context.Code))
{
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
@ -50,9 +52,6 @@ else
var cultures = await LocalizationService.GetCulturesAsync();
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
// Adds English as default language
_languages.Insert(0, new Language { Name = culture.DisplayName, Code = culture.Name, IsDefault = !_languages.Any(l => l.IsDefault) });
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
@ -81,7 +80,7 @@ else
var upgradeavailable = false;
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + ".Client." + code)).FirstOrDefault();
var package = _packages.Where(item => item.PackageId == ("Oqtane.Client." + code)).FirstOrDefault();
if (package != null)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0);

View File

@ -184,7 +184,7 @@
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password};
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (!twofactor)
{
@ -206,7 +206,7 @@
}
else
{
if (PageState.Site.Settings["LoginOptions:TwoFactor"] == "required" || user.TwoFactorRequired)
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired)
{
twofactor = true;
validated = false;

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Logs
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ILogService LogService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@ -91,19 +92,23 @@ else
}
@code {
private string _level = "-";
private string _function = "-";
private string _rows = "10";
private string _level = "-";
private string _function = "-";
private string _rows = "10";
private int _page = 1;
private List<Log> _logs;
private string _retention = "";
private List<Log> _logs;
private string _retention = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
protected override async Task OnInitializedAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id))
{
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"id={id}"));
}
if (PageState.QueryString.ContainsKey("level"))
{
_level = PageState.QueryString["level"];

View File

@ -76,64 +76,71 @@ else
}
@code {
private ElementReference form;
private bool validated = false;
private string _moduledefinitionname = string.Empty;
private string _owner = string.Empty;
private string _module = string.Empty;
private string _description = string.Empty;
private List<Template> _templates;
private string _template = "-";
private string[] _versions;
private string _reference = Constants.Version;
private string _minversion = "2.0.0";
private string _location = string.Empty;
private ElementReference form;
private bool validated = false;
private string _moduledefinitionname = string.Empty;
private string _owner = string.Empty;
private string _module = string.Empty;
private string _description = string.Empty;
private List<Template> _templates;
private string _template = "-";
private string[] _versions;
private string _reference = "local";
private string _minversion = "2.0.0";
private string _location = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override void OnInitialized()
{
_moduledefinitionname = SettingService.GetSetting(ModuleState.Settings, "ModuleDefinitionName", "");
if (string.IsNullOrEmpty(_moduledefinitionname))
{
AddModuleMessage(Localizer["Info.Module.Creator"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Info.Module.Activate"], MessageType.Info);
}
}
protected override void OnInitialized()
{
_moduledefinitionname = SettingService.GetSetting(ModuleState.Settings, "ModuleDefinitionName", "");
if (string.IsNullOrEmpty(_moduledefinitionname))
{
AddModuleMessage(Localizer["Info.Module.Creator"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Info.Module.Activate"], MessageType.Info);
}
}
protected override async Task OnParametersSetAsync()
{
try
{
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Module Creator");
}
}
protected override async Task OnParametersSetAsync()
{
try
{
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Module Creator");
}
}
private async Task CreateModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
private async Task CreateModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
{
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
}
}
catch (Exception ex)
{

View File

@ -35,6 +35,7 @@
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@ -90,9 +91,9 @@
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
@if (!string.IsNullOrEmpty(_packagelicense))
{
@((MarkupString)_license)
@((MarkupString)_packagelicense)
}
else
{
@ -114,145 +115,145 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _packageid = "";
private string _packagelicense = "";
private string _packageversion = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Package.Load"], MessageType.Error);
}
}
protected override async Task OnInitializedAsync()
{
try
{
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Package.Load"], MessageType.Error);
}
}
private async Task LoadModuleDefinitions()
{
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "");
private async Task LoadModuleDefinitions()
{
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "");
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
}
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
}
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadModuleDefinitions();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadModuleDefinitions();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
private async Task Search()
{
try
{
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
private async Task Search()
{
try
{
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
private async Task Reset()
{
try
{
_search = "";
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
}
private async Task Reset()
{
try
{
_search = "";
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
}
private void HideModal()
{
_productname = "";
_license = "";
StateHasChanged();
}
private void HideModal()
{
_productname = "";
_packagelicense = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageversion = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Module.Download"], MessageType.Success);
_productname = "";
_license = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _packageversion, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
AddModuleMessage(Localizer["Success.Module.Download"], MessageType.Success);
_productname = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task InstallModules()
{
try
{
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Module");
}
}
private async Task InstallModules()
{
try
{
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Modules");
}
}
}

View File

@ -151,7 +151,7 @@
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
}
GetLocation();
GetLocation();
}
private void GetLocation()

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Modules.Admin.ModuleDefinitions
@inherits ModuleBase
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -43,7 +44,13 @@
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<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 module was installed" 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 module" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
@ -74,7 +81,13 @@
</div>
</div>
</div>
</Section>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@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">
<div class="container">
@ -82,24 +95,100 @@
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
<TabPanel Name="Translations" ResourceKey="Translations">
@if (_packages != null)
{
if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
}
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@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>
@if (_productname != "")
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_packagelicense))
{
@((MarkupString)_packagelicense)
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadTranslation">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
@code {
private ElementReference form;
private bool validated = false;
private int _moduleDefinitionId;
private string _name;
private string _version;
private string _categories;
private string _description = "";
private string _categories;
private string _moduledefinitionname = "";
private string _description = "";
private string _owner = "";
private string _version;
private string _packagename = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
@ -114,6 +203,12 @@
private PermissionGrid _permissionGrid;
#pragma warning restore 649
private List<Package> _packages;
private string _productname = "";
private string _packageid = "";
private string _packagelicense = "";
private string _packageversion = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
@ -125,11 +220,12 @@
if (moduleDefinition != null)
{
_name = moduleDefinition.Name;
_version = moduleDefinition.Version;
_description = moduleDefinition.Description;
_categories = moduleDefinition.Categories;
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_description = moduleDefinition.Description;
_owner = moduleDefinition.Owner;
_version = moduleDefinition.Version;
_packagename = moduleDefinition.PackageName;
_owner = moduleDefinition.Owner;
_url = moduleDefinition.Url;
_contact = moduleDefinition.Contact;
_license = moduleDefinition.License;
@ -139,7 +235,9 @@
_createdon = moduleDefinition.CreatedOn;
_modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn;
}
_packages = await PackageService.GetPackagesAsync("translation", "", "", moduleDefinition.PackageName);
}
}
catch (Exception ex)
{
@ -185,4 +283,66 @@
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void HideModal()
{
_productname = "";
_packagelicense = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageversion = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task DownloadTranslation()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success);
_productname = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
}

View File

@ -12,11 +12,32 @@
}
else
{
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
<div class="container">
<div class="row mb-3 align-items-center">
<div class="col-sm-6">
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
</div>
<div class="col-sm-6">
<select class="form-select" @onchange="(e => CategoryChanged(e))">
@foreach (var category in _categories)
{
if (category == _category)
{
<option value="@category" selected>@category @Localizer["Modules"]</option>
}
else
{
<option value="@category">@category @Localizer["Modules"]</option>
}
}
</select>
</div>
</div>
</div>
<Pager Items="@_moduleDefinitions">
<Pager Items="@_moduleDefinitions.Where(item => item.Categories.Contains(_category))">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
@ -30,9 +51,9 @@ else
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
<td>
@if (context.AssemblyName != "Oqtane.Client")
{
{
<ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete", context.Name])" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" />
}
}
</td>
<td>@context.Name</td>
<td>@context.Version</td>
@ -65,6 +86,8 @@ else
@code {
private List<ModuleDefinition> _moduleDefinitions;
private List<Package> _packages;
private List<string> _categories = new List<string>();
private string _category = "Common";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -74,6 +97,7 @@ else
{
_moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module");
_categories = _moduleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
catch (Exception ex)
{
@ -115,38 +139,44 @@ else
{
return package.Version;
}
}
return version;
}
}
return version;
}
private async Task DownloadModule(string packagename, string version)
{
try
{
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadModule(string packagename, string version)
{
try
{
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DeleteModule(ModuleDefinition moduleDefinition)
{
try
{
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message);
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
}
}
private async Task DeleteModule(ModuleDefinition moduleDefinition)
{
try
{
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message);
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
}
}
private void CategoryChanged(ChangeEventArgs e)
{
_category = (string)e.Value;
StateHasChanged();
}
}

View File

@ -90,116 +90,125 @@
</form>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
private ElementReference form;
private bool validated = false;
private List<Theme> _themes;
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _title;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private string _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
private RenderFragment ContainerSettingsComponent { get; set; }
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private ElementReference form;
private bool validated = false;
private List<Theme> _themes;
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _title;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private string _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
private RenderFragment ContainerSettingsComponent { get; set; }
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
protected override async Task OnInitializedAsync()
{
_title = ModuleState.Title;
_themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.Permissions;
_permissionNames = ModuleState.ModuleDefinition.PermissionNames;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
protected override async Task OnInitializedAsync()
{
_title = ModuleState.Title;
_themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.Permissions;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
}
else
{
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
}
if (_moduleSettingsType != null)
{
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
if (!string.IsNullOrEmpty(moduleobject.Title))
{
_moduleSettingsTitle = moduleobject.Title;
}
if (ModuleState.ModuleDefinition != null)
{
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
ModuleSettingsComponent = builder =>
{
builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent();
};
}
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
}
else
{
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
}
if (_moduleSettingsType != null)
{
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
if (!string.IsNullOrEmpty(moduleobject.Title))
{
_moduleSettingsTitle = moduleobject.Title;
}
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);
if (_containerSettingsType != null)
{
ContainerSettingsComponent = builder =>
{
builder.OpenComponent(0, _containerSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
builder.CloseComponent();
};
}
}
}
ModuleSettingsComponent = builder =>
{
builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent();
};
}
}
else
{
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
}
private async Task SaveModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (!string.IsNullOrEmpty(_title))
{
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);
if (_containerSettingsType != null)
{
ContainerSettingsComponent = builder =>
{
builder.OpenComponent(0, _containerSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
builder.CloseComponent();
};
}
}
}
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
private async Task SaveModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (!string.IsNullOrEmpty(_title))
{
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.PageModuleId = ModuleState.PageModuleId;
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
if (_moduleSettingsType != null)
{

View File

@ -59,7 +59,7 @@ else
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
_roles = _roles.Where(item => item.Name != RoleNames.Everyone).ToList();
_roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
}
else
{

View File

@ -170,24 +170,54 @@
@if (_aliases != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliases" HelpText="The list of aliases for this site" ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
<Pager Items="@_aliases">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["AliasName"]</th>
<th>@Localizer["AliasDefault"]</th>
</Header>
<Row>
@if (context.AliasId != _aliasid)
{
<td>
@if (_aliasid == -1)
{
<button type="button" class="btn btn-primary" @onclick="@(() => EditAlias(context))">@SharedLocalizer["Edit"]</button>
}
</td>
<td>
@if (_aliasid == -1)
{
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteModule" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
}
</td>
<td>@context.Name</td>
<td>@context.IsDefault</td>
}
else
{
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SaveAlias())">@SharedLocalizer["Save"]</button></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelAlias())">@SharedLocalizer["Cancel"]</button></td>
<td>
<input id="aliasname" class="form-control" @bind="@_aliasname" />
</td>
<td>
<select id="defaultaias" class="form-select" @bind="@_defaultalias" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
}
</Row>
</Pager>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultalias" HelpText="The default alias for the site. Requests for non-default aliases will be redirected to the default alias." ResourceKey="DefaultAlias">Default Alias: </Label>
<div class="col-sm-9">
<select id="defaultalias" class="form-select" @bind="@_defaultalias" required>
@foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray())
{
<option value="@name">@name</option>
}
</select>
</div>
</div>
</div>
</Section>
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
@ -253,8 +283,9 @@
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
private List<Alias> _aliases;
private string _defaultalias = string.Empty;
private string _urls = string.Empty;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _runtime = "";
private string _prerender = "";
private int _logofileid = -1;
@ -304,10 +335,7 @@
_prerender = site.RenderMode.Replace(_runtime, "");
_isdeleted = site.IsDeleted.ToString();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await GetAliases();
}
await GetAliases();
if (site.LogoFileId != null)
{
@ -409,152 +437,92 @@
{
if (_name != string.Empty && _themetype != "-" && _containertype != "-")
{
var unique = true;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
_urls = Regex.Replace(_urls, @"\r\n?|\n", ","); // convert line breaks to commas
var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray())
bool refresh = false;
bool reload = false;
site.Name = _name;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var alias = aliases.Where(item => item.Name == name).FirstOrDefault();
if (alias != null && unique)
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
unique = (alias.TenantId == PageState.Site.TenantId && alias.SiteId == PageState.Site.SiteId);
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
reload = true; // needs to be reloaded on server
}
}
if (unique && string.IsNullOrEmpty(_defaultalias)) unique = false;
}
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
if (unique)
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{
bool refresh = false;
bool reload = false;
site.Name = _name;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
reload = true; // needs to be reloaded on server
}
}
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{
site.LogoFileId = logofileid;
}
int? faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId == -1) faviconFieldId = null;
if (site.FaviconFileId != faviconFieldId)
{
site.FaviconFileId = faviconFieldId;
reload = true; // needs to be reloaded on server
}
if (site.DefaultThemeType != _themetype)
{
site.DefaultThemeType = _themetype;
refresh = true; // needs to be refreshed on client
}
if (site.DefaultContainerType != _containertype)
{
site.DefaultContainerType = _containertype;
refresh = true; // needs to be refreshed on client
}
site.AdminContainerType = _admincontainertype;
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
reload = true; // needs to be reloaded on server
}
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid == -1) pwaappiconfileid = null;
if (site.PwaAppIconFileId != pwaappiconfileid)
{
site.PwaAppIconFileId = pwaappiconfileid;
reload = true; // needs to be reloaded on server
}
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
if (site.PwaSplashIconFileId != pwasplashiconfileid)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
reload = true; // needs to be reloaded on server
}
site = await SiteService.UpdateSiteAsync(site);
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, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(sValue => sValue.Trim()).ToArray();
foreach (Alias alias in _aliases)
{
if (!names.Contains(alias.Name.Trim()))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
}
}
foreach (string name in names)
{
var alias = _aliases.Find(item => item.Name.Trim() == name);
if (alias == null)
{
alias = new Alias();
alias.Name = name;
alias.TenantId = site.TenantId;
alias.SiteId = site.SiteId;
alias.IsDefault = (name == _defaultalias);
await AliasService.AddAliasAsync(alias);
}
else
{
if (alias.Name != name || alias.IsDefault != (alias.Name.Trim() == _defaultalias))
{
alias.Name = name;
alias.IsDefault = (name == _defaultalias);
await AliasService.UpdateAliasAsync(alias);
}
}
}
await GetAliases();
}
await logger.LogInformation("Site Settings Saved {Site}", site);
if (refresh || reload)
{
NavigationManager.NavigateTo(NavigateUrl(true), reload); // refresh/reload
}
else
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await interop.ScrollTo(0, 0, "smooth");
}
site.LogoFileId = logofileid;
}
int? faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId == -1) faviconFieldId = null;
if (site.FaviconFileId != faviconFieldId)
{
site.FaviconFileId = faviconFieldId;
reload = true; // needs to be reloaded on server
}
if (site.DefaultThemeType != _themetype)
{
site.DefaultThemeType = _themetype;
refresh = true; // needs to be refreshed on client
}
if (site.DefaultContainerType != _containertype)
{
site.DefaultContainerType = _containertype;
refresh = true; // needs to be refreshed on client
}
site.AdminContainerType = _admincontainertype;
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
reload = true; // needs to be reloaded on server
}
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid == -1) pwaappiconfileid = null;
if (site.PwaAppIconFileId != pwaappiconfileid)
{
site.PwaAppIconFileId = pwaappiconfileid;
reload = true; // needs to be reloaded on server
}
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
if (site.PwaSplashIconFileId != pwasplashiconfileid)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
reload = true; // needs to be reloaded on server
}
site = await SiteService.UpdateSiteAsync(site);
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, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Settings Saved {Site}", site);
if (refresh || reload)
{
NavigationManager.NavigateTo(NavigateUrl(true), reload); // refresh/reload
}
else
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await interop.ScrollTo(0, 0, "smooth");
}
}
else // deuplicate alias or default alias not specified
{
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
}
}
else
@ -578,19 +546,19 @@
{
try
{
var sites = await SiteService.GetSitesAsync();
if (sites.Count > 1)
var aliases = await AliasService.GetAliasesAsync();
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);
var aliases = await AliasService.GetAliasesAsync();
foreach (Alias a in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
{
await AliasService.DeleteAliasAsync(a.AliasId);
await AliasService.DeleteAliasAsync(alias.AliasId);
}
NavigationManager.NavigateTo(NavigateUrl("admin/sites"));
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId);
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
}
else
{
@ -637,20 +605,6 @@
}
}
private async Task GetAliases()
{
_urls = string.Empty;
_defaultalias = string.Empty;
_aliases = await AliasService.GetAliasesAsync();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
foreach (Alias alias in _aliases)
{
_urls += (_urls == string.Empty) ? alias.Name.Trim() : ", " + alias.Name.Trim();
if (alias.IsDefault && string.IsNullOrEmpty(_defaultalias)) _defaultalias = alias.Name.Trim();
}
if (string.IsNullOrEmpty(_defaultalias)) _defaultalias = _aliases.First().Name.Trim();
}
private void ToggleSMTPPassword()
{
if (_smtppasswordtype == "password")
@ -664,4 +618,84 @@
_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.Site.TenantId).OrderBy(item => item.AliasId).ToList();
}
}
private void AddAlias()
{
_aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false });
_aliasid = 0;
_aliasname = "";
_defaultalias = "False";
StateHasChanged();
}
private void EditAlias(Alias alias)
{
_aliasid = alias.AliasId;
_aliasname = alias.Name;
_defaultalias = alias.IsDefault.ToString();
StateHasChanged();
}
private async Task DeleteAlias(Alias alias)
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
await GetAliases();
StateHasChanged();
}
}
private async Task SaveAlias()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (!string.IsNullOrEmpty(_aliasname))
{
var aliases = await AliasService.GetAliasesAsync();
var alias = aliases.Where(item => item.Name == _aliasname).FirstOrDefault();
bool unique = (alias == null || alias.AliasId == _aliasid);
if (unique)
{
if (_aliasid == 0)
{
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Site.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
await AliasService.AddAliasAsync(alias);
}
else
{
alias = _aliases.Single(item => item.AliasId == _aliasid);
alias.Name = _aliasname;
alias.IsDefault = bool.Parse(_defaultalias);
await AliasService.UpdateAliasAsync(alias);
}
}
else // duplicate alias
{
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
}
}
await GetAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
}
private async Task CancelAlias()
{
await GetAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
}

View File

@ -29,7 +29,7 @@ else
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they can be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder)." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
@ -128,25 +128,43 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
<div class="col-sm-9">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
if (database.IsDefault)
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
}
</select>
</div>
@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>
if (_databaseConfigType != null)
{
@DatabaseConfigComponent;
}
@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">String:</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">
@ -169,153 +187,171 @@ else
}
@code {
private List<Database> _databases;
private ElementReference form;
private bool validated = false;
private string _databaseName = "LocalDB";
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private List<Database> _databases;
private ElementReference form;
private bool validated = false;
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<SiteTemplate> _siteTemplates;
private List<Tenant> _tenants;
private string _tenantid = "-";
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<SiteTemplate> _siteTemplates;
private List<Tenant> _tenants;
private string _tenantid = "-";
private string _tenantName = string.Empty;
private string _tenantName = string.Empty;
private string _hostusername = string.Empty;
private string _hostpassword = string.Empty;
private string _hostusername = string.Empty;
private string _hostpassword = string.Empty;
private string _name = string.Empty;
private string _urls = string.Empty;
private string _themetype = "-";
private string _containertype = "-";
private string _admincontainertype = "";
private string _sitetemplatetype = "-";
private string _runtime = "Server";
private string _prerender = "Prerendered";
private string _name = string.Empty;
private string _urls = string.Empty;
private string _themetype = "-";
private string _containertype = "-";
private string _admincontainertype = "";
private string _sitetemplatetype = "-";
private string _runtime = "Server";
private string _prerender = "Prerendered";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
_tenants = await TenantService.GetTenantsAsync();
_urls = PageState.Alias.Name;
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
protected override async Task OnInitializedAsync()
{
_tenants = await TenantService.GetTenantsAsync();
_urls = PageState.Alias.Name;
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
_databases = await DatabaseService.GetDatabasesAsync();
LoadDatabaseConfigComponent();
}
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
{
_databaseName = _databases.Find(item => item.IsDefault).Name;
}
else
{
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
}
private void DatabaseChanged(ChangeEventArgs eventArgs)
{
try
{
_databaseName = (string)eventArgs.Value;
private void DatabaseChanged(ChangeEventArgs eventArgs)
{
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
{
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
}
}
LoadDatabaseConfigComponent();
}
catch
{
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
}
}
private void LoadDatabaseConfigComponent()
{
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (database != null)
{
_databaseConfigType = Type.GetType(database.ControlType);
DatabaseConfigComponent = builder =>
{
builder.OpenComponent(0, _databaseConfigType);
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
builder.CloseComponent();
};
}
}
private void LoadDatabaseConfigComponent()
{
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (database != null)
{
_databaseConfigType = Type.GetType(database.ControlType);
DatabaseConfigComponent = builder =>
{
builder.OpenComponent(0, _databaseConfigType);
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
builder.CloseComponent();
};
}
}
private void TenantChanged(ChangeEventArgs e)
{
_tenantid = (string)e.Value;
if (string.IsNullOrEmpty(_tenantName))
{
_tenantName = _name;
}
StateHasChanged();
}
private void TenantChanged(ChangeEventArgs e)
{
_tenantid = (string)e.Value;
if (string.IsNullOrEmpty(_tenantName))
{
_tenantName = _name;
}
StateHasChanged();
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_admincontainertype = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
}
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_admincontainertype = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
}
}
private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
{
private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
{
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (aliases.Exists(item => item.Name == name))
{
duplicates.Add(name);
}
}
var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (aliases.Exists(item => item.Name == name))
{
duplicates.Add(name);
}
}
if (duplicates.Count == 0)
{
InstallConfig config = new InstallConfig();
if (duplicates.Count == 0)
{
InstallConfig config = new InstallConfig();
if (_tenantid == "+")
{
if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null)
{
// validate host credentials
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _hostusername;
user.Password = _hostpassword;
user = await UserService.LoginUserAsync(user);
if (_tenantid == "+")
{
if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null)
{
// validate host credentials
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _hostusername;
user.Password = _hostpassword;
user.LastIPAddress = PageState.RemoteIPAddress;
user = await UserService.LoginUserAsync(user);
if (user.IsAuthenticated)
{
var connectionString = String.Empty;
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var connectionString = String.Empty;
if (_showConnectionString)
{
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
}
if (connectionString != "")
{
@ -396,4 +432,13 @@ else
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void ToggleConnectionString()
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
_connectionString = databaseConfigControl.GetConnectionString();
}
_showConnectionString = !_showConnectionString;
}
}

View File

@ -38,7 +38,13 @@
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<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 />
@ -142,6 +148,7 @@
<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>
</TabStrip>
<br /><br />
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -151,6 +158,7 @@
private string _osversion = 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;
@ -175,6 +183,7 @@
_osversion = systeminfo["OSVersion"].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";

View File

@ -35,6 +35,7 @@
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">

View File

@ -25,7 +25,13 @@
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<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 module was installed" 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 />
@ -53,28 +59,30 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _themeName = "";
private string _name;
private string _version;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private string _themeName = "";
private string _name;
private string _version;
private string _packagename;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeName = WebUtility.UrlDecode(PageState.QueryString["name"]);
var themes = await ThemeService.GetThemesAsync();
var theme = themes.FirstOrDefault(item => item.ThemeName == _themeName);
if (theme != null)
{
_name = theme.Name;
_version = theme.Version;
_owner = theme.Owner;
protected override async Task OnInitializedAsync()
{
try
{
_themeName = WebUtility.UrlDecode(PageState.QueryString["name"]);
var themes = await ThemeService.GetThemesAsync();
var theme = themes.FirstOrDefault(item => item.ThemeName == _themeName);
if (theme != null)
{
_name = theme.Name;
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;

View File

@ -160,7 +160,7 @@ else
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.FromDisplayName</td>
<td>@context.Subject</td>
<td>@context.CreatedOn</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
</Row>
<Detail>
<td colspan="2"></td>
@ -193,7 +193,7 @@ else
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.ToDisplayName</td>
<td>@context.Subject</td>
<td>@context.CreatedOn</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
</Row>
<Detail>
<td colspan="2"></td>

View File

@ -72,8 +72,19 @@ else
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
<div class="col-sm-9">
<input id="lastlogin" class="form-control" @bind="@lastlogin" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress"></Label>
<div class="col-sm-9">
<input id="lastipaddress" class="form-control" @bind="@lastipaddress" readonly />
</div>
</div>
</div>
}
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
@ -137,63 +148,70 @@ else
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
@code {
private int userid;
private string username = string.Empty;
private string _password = string.Empty;
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 displayname = string.Empty;
private FileManager filemanager;
private int photofileid = -1;
private File photo = null;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private string deletedby;
private DateTime? deletedon;
private string isdeleted;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int photofileid = -1;
private File photo = null;
private string isdeleted;
private string lastlogin;
private string lastipaddress;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
protected override async Task OnParametersSetAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id"))
{
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
userid = Int32.Parse(PageState.QueryString["id"]);
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null)
{
username = user.Username;
email = user.Email;
displayname = user.DisplayName;
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
settings = await SettingService.GetUserSettingsAsync(user.UserId);
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private string deletedby;
private DateTime? deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id"))
{
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
userid = Int32.Parse(PageState.QueryString["id"]);
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null)
{
username = user.Username;
email = user.Email;
displayname = user.DisplayName;
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
isdeleted = user.IsDeleted.ToString();
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy;
deletedon = user.DeletedOn;
isdeleted = user.IsDeleted.ToString();
}
}
}

View File

@ -33,14 +33,12 @@ else
</div>
<Pager Items="@users" RowClass="align-middle">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Username"]</th>
<th>@Localizer["LastLoginOn"]</th>
<th>@Localizer["LastIPAddress"]</th>
<th>@Localizer["CreatedOn"]</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Username"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["LastLoginOn"]</th>
</Header>
<Row>
<td>
@ -52,12 +50,9 @@ else
<td>
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
</td>
<td>@context.User.DisplayName</td>
<td>@context.User.Username</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}",context.User.LastLoginOn)</td>
<td>@context.User.LastIPAddress</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}",context.User.CreatedOn)</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn)</td>
</Row>
</Pager>
</TabPanel>
@ -259,6 +254,12 @@ else
<input id="scopes" class="form-control" @bind="@_scopes" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parameters" HelpText="Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple)." ResourceKey="Parameters">Parameters:</Label>
<div class="col-sm-9">
<input id="parameters" class="form-control" @bind="@_parameters" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
<div class="col-sm-9">
@ -380,6 +381,7 @@ else
private string _clientsecrettype = "password";
private string _toggleclientsecret = string.Empty;
private string _scopes;
private string _parameters;
private string _pkce;
private string _redirecturl;
private string _identifierclaimtype;
@ -432,6 +434,7 @@ else
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = SharedLocalizer["ShowPassword"];
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
@ -549,6 +552,7 @@ else
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);

View File

@ -88,15 +88,17 @@ else
userid = Int32.Parse(PageState.QueryString["id"]);
User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
name = user.DisplayName;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
roles = roles.Where(item => item.Name != RoleNames.Everyone).ToList();
roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
}
else
{
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
}
await GetUserRoles();
}
catch (Exception ex)

View File

@ -86,279 +86,296 @@
}
@code {
private string _id;
private List<Folder> _folders;
private List<File> _files = new List<File>();
private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty;
private string _progressbarid = string.Empty;
private string _filter = "*";
private bool _haseditpermission = false;
private string _image = string.Empty;
private File _file = null;
private string _guid;
private string _message = string.Empty;
private MessageType _messagetype;
private string _id;
private List<Folder> _folders;
private List<File> _files = new List<File>();
private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty;
private string _progressbarid = string.Empty;
private string _filter = "*";
private bool _haseditpermission = false;
private string _image = string.Empty;
private File _file = null;
private string _guid;
private string _message = string.Empty;
private MessageType _messagetype;
[Parameter]
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
[Parameter]
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
[Parameter]
public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid
[Parameter]
public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid
[Parameter]
public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path
[Parameter]
public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path
[Parameter]
public int FileId { get; set; } = -1; // optional - for selecting a specific file by default
[Parameter]
public int FileId { get; set; } = -1; // optional - for selecting a specific file by default
[Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
[Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
[Parameter]
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
[Parameter]
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
[Parameter]
public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true
[Parameter]
public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true
[Parameter]
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter]
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter]
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
[Parameter]
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
[Parameter]
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
[Parameter]
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
[Parameter]
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter]
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
[Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
[Parameter]
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
[Parameter]
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
[Parameter]
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
[Parameter]
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
protected override async Task OnInitializedAsync()
{
if (!string.IsNullOrEmpty(Id))
{
_id = Id;
}
protected override async Task OnInitializedAsync()
{
if (!string.IsNullOrEmpty(Id))
{
_id = Id;
}
// packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder)
{
ShowFiles = false;
ShowFolders = false;
Filter = "nupkg";
ShowSuccess = true;
}
// packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder)
{
ShowFiles = false;
ShowFolders = false;
Filter = "nupkg";
ShowSuccess = true;
}
if (!ShowFiles)
{
ShowImage = false;
}
if (!ShowFiles)
{
ShowImage = false;
}
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
{
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;
}
}
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
{
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;
}
}
if (FileId != -1)
{
File file = await FileService.GetFileAsync(FileId);
if (file != null)
{
FolderId = file.FolderId;
await OnSelect.InvokeAsync(FileId);
}
else
{
FileId = -1; // file does not exist
_message = "FileId " + FileId.ToString() + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
if (FileId != -1)
{
File file = await FileService.GetFileAsync(FileId);
if (file != null)
{
FolderId = file.FolderId;
await OnSelect.InvokeAsync(FileId);
}
else
{
FileId = -1; // file does not exist
_message = "FileId " + FileId.ToString() + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
await SetImage();
await SetImage();
if (!string.IsNullOrEmpty(Filter))
{
_filter = "." + Filter.Replace(",", ",.");
}
if (!string.IsNullOrEmpty(Filter))
{
_filter = "." + Filter.Replace(",", ",.");
}
await GetFiles();
await GetFiles();
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = _guid + "FileInput";
_progressinfoid = _guid + "ProgressInfo";
_progressbarid = _guid + "ProgressBar";
}
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = _guid + "FileInput";
_progressinfoid = _guid + "ProgressInfo";
_progressbarid = _guid + "ProgressBar";
}
private async Task GetFiles()
{
_haseditpermission = false;
if (Folder == Constants.PackagesFolder)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = new List<File>();
}
else
{
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.Permissions);
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
_haseditpermission = false;
_files = new List<File>();
}
}
if (_filter != "*")
{
List<File> filtered = new List<File>();
foreach (File file in _files)
{
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
{
filtered.Add(file);
}
}
_files = filtered;
}
}
private async Task GetFiles()
{
_haseditpermission = false;
if (Folder == Constants.PackagesFolder)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = new List<File>();
}
else
{
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.Permissions);
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
_haseditpermission = false;
_files = new List<File>();
}
}
if (_filter != "*")
{
List<File> filtered = new List<File>();
foreach (File file in _files)
{
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
{
filtered.Add(file);
}
}
_files = filtered;
}
}
private async Task FolderChanged(ChangeEventArgs e)
{
_message = string.Empty;
try
{
FolderId = int.Parse((string)e.Value);
await GetFiles();
FileId = -1;
_file = null;
_image = string.Empty;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error;
}
}
private async Task FolderChanged(ChangeEventArgs e)
{
_message = string.Empty;
try
{
FolderId = int.Parse((string)e.Value);
await GetFiles();
FileId = -1;
_file = null;
_image = string.Empty;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error;
}
}
private async Task FileChanged(ChangeEventArgs e)
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
if (FileId != -1)
{
await OnSelect.InvokeAsync(FileId);
}
private async Task FileChanged(ChangeEventArgs e)
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
if (FileId != -1)
{
await OnSelect.InvokeAsync(FileId);
}
await SetImage();
StateHasChanged();
}
await SetImage();
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;
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;
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() + "\" />";
}
}
}
_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 UploadFile()
{
_message = string.Empty;
var interop = new Interop(JSRuntime);
var upload = await interop.GetFiles(_fileinputid);
if (upload.Length > 0)
{
try
{
string result;
if (Folder == Constants.PackagesFolder)
{
result = await FileService.UploadFilesAsync(Folder, upload, _guid);
}
else
{
result = await FileService.UploadFilesAsync(FolderId, upload, _guid);
}
private async Task UploadFile()
{
_message = string.Empty;
var interop = new Interop(JSRuntime);
var upload = await interop.GetFiles(_fileinputid);
if (upload.Length > 0)
{
string restricted = "";
foreach (var file in upload)
{
var extension = (file.LastIndexOf(".") != -1) ? file.Substring(file.LastIndexOf(".") + 1) : "";
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{
restricted += (restricted == "" ? "" : ",") + extension;
}
}
if (restricted == "")
{
try
{
string result;
if (Folder == Constants.PackagesFolder)
{
result = await FileService.UploadFilesAsync(Folder, upload, _guid);
}
else
{
result = await FileService.UploadFilesAsync(FolderId, upload, _guid);
}
if (result == string.Empty)
{
await logger.LogInformation("File Upload Succeeded {Files}", upload);
if (ShowSuccess)
{
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
if (result == string.Empty)
{
await logger.LogInformation("File Upload Succeeded {Files}", upload);
if (ShowSuccess)
{
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
// set FileId to first file in upload collection
await GetFiles();
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
StateHasChanged();
}
else
{
await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", "));
// set FileId to first file in upload collection
await GetFiles();
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
StateHasChanged();
}
else
{
await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", "));
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
}
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
}
else
{
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
_messagetype = MessageType.Warning;
}
}
else
{
_message = Localizer["Message.File.NotSelected"];

View File

@ -1,13 +1,13 @@
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Oqtane.Shared;
namespace Oqtane.Modules.Controls
{
public class LocalizableComponent : ModuleControlBase
{
[Inject] public IStringLocalizerFactory LocalizerFactory { get; set; }
private IStringLocalizer _localizer;
[Parameter]
@ -30,22 +30,15 @@ namespace Oqtane.Modules.Controls
var key = $"{ResourceKey}.{propertyName}";
var value = Localize(key);
if (value == key)
if (value == key || value == String.Empty)
{
// Returns default property value (English version) instead of ResourceKey.PropertyName
// return default property value if key does not exist in resource file or value is empty
return propertyValue;
}
else
{
if (value == String.Empty)
{
// Returns default property value (English version)
return propertyValue;
}
else
{
return value;
}
// return localized value
return value;
}
}
@ -53,24 +46,15 @@ namespace Oqtane.Modules.Controls
{
IsLocalizable = false;
if (string.IsNullOrEmpty(ResourceType))
if (String.IsNullOrEmpty(ResourceType))
{
ResourceType = ModuleState?.ModuleType;
}
if (!String.IsNullOrEmpty(ResourceKey) && !string.IsNullOrEmpty(ResourceType))
if (!String.IsNullOrEmpty(ResourceKey) && !String.IsNullOrEmpty(ResourceType))
{
var moduleType = Type.GetType(ResourceType);
if (moduleType != null)
{
using (var scope = ServiceActivator.GetScope())
{
var localizerFactory = scope.ServiceProvider.GetService<IStringLocalizerFactory>();
_localizer = localizerFactory.Create(moduleType);
IsLocalizable = true;
}
}
_localizer = LocalizerFactory.Create(ResourceType);
IsLocalizable = true;
}
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@inject IStringLocalizerFactory LocalizerFactory
@typeparam TableItem
@if (ItemList != null)
@ -48,7 +49,7 @@
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@ -156,67 +157,73 @@
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
}
@code {
private int _pages = 0;
private int _page = 1;
private int _maxItems = 10;
private int _displayPages = 5;
private int _startPage = 0;
private int _endPage = 0;
private int _columns = 0;
[Parameter]
public string Format { get; set; } // Table or Grid
[Parameter]
public string Toolbar { get; set; } // Top, Bottom or Both
[Parameter]
public RenderFragment Header { get; set; } = null; // only applicable to Table layouts
[Parameter]
public RenderFragment<TableItem> Row { get; set; } = null; // required
[Parameter]
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
[Parameter]
public IEnumerable<TableItem> Items { get; set; } // the IEnumerable data source
[Parameter]
public string PageSize { get; set; } // number of items to display on a page
[Parameter]
public string Columns { get; set; } // only applicable to Grid layouts - default is zero indicating use responsive behavior
[Parameter]
public string CurrentPage { get; set; } // sets the initial page to display
[Parameter]
public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection
[Parameter]
public string Class { get; set; } // class for the containing element - ie. <table> for Table or <div> for Grid
[Parameter]
public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid
private IStringLocalizer Localizer;
private int _pages = 0;
private int _page = 1;
private int _maxItems = 10;
private int _displayPages = 5;
private int _startPage = 0;
private int _endPage = 0;
private int _columns = 0;
[Parameter]
public string ColumnClass { get; set; } // class for column element - only applicable to Grid format
public string Format { get; set; } // Table or Grid
[Parameter]
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
[Parameter]
public string Toolbar { get; set; } // Top, Bottom or Both
private IEnumerable<TableItem> ItemList { get; set; }
[Parameter]
public RenderFragment Header { get; set; } = null; // only applicable to Table layouts
protected override void OnParametersSet()
{
[Parameter]
public RenderFragment<TableItem> Row { get; set; } = null; // required
[Parameter]
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
[Parameter]
public IEnumerable<TableItem> Items { get; set; } // the IEnumerable data source
[Parameter]
public string PageSize { get; set; } // number of items to display on a page
[Parameter]
public string Columns { get; set; } // only applicable to Grid layouts - default is zero indicating use responsive behavior
[Parameter]
public string CurrentPage { get; set; } // sets the initial page to display
[Parameter]
public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection
[Parameter]
public string Class { get; set; } // class for the containing element - ie. <table> for Table or <div> for Grid
[Parameter]
public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid
[Parameter]
public string ColumnClass { get; set; } // class for column element - only applicable to Grid format
[Parameter]
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
private IEnumerable<TableItem> ItemList { get; set; }
protected override void OnInitialized()
{
Localizer = LocalizerFactory.Create(GetType().FullName);
}
protected override void OnParametersSet()
{
if (string.IsNullOrEmpty(Format))
{
Format = "Table";

View File

@ -127,11 +127,10 @@
_permissionnames = PermissionNames;
}
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId);
_roles.Insert(0, new Role { Name = RoleNames.Everyone });
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_roles.Add(new Role { Name = RoleNames.Host });
_roles.RemoveAll(item => item.Name == RoleNames.Host);
}
_permissions = new List<PermissionString>();
@ -254,6 +253,7 @@
permission = _permissions[i];
List<string> ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList();
ids.Remove("!" + RoleNames.Everyone); // remove deny all users
ids.Remove("!" + RoleNames.Unauthenticated); // remove deny unauthenticated
ids.Remove("!" + RoleNames.Registered); // remove deny registered users
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{

View File

@ -150,10 +150,6 @@
// preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
_originalrichhtml = await interop.GetHtml(_editorElement);
}
else
{
await interop.LoadEditorContent(_editorElement, _richhtml);
}
}
public void CloseFileManager()

View File

@ -5,7 +5,7 @@
<OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations>
<Version>3.1.1</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -13,7 +13,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>

View File

@ -114,6 +114,7 @@ namespace Oqtane.Client
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
{
// dynamically register module scoped services
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{

View File

@ -121,7 +121,7 @@
<value>Server:</value>
</data>
<data name="Server.HelpText" xml:space="preserve">
<value>Enter the database server</value>
<value>Enter the database server name. This might include a port number as well if you are using a cloud service (ie. servername.database.windows.net,1433) </value>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database:</value>
@ -133,7 +133,7 @@
<value>Integrated Security:</value>
</data>
<data name="IntegratedSecurity.HelpText" xml:space="preserve">
<value>Select if you want integrated security or not</value>
<value>Select if you are using integrated security</value>
</data>
<data name="Uid.Text" xml:space="preserve">
<value>User Id:</value>
@ -163,7 +163,7 @@
<value>Self Signed</value>
</data>
<data name="TrustServerCertificate.HelpText" xml:space="preserve">
<value>Specify the type of certificate you are using for encryption</value>
<value>Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True.</value>
</data>
<data name="TrustServerCertificate.Text" xml:space="preserve">
<value>Trust Server Certificate:</value>

View File

@ -135,10 +135,10 @@
<data name="Message.Require.DbInfo" xml:space="preserve">
<value>Please Enter All Required Fields. Ensure Passwords Match And Email Address Provided Is Valid.</value>
</data>
<data name="Message.Password.Invalid" xml:space="preserve">
<data name="Message.Password.Invalid" xml:space="preserve">
<value>The Password Provided Does Not Meet The Password Policy. Please Verify The Minimum Password Length And Complexity Requirements.</value>
</data>
<data name="Register" xml:space="preserve">
<data name="Register" xml:space="preserve">
<value>Please Register Me For Major Product Updates And Security Bulletins</value>
</data>
<data name="Confirm.HelpText" xml:space="preserve">
@ -168,4 +168,16 @@
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>Enter a complete connection string including all parameters and delimiters</value>
</data>
<data name="ConnectionString.Text" xml:space="preserve">
<value>String:</value>
</data>
<data name="EnterConnectionParameters" xml:space="preserve">
<value>Enter Connection Parameters</value>
</data>
<data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -118,6 +118,6 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Error.Module.Load" xml:space="preserve">
<value>A Problem Was Encountered Loading Module {0}</value>
<value>A Problem Was Encountered Loading Module {0}. The Module Is Either Invalid Or Does Not Exist.</value>
</data>
</root>

View File

@ -192,4 +192,7 @@
<data name="Once" xml:space="preserve">
<value>Execute Once</value>
</data>
<data name="Message.NoJobs" xml:space="preserve">
<value>Please Note That After An Initial Installation You Must &amp;lt;a href={0}&amp;gt;Restart&amp;lt;/a&amp;gt; The Application In Order To Activate The Default Scheduled Jobs.</value>
</data>
</root>

View File

@ -154,13 +154,13 @@
<value>No Translations Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Download</value>
<value>Translations</value>
</data>
<data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translations. Once they are uploaded click Install to complete the installation.</value>
<value>Upload one or more translation packages. Once they are uploaded click Install to complete the installation.</value>
</data>
<data name="LanguageUpload.Text" xml:space="preserve">
<value>Upload Language</value>
<value>Translation</value>
</data>
<data name="Manage.Heading" xml:space="preserve">
<value>Manage</value>

View File

@ -144,4 +144,7 @@
<data name="DeleteLanguage.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Translation" xml:space="preserve">
<value>Translation</value>
</data>
</root>

View File

@ -195,4 +195,25 @@
<data name="Information.Text" xml:space="preserve">
<value>Information</value>
</data>
<data name="PackageName.HelpText" xml:space="preserve">
<value>The unique name of the package from which this module was installed</value>
</data>
<data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value>
</data>
<data name="Error.Translation.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Translations Exist For This Module Or Package Service Is Disabled</value>
</data>
<data name="Success.Translation.Download" xml:space="preserve">
<value>Translation Downloaded Successfully. Click Install To Complete Installation.</value>
</data>
<data name="Success.Translation.Install" xml:space="preserve">
<value>Translation Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Translations.Heading" xml:space="preserve">
<value>Translations</value>
</data>
</root>

View File

@ -150,4 +150,7 @@
<data name="EditModule.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Modules" xml:space="preserve">
<value>Modules</value>
</data>
</root>

View File

@ -147,4 +147,7 @@
<data name="Message.Required.Title" xml:space="preserve">
<value>You Must Provide A Title For The Module</value>
</data>
<data name="Error.Module.Load" xml:space="preserve">
<value>A Problem Was Encountered Loading Module {0}. The Module Is Either Invalid Or Does Not Exist.</value>
</data>
</root>

View File

@ -142,7 +142,7 @@
<value>Site Settings Saved</value>
</data>
<data name="Message.Aliases.Taken" xml:space="preserve">
<value>The Default Alias Has Not Been Specified Or An Alias Was Specified That Has Already Been Used For Another Site</value>
<value>An Alias Was Specified That Has Already Been Used For Another Site</value>
</data>
<data name="Message.Required.SiteName" xml:space="preserve">
<value>You Must Provide A Site Name, Alias, And Default Theme/Container</value>
@ -166,7 +166,7 @@
<value>Enter the tenant for the site</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve">
<value>The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas.</value>
<value>The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder).</value>
</data>
<data name="IsDeleted.HelpText" xml:space="preserve">
<value>Is this site deleted?</value>
@ -324,4 +324,13 @@
<data name="Aliases.Heading" xml:space="preserve">
<value>Aliases</value>
</data>
<data name="AliasName" xml:space="preserve">
<value>Name</value>
</data>
<data name="AliasDefault" xml:space="preserve">
<value>Default?</value>
</data>
<data name="Confirm.Alias.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete {0}?</value>
</data>
</root>

View File

@ -270,4 +270,16 @@
<data name="Runtime.Text" xml:space="preserve">
<value>Runtime: </value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>Enter a complete connection string including all parameters and delimiters</value>
</data>
<data name="ConnectionString.Text" xml:space="preserve">
<value>String:</value>
</data>
<data name="EnterConnectionParameters" xml:space="preserve">
<value>Enter Connection Parameters</value>
</data>
<data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value>
</data>
</root>

View File

@ -270,4 +270,10 @@
<data name="WorkingSet.Text" xml:space="preserve">
<value>Memory Allocation:</value>
</data>
<data name="Environment.HelpText" xml:space="preserve">
<value>Environment Name</value>
</data>
<data name="Environment.Text" xml:space="preserve">
<value>Environment:</value>
</data>
</root>

View File

@ -162,4 +162,10 @@
<data name="License.HelpText" xml:space="preserve">
<value>The license of the theme</value>
</data>
<data name="PackageName.HelpText" xml:space="preserve">
<value>The unique name of the package from which this module was installed</value>
</data>
<data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value>
</data>
</root>

View File

@ -219,4 +219,10 @@
<data name="DeleteAllNotifications.Text" xml:space="preserve">
<value>Delete ALL Notifications</value>
</data>
<data name="Notifications.Heading" xml:space="preserve">
<value>Notifications</value>
</data>
<data name="Profile.Heading" xml:space="preserve">
<value>Profile</value>
</data>
</root>

View File

@ -186,4 +186,16 @@
<data name="Password.Placeholder" xml:space="preserve">
<value>Password</value>
</data>
<data name="LastIPAddress.HelpText" xml:space="preserve">
<value>The IP Address of the user recorded during their last login</value>
</data>
<data name="LastIPAddress.Text" xml:space="preserve">
<value>Last IP Address: </value>
</data>
<data name="LastLogin.HelpText" xml:space="preserve">
<value>The date and time when the user last signed in</value>
</data>
<data name="LastLogin.Text" xml:space="preserve">
<value>Last Login:</value>
</data>
</root>

View File

@ -369,12 +369,6 @@
<data name="Required" xml:space="preserve">
<value>Required</value>
</data>
<data name="CreatedOn" xml:space="preserve">
<value>Created On</value>
</data>
<data name="LastIPAddress" xml:space="preserve">
<value>Last IP Address</value>
</data>
<data name="LastLoginOn" xml:space="preserve">
<value>Last Login</value>
</data>
@ -384,4 +378,10 @@
<data name="IdentifierClaimType.Text" xml:space="preserve">
<value>Identifier Claim:</value>
</data>
<data name="Parameters.HelpText" xml:space="preserve">
<value>Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple).</value>
</data>
<data name="Parameters.Text" xml:space="preserve">
<value>Parameters:</value>
</data>
</root>

View File

@ -141,4 +141,7 @@
<data name="Success.File.Upload" xml:space="preserve">
<value>File Upload Succeeded</value>
</data>
<data name="Message.File.Restricted" xml:space="preserve">
<value>Files With Extension Of {0} Are Restricted From Upload. Please Contact Your Administrator For More Information.</value>
</data>
</root>

View File

@ -0,0 +1,123 @@
<?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="PageOfPages" xml:space="preserve">
<value>Page {0} of {1}</value>
</data>
</root>

View File

@ -327,4 +327,13 @@
<data name="ShowPassword" xml:space="preserve">
<value>Show</value>
</data>
<data name="PageOfPages" xml:space="preserve">
<value>Page {0} of {1}</value>
</data>
<data name="Url Mappings" xml:space="preserve">
<value>Url Mappings</value>
</data>
<data name="Visitor Management" xml:space="preserve">
<value>Visitor Management</value>
</data>
</root>

View File

@ -138,6 +138,12 @@
<data name="Register.Text" xml:space="preserve">
<value>Show Register?</value>
</data>
<data name="Scope.HelpText" xml:space="preserve">
<value>Specify if the settings are applicable to this page or the entire site.</value>
</data>
<data name="Scope.Text" xml:space="preserve">
<value>Setting Scope:</value>
</data>
<data name="Site" xml:space="preserve">
<value>Site</value>
</data>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
@ -14,10 +15,12 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class FileService : ServiceBase, IFileService
{
private readonly SiteState _siteState;
private readonly IJSRuntime _jsRuntime;
public FileService(HttpClient http, SiteState siteState, IJSRuntime jsRuntime) : base(http, siteState)
{
_siteState = siteState;
_jsRuntime = jsRuntime;
}
@ -30,7 +33,8 @@ namespace Oqtane.Services
public async Task<List<File>> GetFilesAsync(string folder)
{
return await GetJsonAsync<List<File>>($"{Apiurl}?folder={folder}");
List<File> files = await GetJsonAsync<List<File>>($"{Apiurl}?folder={folder}");
return files.OrderBy(item => item.Name).ToList();
}
public async Task<List<File>> GetFilesAsync(int siteId, string folderPath)
@ -42,7 +46,8 @@ namespace Oqtane.Services
var path = WebUtility.UrlEncode(folderPath);
return await GetJsonAsync<List<File>>($"{Apiurl}/{siteId}/{path}");
List<File> files = await GetJsonAsync<List<File>>($"{Apiurl}/{siteId}/{path}");
return files?.OrderBy(item => item.Name).ToList();
}
public async Task<File> GetFileAsync(int fileId)
@ -80,7 +85,7 @@ namespace Oqtane.Services
string result = "";
var interop = new Interop(_jsRuntime);
await interop.UploadFiles($"{Apiurl}/upload", folder, id);
await interop.UploadFiles($"{Apiurl}/upload", folder, id, _siteState.AntiForgeryToken);
// uploading files is asynchronous so we need to wait for the upload to complete
bool success = false;

View File

@ -17,6 +17,14 @@ namespace Oqtane.Services
/// <returns></returns>
Task<List<Language>> GetLanguagesAsync(int siteId);
/// <summary>
/// Returns a list of all available languages for the given <see cref="Site" /> and package
/// </summary>
/// <param name="siteId"></param>
/// <param name="packageName"></param>
/// <returns></returns>
Task<List<Language>> GetLanguagesAsync(int siteId, string packageName);
/// <summary>
/// Returns the given language
/// </summary>

View File

@ -17,18 +17,27 @@ namespace Oqtane.Services
public async Task<List<Language>> GetLanguagesAsync(int siteId)
{
var languages = await GetJsonAsync<List<Language>>($"{Apiurl}?siteid={siteId}");
return await GetLanguagesAsync(siteId, "");
}
return languages?.OrderBy(l => l.Name).ToList() ?? Enumerable.Empty<Language>().ToList();
public async Task<List<Language>> GetLanguagesAsync(int siteId, string packageName)
{
return await GetJsonAsync<List<Language>>($"{Apiurl}?siteid={siteId}&packagename={packageName}");
}
public async Task<Language> GetLanguageAsync(int languageId)
=> await GetJsonAsync<Language>($"{Apiurl}/{languageId}");
{
return await GetJsonAsync<Language>($"{Apiurl}/{languageId}");
}
public async Task<Language> AddLanguageAsync(Language language)
=> await PostJsonAsync<Language>(Apiurl, language);
{
return await PostJsonAsync<Language>(Apiurl, language);
}
public async Task DeleteLanguageAsync(int languageId)
=> await DeleteAsync($"{Apiurl}/{languageId}");
{
await DeleteAsync($"{Apiurl}/{languageId}");
}
}
}

View File

@ -17,7 +17,7 @@
<LanguageSwitcher />
}
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions) || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
{
if (PageState.EditMode)
{
@ -218,6 +218,7 @@
}
@code{
private bool _showEditMode = false;
private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>();
private List<ModuleDefinition> _allModuleDefinitions;
@ -285,8 +286,10 @@
protected override async Task OnParametersSetAsync()
{
_showEditMode = false;
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{
_showEditMode = true;
_pages?.Clear();
foreach (Page p in PageState.Pages)
@ -305,6 +308,17 @@
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
else
{
foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, module.Permissions))
{
_showEditMode = true;
break;
}
}
}
}
private void CategoryChanged(ChangeEventArgs e)
@ -405,21 +419,21 @@
Message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
Title = "";
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
Message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>";
}
}
else
{
Message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>";
}
}
}
else
{
Message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>";
}
}
else
{
Message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>";
}
}
private async Task ToggleEditMode(bool EditMode)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
if (_showEditMode)
{
if (EditMode)
{
@ -445,7 +459,6 @@
private void Navigate(string location)
{
//HideControlPanel();
Module module;
switch (location)
{

View File

@ -24,13 +24,9 @@
@code{
private IEnumerable<Culture> _supportedCultures;
protected override async Task OnParametersSetAsync()
protected override void OnParametersSet()
{
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName });
var languages = PageState.Languages;
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
}

View File

@ -9,15 +9,19 @@
if (childPage.PageId == PageState.Page.PageId)
{
<a class="nav-link active px-3" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
</span>
</a>
}
else
{
<a class="nav-link px-3" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
</span>
</a>
}
}
@ -34,8 +38,10 @@ else
{
<li class="nav-item">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
</span>
</a>
</li>
}
@ -43,8 +49,10 @@ else
{
<li class="nav-item">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
</span>
</a>
</li>
}

View File

@ -9,8 +9,10 @@
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
</span>
</a>
</li>
}
@ -18,8 +20,10 @@
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
</span>
</a>
</li>
}
@ -38,8 +42,10 @@ else
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
</span>
</a>
</li>
}
@ -47,8 +53,10 @@ else
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
</span>
</a>
</li>
}

View File

@ -189,13 +189,13 @@ namespace Oqtane.UI
}
}
public Task UploadFiles(string posturl, string folder, string id)
public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken)
{
try
{
_jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.uploadFiles",
posturl, folder, id);
posturl, folder, id, antiforgerytoken);
return Task.CompletedTask;
}
catch

View File

@ -8,6 +8,7 @@ namespace Oqtane.UI
{
public Alias Alias { get; set; }
public Site Site { get; set; }
public List<Language> Languages { get; set; }
public List<Page> Pages { get; set; }
public Page Page { get; set; }
public User User { get; set; }

View File

@ -6,6 +6,7 @@
@inject INavigationInterception NavigationInterception
@inject ISyncService SyncService
@inject ISiteService SiteService
@inject ILanguageService LanguageService
@inject IPageService PageService
@inject IUserService UserService
@inject IModuleService ModuleService
@ -70,6 +71,7 @@
private async Task Refresh()
{
Site site;
List<Language> languages;
List<Page> pages;
Page page;
User user = null;
@ -173,11 +175,13 @@
if (PageState == null || refresh == UI.Refresh.Site)
{
languages = await LanguageService.GetLanguagesAsync(site.SiteId);
pages = await PageService.GetPagesAsync(site.SiteId);
pages = pages.Where(item => !item.IsDeleted).ToList();
}
else
{
languages = PageState.Languages;
pages = PageState.Pages;
}
@ -230,6 +234,7 @@
{
Alias = SiteState.Alias,
Site = site,
Languages = languages,
Pages = pages,
Page = page,
User = user,

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.1.1</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.MySQL</id>
<version>3.1.1</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane MySQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.1.1</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.PostgreSQL</id>
<version>3.1.1</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane PostgreSQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.1.1</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.SqlServer</id>
<version>3.1.1</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane SQL Server Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -32,6 +32,21 @@ namespace Oqtane.Database.SqlServer
return table.Column<int>(name: name, nullable: false).Annotation("SqlServer:Identity", "1, 1");
}
public override void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode, string index)
{
var elements = index.Split(':', StringSplitOptions.RemoveEmptyEntries);
if (elements.Length != 0)
{
builder.DropIndex(elements[0], table);
}
builder.AlterColumn<string>(name, table, maxLength: length, nullable: nullable, unicode: unicode);
if (elements.Length != 0)
{
var columns = elements[1].Split(',');
builder.CreateIndex(elements[0], table, columns, null, bool.Parse(elements[2]), null);
}
}
public override int ExecuteNonQuery(string connectionString, string query)
{
var conn = new SqlConnection(FormatConnectionString(connectionString));

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.1.1</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.Sqlite</id>
<version>3.1.1</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane SQLite Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -35,7 +35,7 @@ namespace Oqtane.Database.Sqlite
// not implemented as SQLite does not support dropping columns
}
public override void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode)
public override void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode, string index)
{
// not implemented as SQLite does not support altering columns
}

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Client</id>
<version>3.1.1</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Framework</id>
<version>3.1.1</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.1.1/Oqtane.Framework.3.1.1.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</releaseNotes>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.1.4/Oqtane.Framework.3.1.2.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane framework</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Server</id>
<version>3.1.1</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Shared</id>
<version>3.1.1</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Updater</id>
<version>3.1.1</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.1.1.Install.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.1.4.Install.zip" -Force

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.1.1.Upgrade.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.1.4.Upgrade.zip" -Force

View File

@ -16,12 +16,14 @@ namespace Oqtane.Controllers
public class AliasController : Controller
{
private readonly IAliasRepository _aliases;
private readonly ITenantRepository _tenants;
private readonly ILogManager _logger;
private readonly Alias _alias;
public AliasController(IAliasRepository aliases, ILogManager logger, ITenantManager tenantManager)
public AliasController(IAliasRepository aliases, ITenantRepository tenants, ILogManager logger, ITenantManager tenantManager)
{
_aliases = aliases;
_tenants = tenants;
_logger = logger;
_alias = tenantManager.GetAlias();
}
@ -95,6 +97,13 @@ namespace Oqtane.Controllers
{
_aliases.DeleteAlias(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id);
var aliases = _aliases.GetAliases();
if (!aliases.Any(item => item.TenantId == alias.TenantId))
{
_tenants.DeleteTenant(alias.TenantId);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Tenant Deleted {TenantId}", alias.TenantId);
}
}
else
{

View File

@ -255,7 +255,7 @@ namespace Oqtane.Controllers
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "File Could Not Be Downloaded From Url {Url} {Error}", url, ex.Message);
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "File Could Not Be Downloaded From Url {Url} {Error}", url, ex.Message);
}
}
else
@ -276,9 +276,17 @@ namespace Oqtane.Controllers
return;
}
if (!formfile.FileName.IsPathOrFileValid())
// ensure filename is valid
string token = ".part_";
if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token))
{
return;
}
// check for allowable file extensions (ignore token)
var extension = Path.GetExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token))).Replace(".", "");
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
return;
}
@ -331,9 +339,9 @@ namespace Oqtane.Controllers
{
string merged = "";
// parse the filename which is in the format of filename.ext.part_x_y
// parse the filename which is in the format of filename.ext.part_001_999
string token = ".part_";
string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "x_y"
string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "001_999"
int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1));
filename = Path.GetFileNameWithoutExtension(filename); // base filename
@ -370,23 +378,15 @@ namespace Oqtane.Controllers
System.IO.File.Delete(filepart);
}
// check for allowable file extensions
if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(filename)?.ToLower().Replace(".", "")))
// remove file if it already exists
if (System.IO.File.Exists(Path.Combine(folder, filename)))
{
System.IO.File.Delete(Path.Combine(folder, filename + ".tmp"));
System.IO.File.Delete(Path.Combine(folder, filename));
}
else
{
// remove file if it already exists
if (System.IO.File.Exists(Path.Combine(folder, filename)))
{
System.IO.File.Delete(Path.Combine(folder, filename));
}
// rename file now that the entire process is completed
System.IO.File.Move(Path.Combine(folder, filename + ".tmp"), Path.Combine(folder, filename));
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folder, filename));
}
// rename file now that the entire process is completed
System.IO.File.Move(Path.Combine(folder, filename + ".tmp"), Path.Combine(folder, filename));
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folder, filename));
merged = filename;
}
@ -394,8 +394,7 @@ namespace Oqtane.Controllers
// clean up file parts which are more than 2 hours old ( which can happen if a prior file upload failed )
var cleanupFiles = Directory.EnumerateFiles(folder, "*" + token + "*")
.Where(f => Path.GetExtension(f).StartsWith(token));
.Where(f => Path.GetExtension(f).StartsWith(token) && !Path.GetFileName(f).StartsWith(filename));
foreach (var file in cleanupFiles)
{
var createdDate = System.IO.File.GetCreationTime(file).ToUniversalTime();
@ -603,7 +602,7 @@ namespace Oqtane.Controllers
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Rotate} {Error}", filepath, width, height, mode, rotate, ex.Message);
_logger.Log(LogLevel.Error, this, LogFunction.Security, ex, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Rotate} {Error}", filepath, width, height, mode, rotate, ex.Message);
imagepath = "";
}

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Enums;
@ -7,6 +9,10 @@ using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
using System.Linq;
using System.Diagnostics;
using System.Globalization;
using System;
namespace Oqtane.Controllers
{
@ -14,23 +20,40 @@ namespace Oqtane.Controllers
public class LanguageController : Controller
{
private readonly ILanguageRepository _languages;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public LanguageController(ILanguageRepository language, ILogManager logger, ITenantManager tenantManager)
public LanguageController(ILanguageRepository language, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{
_languages = language;
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
}
[HttpGet]
public IEnumerable<Language> Get(string siteid)
public IEnumerable<Language> Get(string siteid, string packagename)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
return _languages.GetLanguages(SiteId);
if (string.IsNullOrEmpty(packagename))
{
packagename = "Oqtane";
}
var languages = _languages.GetLanguages(SiteId).ToList();
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}.*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
{
var code = Path.GetFileName(Path.GetDirectoryName(file));
if (languages.Any(item => item.Code == code))
{
languages.Single(item => item.Code == code).Version = FileVersionInfo.GetVersionInfo(file).FileVersion;
}
}
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName, Version = Constants.Version, IsDefault = !languages.Any(l => l.IsDefault) });
return languages.OrderBy(item => item.Name);
}
else
{
@ -63,6 +86,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && language.SiteId == _alias.SiteId)
{
language = _languages.AddLanguage(language);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language);
}
else
@ -82,6 +106,7 @@ namespace Oqtane.Controllers
if (language != null && language.SiteId == _alias.SiteId)
{
_languages.DeleteLanguage(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id);
}
else
@ -89,7 +114,6 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Language Delete Attempt {LanguageId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@ -22,11 +22,15 @@ namespace Oqtane.Controllers
// GET: api/localization
[HttpGet()]
public IEnumerable<Culture> Get()
=> _localizationManager.GetSupportedCultures().Select(c => new Culture {
Name = CultureInfo.GetCultureInfo(c).Name,
DisplayName = CultureInfo.GetCultureInfo(c).DisplayName,
IsDefault = _localizationManager.GetDefaultCulture()
{
var cultures = _localizationManager.GetSupportedCultures().Select(c => new Culture
{
Name = CultureInfo.GetCultureInfo(c).Name,
DisplayName = CultureInfo.GetCultureInfo(c).DisplayName,
IsDefault = _localizationManager.GetDefaultCulture()
.Equals(CultureInfo.GetCultureInfo(c).Name, StringComparison.OrdinalIgnoreCase)
});
});
return cultures.OrderBy(item => item.DisplayName);
}
}
}

View File

@ -63,6 +63,8 @@ namespace Oqtane.Controllers
module.CreatedOn = pagemodule.Module.CreatedOn;
module.ModifiedBy = pagemodule.Module.ModifiedBy;
module.ModifiedOn = pagemodule.Module.ModifiedOn;
module.DeletedBy = pagemodule.DeletedBy;
module.DeletedOn = pagemodule.DeletedOn;
module.IsDeleted = pagemodule.IsDeleted;
module.PageModuleId = pagemodule.PageModuleId;
@ -139,24 +141,41 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Registered)]
public Module Put(int id, [FromBody] Module module)
{
if (ModelState.IsValid && module.SiteId == _alias.SiteId && _modules.GetModule(module.ModuleId, false) != null && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
var _module = _modules.GetModule(module.ModuleId, false);
if (ModelState.IsValid && module.SiteId == _alias.SiteId && _module != null && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
{
module = _modules.UpdateModule(module);
if (module.AllPages)
{
var pageModule = _pageModules.GetPageModules(module.SiteId).FirstOrDefault(item => item.ModuleId == module.ModuleId);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module);
var pages = _pages.GetPages(module.SiteId).ToList();
foreach (Page page in pages)
if (_module.AllPages != module.AllPages)
{
var pageModules = _pageModules.GetPageModules(module.SiteId).ToList();
if (module.AllPages)
{
if (page.PageId != pageModule.PageId && !page.Path.StartsWith("admin/"))
var pageModule = _pageModules.GetPageModule(module.PageModuleId);
var pages = _pages.GetPages(module.SiteId).ToList();
foreach (Page page in pages)
{
_pageModules.AddPageModule(new PageModule { PageId = page.PageId, ModuleId = pageModule.ModuleId, Title = pageModule.Title, Pane = pageModule.Pane, Order = pageModule.Order, ContainerType = pageModule.ContainerType });
if (!pageModules.Exists(item => item.ModuleId == module.ModuleId && item.PageId == page.PageId) && !page.Path.StartsWith("admin/"))
{
_pageModules.AddPageModule(new PageModule { PageId = page.PageId, ModuleId = pageModule.ModuleId, Title = pageModule.Title, Pane = pageModule.Pane, Order = pageModule.Order, ContainerType = pageModule.ContainerType });
}
}
}
else
{
foreach (var pageModule in pageModules)
{
if (pageModule.ModuleId == module.ModuleId && pageModule.PageModuleId != module.PageModuleId)
{
_pageModules.DeletePageModule(pageModule.PageModuleId);
}
}
}
}
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module);
}
else
{

View File

@ -307,9 +307,9 @@ namespace Oqtane.Controllers
if (moduleDefinition.Version == "local")
{
text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", "<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[ServerReference]", "<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Server.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", "<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>");
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[ServerReference]", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Server.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>");
}
else
{

View File

@ -53,7 +53,7 @@ namespace Oqtane.Controllers
catch (Exception ex)
{
results.Add(new Dictionary<string, string>() { { "Error", ex.Message } });
_logger.Log(LogLevel.Error, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId} Resulted In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message);
_logger.Log(LogLevel.Error, this, LogFunction.Other, ex, "Sql Query {Query} Executed on Tenant {TenantId} Resulted In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message);
}
sqlquery.Results = results;
return sqlquery;

View File

@ -38,6 +38,7 @@ namespace Oqtane.Controllers
systeminfo.Add("TickCount", Environment.TickCount64.ToString());
systeminfo.Add("ContentRootPath", _environment.ContentRootPath);
systeminfo.Add("WebRootPath", _environment.WebRootPath);
systeminfo.Add("Environment", _environment.EnvironmentName);
systeminfo.Add("ServerTime", DateTime.UtcNow.ToString());
var feature = HttpContext.Features.Get<IHttpConnectionFeature>();
systeminfo.Add("IPAddress", feature?.LocalIpAddress?.ToString());

View File

@ -188,8 +188,8 @@ namespace Oqtane.Controllers
if (theme.Version == "local")
{
text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", "<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", "<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>");
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>");
}
else
{

View File

@ -327,6 +327,8 @@ namespace Oqtane.Controllers
var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, true);
if (result.Succeeded)
{
var LastIPAddress = user.LastIPAddress ?? "";
user = _users.GetUser(user.Username);
if (user.TwoFactorRequired)
{
@ -353,7 +355,7 @@ namespace Oqtane.Controllers
{
loginUser.IsAuthenticated = true;
loginUser.LastLoginOn = DateTime.UtcNow;
loginUser.LastIPAddress = HttpContext.Connection.RemoteIpAddress.ToString();
loginUser.LastIPAddress = LastIPAddress;
_users.UpdateUser(loginUser);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", user.Username);
}

View File

@ -39,32 +39,24 @@ namespace Oqtane.Controllers
public IEnumerable<UserRole> Get(string siteid, string userid = null, string rolename = null)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null || rolename != null))
{
int UserId = (int.TryParse(userid, out UserId)) ? UserId : -1;
if (User.IsInRole(RoleNames.Admin) || ((userid == null || _userPermissions.GetUser().UserId == UserId) && (rolename == null || (User.IsInRole(rolename) && rolename != RoleNames.Registered))))
var userroles = _userRoles.GetUserRoles(SiteId).ToList();
if (userid != null)
{
var userroles = _userRoles.GetUserRoles(SiteId).ToList();
if (userid != null)
{
userroles = userroles.Where(item => item.UserId == UserId).ToList();
}
if (rolename != null)
{
userroles = userroles.Where(item => item.Role.Name == rolename).ToList();
}
for (int i = 0; i < userroles.Count(); i++)
{
userroles[i] = Filter(userroles[i]);
}
return userroles.OrderBy(u => u.User.DisplayName);
int UserId = int.TryParse(userid, out UserId) ? UserId : -1;
userroles = userroles.Where(item => item.UserId == UserId).ToList();
}
else
if (rolename != null)
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UserRole Get Attempt For Site {SiteId} User {UserId} Role {RoleName}", siteid, userid, rolename);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
userroles = userroles.Where(item => item.Role.Name == rolename).ToList();
}
var user = _userPermissions.GetUser();
for (int i = 0; i < userroles.Count(); i++)
{
userroles[i] = Filter(userroles[i], user.UserId);
}
return userroles.OrderBy(u => u.User.DisplayName);
}
else
{
@ -82,16 +74,7 @@ namespace Oqtane.Controllers
var userrole = _userRoles.GetUserRole(id);
if (userrole != null && SiteValid(userrole.Role.SiteId))
{
if (User.IsInRole(RoleNames.Admin) || User.Identity.Name?.ToLower() != userrole.User.Username.ToLower() || User.IsInRole(userrole.Role.Name))
{
return Filter(userrole);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Role Get Attempt {UserRoleId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
return Filter(userrole, _userPermissions.GetUser().UserId);
}
else
{
@ -101,7 +84,7 @@ namespace Oqtane.Controllers
}
}
private UserRole Filter(UserRole userrole)
private UserRole Filter(UserRole userrole, int userid)
{
if (userrole != null)
{
@ -110,7 +93,7 @@ namespace Oqtane.Controllers
userrole.User.TwoFactorCode = "";
userrole.User.TwoFactorExpiry = null;
if (!User.IsInRole(RoleNames.Admin) && User.Identity.Name?.ToLower() != userrole.User.Username.ToLower())
if (!User.IsInRole(RoleNames.Admin) && userid != userrole.User.UserId)
{
userrole.User.Email = "";
userrole.User.PhotoFileId = null;

View File

@ -81,7 +81,7 @@ namespace Oqtane.Databases
builder.DropColumn(name, table);
}
public virtual void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode)
public virtual void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode, string index)
{
builder.AlterColumn<string>(RewriteName(name), RewriteName(table), maxLength: length, nullable: nullable, unicode: unicode);
}

View File

@ -34,7 +34,7 @@ namespace Oqtane.Databases.Interfaces
public void DropColumn(MigrationBuilder builder, string name, string table);
public void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode);
public void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode, string index);
public DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString);
}

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
@ -17,6 +18,7 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Repository;
using Oqtane.Security;
@ -248,7 +250,7 @@ namespace Microsoft.Extensions.DependencyInjection
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// dynamically register module services, contexts, and repository classes
// dynamically register module scoped services (ie. client service classes)
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
@ -259,6 +261,17 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
// dynamically register module transient services (ie. server DBContext, repository classes)
implementationTypes = assembly.GetInterfaces<ITransientService>();
foreach (var implementationType in implementationTypes)
{
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
services.AddTransient(serviceType ?? implementationType, implementationType);
}
}
// dynamically register hosted services
var serviceTypes = assembly.GetTypes(hostedServiceType);
foreach (var serviceType in serviceTypes)
@ -315,52 +328,26 @@ namespace Microsoft.Extensions.DependencyInjection
private static void LoadSatelliteAssemblies(string[] supportedCultures)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (assemblyPath == null)
{
return;
}
AssemblyLoadContext.Default.Resolving += ResolveDependencies;
foreach (var culture in supportedCultures)
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
{
if (culture == Constants.DefaultCulture)
var code = Path.GetFileName(Path.GetDirectoryName(file));
if (supportedCultures.Contains(code))
{
continue;
}
var assembliesFolder = new DirectoryInfo(Path.Combine(assemblyPath, culture));
if (assembliesFolder.Exists)
{
foreach (var assemblyFile in assembliesFolder.EnumerateFiles($"*{Constants.SatelliteAssemblyExtension}"))
try
{
AssemblyName assemblyName;
try
{
assemblyName = AssemblyName.GetAssemblyName(assemblyFile.FullName);
}
catch
{
Debug.WriteLine($"Oqtane Error: Cannot Get Satellite Assembly Name For {assemblyFile.Name}");
continue;
}
try
{
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(System.IO.File.ReadAllBytes(assemblyFile.FullName)));
Debug.WriteLine($"Oqtane Info: Loaded Assembly {assemblyName}");
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Unable To Load Assembly {assemblyName} - {ex}");
}
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(System.IO.File.ReadAllBytes(file)));
Debug.WriteLine($"Oqtane Info: Loaded Satellite Assembly {file}");
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Unable To Load Satellite Assembly {file} - {ex}");
}
}
else
{
Debug.WriteLine($"Oqtane Error: The Satellite Assembly Folder For {culture} Does Not Exist");
Debug.WriteLine($"Oqtane Error: Culture Not Supported For Satellite Assembly {file}");
}
}
}

View File

@ -66,6 +66,20 @@ namespace Oqtane.Extensions
options.Events.OnTokenValidated = OnTokenValidated;
options.Events.OnAccessDenied = OnAccessDenied;
options.Events.OnRemoteFailure = OnRemoteFailure;
if (sitesettings.GetValue("ExternalLogin:Parameters", "") != "")
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
foreach(var parameter in sitesettings.GetValue("ExternalLogin:Parameters", "").Split(","))
{
context.ProtocolMessage.SetParameter(parameter.Split("=")[0], parameter.Split("=")[1]);
}
return Task.FromResult(0);
}
};
}
}
});
@ -100,6 +114,22 @@ namespace Oqtane.Extensions
options.Events.OnTicketReceived = OnTicketReceived;
options.Events.OnAccessDenied = OnAccessDenied;
options.Events.OnRemoteFailure = OnRemoteFailure;
if (sitesettings.GetValue("ExternalLogin:Parameters", "") != "")
{
options.Events = new OAuthEvents
{
OnRedirectToAuthorizationEndpoint = context =>
{
var url = context.RedirectUri;
foreach (var parameter in sitesettings.GetValue("ExternalLogin:Parameters", "").Split(","))
{
url += (!url.Contains("?")) ? "?" + parameter : "&" + parameter;
}
context.Response.Redirect(url);
return Task.FromResult(0);
}
};
}
}
});
@ -111,6 +141,7 @@ namespace Oqtane.Extensions
// OAuth 2.0
var email = "";
var id = "";
var claims = "";
if (context.Options.UserInformationEndpoint != "")
{
@ -123,16 +154,16 @@ namespace Oqtane.Extensions
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var output = await response.Content.ReadAsStringAsync();
claims = await response.Content.ReadAsStringAsync();
// parse json output
var idClaimType = context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:IdentifierClaimType", "");
var emailClaimType = context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:EmailClaimType", "");
if (!output.StartsWith("[") && !output.EndsWith("]"))
if (!claims.StartsWith("[") && !claims.EndsWith("]"))
{
output = "[" + output + "]"; // convert to json array
claims = "[" + claims + "]"; // convert to json array
}
JsonNode items = JsonNode.Parse(output)!;
JsonNode items = JsonNode.Parse(claims)!;
foreach (var item in items.AsArray())
{
if (item[emailClaimType] != null)
@ -156,12 +187,12 @@ namespace Oqtane.Extensions
catch (Exception ex)
{
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "An Error Occurred Accessing The User Info Endpoint - {Error}", ex.Message);
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, ex, "An Error Occurred Accessing The User Info Endpoint - {Error}", ex.Message);
}
}
// validate user
var identity = await ValidateUser(email, id, context.HttpContext);
var identity = await ValidateUser(email, id, claims, context.HttpContext);
if (identity.Label == ExternalLoginStatus.Success)
{
identity.AddClaim(new Claim("access_token", context.AccessToken));
@ -193,9 +224,10 @@ namespace Oqtane.Extensions
var id = context.Principal.FindFirstValue(idClaimType);
var emailClaimType = context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:EmailClaimType", "");
var email = context.Principal.FindFirstValue(emailClaimType);
var claims = string.Join(", ", context.Principal.Claims.Select(item => item.Type).ToArray());
// validate user
var identity = await ValidateUser(email, id, context.HttpContext);
var identity = await ValidateUser(email, id, claims, context.HttpContext);
if (identity.Label == ExternalLoginStatus.Success)
{
identity.AddClaim(new Claim("access_token", context.SecurityToken.RawData));
@ -229,7 +261,7 @@ namespace Oqtane.Extensions
return Task.CompletedTask;
}
private static async Task<ClaimsIdentity> ValidateUser(string email, string id, HttpContext httpContext)
private static async Task<ClaimsIdentity> ValidateUser(string email, string id, string claims, HttpContext httpContext)
{
var _logger = httpContext.RequestServices.GetRequiredService<ILogManager>();
ClaimsIdentity identity = new ClaimsIdentity(Constants.AuthenticationScheme);
@ -241,142 +273,149 @@ namespace Oqtane.Extensions
var _users = httpContext.RequestServices.GetRequiredService<IUserRepository>();
User user = null;
// verify if external user is already registerd for this site
var _identityUserManager = httpContext.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
var identityuser = await _identityUserManager.FindByLoginAsync(providerType + ":" + alias.SiteId.ToString(), id);
if (identityuser != null)
if (!string.IsNullOrEmpty(id))
{
user = _users.GetUser(identityuser.UserName);
}
else
{
if (EmailValid(email, httpContext.GetSiteSettings().GetValue("ExternalLogin:DomainFilter", "")))
// verify if external user is already registered for this site
var _identityUserManager = httpContext.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
var identityuser = await _identityUserManager.FindByLoginAsync(providerType + ":" + alias.SiteId.ToString(), id);
if (identityuser != null)
{
bool duplicates = false;
try
user = _users.GetUser(identityuser.UserName);
}
else
{
if (EmailValid(email, httpContext.GetSiteSettings().GetValue("ExternalLogin:DomainFilter", "")))
{
identityuser = await _identityUserManager.FindByEmailAsync(email);
}
catch
{
// FindByEmailAsync will throw an error if the email matches multiple user accounts
duplicates = true;
}
if (identityuser == null)
{
if (duplicates)
bool duplicates = false;
try
{
identity.Label = ExternalLoginStatus.DuplicateEmail;
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Multiple Users Exist With Email Address {Email}. Login Denied.", email);
identityuser = await _identityUserManager.FindByEmailAsync(email);
}
else
catch
{
if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:CreateUsers", "true")))
// FindByEmailAsync will throw an error if the email matches multiple user accounts
duplicates = true;
}
if (identityuser == null)
{
if (duplicates)
{
identityuser = new IdentityUser();
identityuser.UserName = email;
identityuser.Email = email;
identityuser.EmailConfirmed = true;
var result = await _identityUserManager.CreateAsync(identityuser, DateTime.UtcNow.ToString("yyyy-MMM-dd-HH-mm-ss"));
if (result.Succeeded)
identity.Label = ExternalLoginStatus.DuplicateEmail;
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Multiple Users Exist With Email Address {Email}. Login Denied.", email);
}
else
{
if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:CreateUsers", "true")))
{
user = new User
identityuser = new IdentityUser();
identityuser.UserName = email;
identityuser.Email = email;
identityuser.EmailConfirmed = true;
var result = await _identityUserManager.CreateAsync(identityuser, DateTime.UtcNow.ToString("yyyy-MMM-dd-HH-mm-ss"));
if (result.Succeeded)
{
SiteId = alias.SiteId,
Username = email,
DisplayName = email,
Email = email,
LastLoginOn = null,
LastIPAddress = ""
};
user = _users.AddUser(user);
user = new User
{
SiteId = alias.SiteId,
Username = email,
DisplayName = email,
Email = email,
LastLoginOn = null,
LastIPAddress = ""
};
user = _users.AddUser(user);
if (user != null)
{
var _notifications = httpContext.RequestServices.GetRequiredService<INotificationRepository>();
string url = httpContext.Request.Scheme + "://" + alias.Name;
string body = "You Recently Used An External Account To Sign In To Our Site.\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, user, "User Account Notification", body);
_notifications.AddNotification(notification);
if (user != null)
{
var _notifications = httpContext.RequestServices.GetRequiredService<INotificationRepository>();
string url = httpContext.Request.Scheme + "://" + alias.Name;
string body = "You Recently Used An External Account To Sign In To Our Site.\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, user, "User Account Notification", body);
_notifications.AddNotification(notification);
// add user login
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + alias.SiteId.ToString(), id, providerName));
// add user login
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + alias.SiteId.ToString(), id, providerName));
_logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "User Added {User}", user);
_logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "User Added {User}", user);
}
else
{
identity.Label = ExternalLoginStatus.UserNotCreated;
_logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add User {Email}", email);
}
}
else
{
identity.Label = ExternalLoginStatus.UserNotCreated;
_logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add User {Email}", email);
_logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add Identity User {Email} {Error}", email, result.Errors.ToString());
}
}
else
{
identity.Label = ExternalLoginStatus.UserNotCreated;
_logger.Log(user.SiteId, LogLevel.Error, "ExternalLogin", Enums.LogFunction.Create, "Unable To Add Identity User {Email} {Error}", email, result.Errors.ToString());
identity.Label = ExternalLoginStatus.UserDoesNotExist;
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Creation Of New Users Is Disabled For This Site. User With Email Address {Email} Will First Need To Be Registered On The Site.", email);
}
}
else
{
identity.Label = ExternalLoginStatus.UserDoesNotExist;
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Creation Of New Users Is Disabled For This Site. User With Email Address {Email} Will First Need To Be Registered On The Site.", email);
}
}
}
else
{
var logins = await _identityUserManager.GetLoginsAsync(identityuser);
var login = logins.FirstOrDefault(item => item.LoginProvider == (providerType + ":" + alias.SiteId.ToString()));
if (login == null)
{
// new external login using existing user account - verification required
var _notifications = httpContext.RequestServices.GetRequiredService<INotificationRepository>();
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = httpContext.Request.Scheme + "://" + alias.Name;
url += $"/login?name={identityuser.UserName}&token={WebUtility.UrlEncode(token)}&key={WebUtility.UrlEncode(id)}";
string body = $"You Recently Signed In To Our Site With {providerName} Using The Email Address {email}. ";
body += "In Order To Complete The Linkage Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(alias.SiteId, email, email, "External Login Linkage", body);
_notifications.AddNotification(notification);
identity.Label = ExternalLoginStatus.VerificationRequired;
_logger.Log(alias.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Verification For Provider {Provider} Sent To {Email}", providerName, email);
}
else
{
// provider keys do not match
identity.Label = ExternalLoginStatus.ProviderKeyMismatch;
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Key Does Not Match For User {Username}. Login Denied.", identityuser.UserName);
var logins = await _identityUserManager.GetLoginsAsync(identityuser);
var login = logins.FirstOrDefault(item => item.LoginProvider == (providerType + ":" + alias.SiteId.ToString()));
if (login == null)
{
// new external login using existing user account - verification required
var _notifications = httpContext.RequestServices.GetRequiredService<INotificationRepository>();
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = httpContext.Request.Scheme + "://" + alias.Name;
url += $"/login?name={identityuser.UserName}&token={WebUtility.UrlEncode(token)}&key={WebUtility.UrlEncode(id)}";
string body = $"You Recently Signed In To Our Site With {providerName} Using The Email Address {email}. ";
body += "In Order To Complete The Linkage Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(alias.SiteId, email, email, "External Login Linkage", body);
_notifications.AddNotification(notification);
identity.Label = ExternalLoginStatus.VerificationRequired;
_logger.Log(alias.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Verification For Provider {Provider} Sent To {Email}", providerName, email);
}
else
{
// provider keys do not match
identity.Label = ExternalLoginStatus.ProviderKeyMismatch;
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Key Does Not Match For User {Username}. Login Denied.", identityuser.UserName);
}
}
}
else // email invalid
{
identity.Label = ExternalLoginStatus.InvalidEmail;
if (!string.IsNullOrEmpty(email))
{
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The Email Address {Email} Is Invalid Or Does Not Match The Domain Filter Criteria. Login Denied.", email);
}
else
{
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return An Email Address To Uniquely Identify The User. The Email Claim Specified Was {EmailCLaimType} And Actual Claim Types Are {Claims}. Login Denied.", httpContext.GetSiteSettings().GetValue("ExternalLogin:EmailClaimType", ""), claims);
}
}
}
else // email invalid
// manage user
if (user != null)
{
identity.Label = ExternalLoginStatus.InvalidEmail;
if (!string.IsNullOrEmpty(email))
{
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The Email Address {Email} Is Invalid Or Does Not Match The Domain Filter Criteria. Login Denied.", email);
}
else
{
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return An Email To Uniquely Identify The User.");
}
// create claims identity
var _userRoles = httpContext.RequestServices.GetRequiredService<IUserRoleRepository>();
identity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList());
identity.Label = ExternalLoginStatus.Success;
// update user
user.LastLoginOn = DateTime.UtcNow;
user.LastIPAddress = httpContext.Connection.RemoteIpAddress.ToString();
_users.UpdateUser(user);
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} Using Provider {Provider}", user.Username, providerName);
}
}
// manage user
if (user != null)
else // id invalid
{
// create claims identity
var _userRoles = httpContext.RequestServices.GetRequiredService<IUserRoleRepository>();
identity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList());
identity.Label = ExternalLoginStatus.Success;
// update user
user.LastLoginOn = DateTime.UtcNow;
user.LastIPAddress = httpContext.Connection.RemoteIpAddress.ToString();
_users.UpdateUser(user);
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} Using Provider {Provider}", user.Username, providerName);
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return An Identifier To Uniquely Identify The User. The Identifier Claim Specified Was {IdentifierCLaimType} And Actual Claim Types Are {Claims}. Login Denied.", httpContext.GetSiteSettings().GetValue("ExternalLogin:IdentifierClaimType", ""), claims);
}
return identity;

View File

@ -48,26 +48,35 @@ namespace Oqtane.Infrastructure
}
// move packages to secure /Packages folder
foreach (var folder in "Modules,Themes,Packages".Split(","))
foreach (var folderName in "Modules,Themes,Packages".Split(","))
{
foreach(var file in Directory.GetFiles(Path.Combine(webRootPath, folder), "*.nupkg*"))
string folder = Path.Combine(webRootPath, folderName);
if (Directory.Exists(folder))
{
var destinationFile = Path.Combine(sourceFolder, Path.GetFileName(file));
if (File.Exists(destinationFile))
foreach (var file in Directory.GetFiles(folder, "*.nupkg*"))
{
File.Delete(destinationFile);
}
if (destinationFile.ToLower().EndsWith(".nupkg.bak"))
{
// leave a copy in the current folder as it is distributed with the core framework
File.Copy(file, destinationFile);
}
else
{
// move to destination
File.Move(file, destinationFile);
var destinationFile = Path.Combine(sourceFolder, Path.GetFileName(file));
if (File.Exists(destinationFile))
{
File.Delete(destinationFile);
}
if (destinationFile.ToLower().EndsWith(".nupkg.bak"))
{
// leave a copy in the current folder as it is distributed with the core framework
File.Copy(file, destinationFile);
}
else
{
// move to destination
File.Move(file, destinationFile);
}
}
}
else
{
Directory.CreateDirectory(folder);
}
}
// iterate through Nuget packages in source folder

View File

@ -120,6 +120,10 @@ namespace Oqtane.Infrastructure
mailMessage.Body += "Subject: " + notification.Subject + "\n\n";
mailMessage.Body += notification.Body;
// set encoding
mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
// send mail
try
{

View File

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Options;
using Oqtane.Shared;
@ -11,7 +8,6 @@ namespace Oqtane.Infrastructure
public class LocalizationManager : ILocalizationManager
{
private static readonly string DefaultCulture = Constants.DefaultCulture;
private static readonly string[] DefaultSupportedCultures = new[] { DefaultCulture };
private readonly LocalizationOptions _localizationOptions;
@ -21,19 +17,20 @@ namespace Oqtane.Infrastructure
}
public string GetDefaultCulture()
=> String.IsNullOrEmpty(_localizationOptions.DefaultCulture)
? DefaultCulture
: _localizationOptions.DefaultCulture;
{
if (string.IsNullOrEmpty(_localizationOptions.DefaultCulture))
{
return DefaultCulture;
}
else
{
return _localizationOptions.DefaultCulture;
}
}
public string[] GetSupportedCultures()
{
var cultures = new List<string>(DefaultSupportedCultures);
foreach(var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Oqtane.Client.resources.dll", SearchOption.AllDirectories))
{
cultures.Add(Path.GetFileName(Path.GetDirectoryName(file)));
}
return cultures.OrderBy(c => c).ToArray();
return CultureInfo.GetCultures(CultureTypes.AllCultures).Select(item => item.Name).OrderBy(c => c).ToArray();
}
}
}

View File

@ -203,12 +203,15 @@ namespace Oqtane.Infrastructure
}
if (Enum.Parse<LogLevel>(log.Level) >= notifylevel)
{
var alias = _tenantManager.GetAlias();
foreach (var userrole in _userRoles.GetUserRoles(log.SiteId.Value))
{
if (userrole.Role.Name == RoleNames.Host)
{
var url = _accessor.HttpContext.Request.Scheme + "://" + _tenantManager.GetAlias().Name + "/admin/log";
var notification = new Notification(log.SiteId.Value, userrole.User, "Site " + log.Level + " Notification", "Please visit " + url + " for more information");
var subject = $"{alias.Name} Site {log.Level} Notification";
var url = $"{_accessor.HttpContext.Request.Scheme}://{alias.Name}/admin/log?id={log.LogId}";
string body = $"Log Message: {log.Message}\n\nPlease visit {url} for more information";
var notification = new Notification(log.SiteId.Value, userrole.User, subject, body);
_notifications.AddNotification(notification);
}
}

View File

@ -62,7 +62,7 @@ namespace Oqtane.SiteTemplates
"<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> is an open source and cross-platform web UI framework for building single-page apps using .NET and C# instead of JavaScript. Blazor WebAssembly relies on Wasm, an open web standard that does not require plugins or code transpilation in order to run natively in a web browser. Blazor Server uses SignalR to host your application on a web server and provide a responsive and robust development experience. Blazor applications work in all modern web browsers, including mobile browsers.</p>" +
"<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">.NET Core</a>, the popular cross platform web development framework from Microsoft that extends the <a href=\"https://dotnet.microsoft.com/learn/dotnet/what-is-dotnet\" target=\"_new\" >.NET developer platform</a> with tools and libraries for building web apps.</p>"
},
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = "Content",
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),

View File

@ -1,7 +1,5 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Oqtane.Extensions;
using Oqtane.Models;
using Oqtane.Repository;
@ -50,6 +48,12 @@ namespace Oqtane.Infrastructure
case "3.0.1":
Upgrade_3_0_1(tenant, scope);
break;
case "3.1.3":
Upgrade_3_1_3(tenant, scope);
break;
case "3.1.4":
Upgrade_3_1_4(tenant, scope);
break;
}
}
}
@ -182,5 +186,57 @@ namespace Oqtane.Infrastructure
sites.CreatePages(site, pageTemplates);
}
}
private void Upgrade_3_1_3(Tenant tenant, IServiceScope scope)
{
var roles = scope.ServiceProvider.GetRequiredService<IRoleRepository>();
if (!roles.GetRoles(-1, true).ToList().Where(item => item.Name == RoleNames.Unauthenticated).Any())
{
roles.AddRole(new Role { SiteId = null, Name = RoleNames.Unauthenticated, Description = RoleNames.Unauthenticated, IsAutoAssigned = false, IsSystem = true });
}
}
private void Upgrade_3_1_4(Tenant tenant, IServiceScope scope)
{
var pageTemplates = new List<PageTemplate>();
pageTemplates.Add(new PageTemplate
{
Name = "Not Found",
Parent = "",
Path = "404",
Icon = Icons.X,
IsNavigation = false,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Not Found", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = "<p>The page you requested does not exist.</p>"
}
}
});
var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>();
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (Site site in sites.GetSites().ToList())
{
if (!pages.GetPages(site.SiteId).ToList().Where(item => item.Path == "404").Any())
{
sites.CreatePages(site, pageTemplates);
}
}
}
}
}

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