Compare commits

...

130 Commits

Author SHA1 Message Date
3d692e4198 Merge pull request #3132 from oqtane/master
Merge pull request #3131 from oqtane/dev
2023-08-09 08:22:44 -04:00
215ca9f755 Merge pull request #3131 from oqtane/dev
4.0.2 Release
2023-08-09 08:22:24 -04:00
7dbcb24f3d Merge pull request #3129 from alikoli/fix-missing-translations
Fix missing translations
2023-08-09 08:16:06 -04:00
18b4bd62f7 Update appsettings.json 2023-08-09 04:06:14 +02:00
20f3cf5327 Add empty appsettings 2023-08-09 03:58:34 +02:00
c1d35e8af5 Fix missing translations 2023-08-09 03:06:15 +02:00
e1cd318f61 Merge pull request #3128 from sbwalker/dev
fix installed cultures logic to recognize all satellite resources
2023-08-08 13:22:10 -04:00
4da0952091 fix installed cultures logic to recognize all satellite resources 2023-08-08 13:21:56 -04:00
a60f75f0a9 Merge pull request #3127 from sbwalker/dev
fix refresh logic in router
2023-08-08 13:07:43 -04:00
3814009ad9 fix refresh logic in router 2023-08-08 13:07:29 -04:00
fe7bb00112 Merge pull request #3125 from leigh-pointer/ModuleTitleLoc
Updated Module Title using SetModuleTitle on ModuleBase
2023-08-08 12:23:36 -04:00
2356753dfc Updated Module Title using SetModuleTitle on ModuleBase 2023-08-08 16:37:40 +02:00
bd23ec268b Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-08 15:34:25 +02:00
1d5f23c3c7 Merge pull request #3123 from leigh-pointer/TransKeys
Trans keys
2023-08-08 09:33:43 -04:00
f424bf53b3 Revert Title to "Folder Management"
This is due to the public virtual string Title can not use the Localize instance as it not yet created so error with null reference.
2023-08-08 15:30:39 +02:00
efbdf0006e Merge remote-tracking branch 'oqtane/dev' into TransKeys 2023-08-08 15:22:03 +02:00
625b173ee4 Merge pull request #3122 from leigh-pointer/TransKeys
Updated the Settings Heading
2023-08-08 08:05:21 -04:00
0a8d9bc3d3 Updated the Settings Heading 2023-08-08 14:01:45 +02:00
0d0909a461 Merge pull request #3120 from leigh-pointer/TransKeys
Translation for thread #3094
2023-08-08 07:55:35 -04:00
ebb0a538f8 Merge pull request #3121 from sbwalker/dev
improve sync service to always rely on server dates
2023-08-08 07:51:26 -04:00
337a566617 improve sync service to always rely on server dates 2023-08-08 07:51:07 -04:00
fecee7a12b Translation for thread #3094
Updated the Missing or incorrect keys
2023-08-08 13:30:10 +02:00
282ec99ce8 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-08 12:14:18 +02:00
17a4985ebe Merge pull request #3119 from sbwalker/dev
fix Section component localization
2023-08-07 17:11:36 -04:00
13b17d91a9 fix Section component localizatioon 2023-08-07 17:11:17 -04:00
5782005007 Merge pull request #3118 from sbwalker/dev
improve reload in router to prevent looping
2023-08-07 15:40:00 -04:00
258f2dbe8f improve reload in router to prevent looping 2023-08-07 15:39:44 -04:00
e7b35bd0c2 Merge pull request #3117 from sbwalker/dev
fix #3094 - localization of admin module titles
2023-08-07 12:14:11 -04:00
176bd229e6 fix #3094 - localization of admin module titles 2023-08-07 12:13:53 -04:00
c12f1251b0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-07 18:07:11 +02:00
0710736661 Merge pull request #3116 from sbwalker/dev
improve code documentation
2023-08-07 10:39:40 -04:00
51ebe520f4 improve code documentation 2023-08-07 10:39:24 -04:00
1ef566c824 Merge pull request #3115 from sbwalker/dev
fix issue where user could not be shared across multiple sites
2023-08-07 09:58:41 -04:00
96cf06fe8d fix issue where user could not be shared across multiple sites 2023-08-07 09:58:24 -04:00
38ebfdcd0c Merge pull request #3114 from sbwalker/dev
fix #3108 - raise reload event after user logs out
2023-08-07 09:34:37 -04:00
b5649e2a6f fix #3108 - raise reload event after user logs out 2023-08-07 09:34:20 -04:00
5fc6dadeae Merge pull request #3113 from sbwalker/dev
prepare for 4.0.2 release
2023-08-06 08:34:05 -04:00
22cfec9276 prepare for 4.0.2 release 2023-08-06 08:33:36 -04:00
0a82ec5f0a Update README.md 2023-08-06 08:32:05 -04:00
3bdd1f6c4f Merge pull request #3112 from sbwalker/dev
use new GetJsonAsync method in external module template
2023-08-06 08:19:16 -04:00
1e466dc1fe use new GetJsonAsync method in external module template 2023-08-06 08:17:56 -04:00
3b302d2f86 Merge pull request #3111 from sbwalker/dev
change Help button style
2023-08-06 07:58:50 -04:00
7f108eebf9 change Help button style 2023-08-06 07:58:27 -04:00
9d41fee2fe Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-06 09:39:41 +02:00
bef686f95a Merge pull request #3110 from sbwalker/dev
change defaultvalue parameter to defaultResult to make more intuitive
2023-08-05 16:35:04 -04:00
4bdcb974bd change defaultvalue parameter to defaultResult to make more intuitive 2023-08-05 16:34:41 -04:00
af042bd23c Merge pull request #3109 from sbwalker/dev
introduce new GetJsonAsync method with default value parameter
2023-08-05 09:01:03 -04:00
19e3cef7dd introduce new GetJsonAsync method with default value parameter 2023-08-05 09:00:38 -04:00
085ab4bfb4 Merge pull request #3107 from sbwalker/dev
add transparency support on image resizing
2023-08-04 16:07:50 -04:00
f7bd03d051 add transparency support on image resizing 2023-08-04 16:07:37 -04:00
b48d751717 Merge pull request #3106 from sbwalker/dev
update Module and Theme Install UI to match Marketplace - including logos and support for sorting
2023-08-04 15:17:36 -04:00
92a4a1b210 update Module and Theme Install UI to match Marketplace - including logos and support for sorting 2023-08-04 15:17:17 -04:00
6a57fcc04a Merge pull request #3103 from sbwalker/dev
fix GetFolderByPath to support root folder path
2023-08-03 17:29:26 -04:00
749e11762f fix GetFolderByPath to support root folder path 2023-08-03 17:29:02 -04:00
61817726c3 Merge pull request #3102 from sbwalker/dev
fix issue where meta name="description" tags were being excluded from output
2023-08-03 16:02:49 -04:00
808354e969 fix issue where meta name="description" tags were being excluded from output 2023-08-03 16:02:34 -04:00
d2f594ff4a Merge pull request #3101 from sbwalker/dev
fix #3069 - exclude templates from release packages
2023-08-03 15:27:05 -04:00
fa18467cdd fix #3069 - exclude templates from release packages 2023-08-03 15:26:23 -04:00
4c940d02ef Merge pull request #3100 from sbwalker/dev
add error handling and logging to folder creation logic
2023-08-03 14:48:58 -04:00
04202a6b70 Merge branch 'dev' of https://github.com/sbwalker/oqtane.framework into dev 2023-08-03 14:45:39 -04:00
4483901270 fix #3072 - add error handling and logging to folder creation logic 2023-08-03 14:45:27 -04:00
f02d894697 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-03 20:25:17 +02:00
2bc130856a Merge pull request #3047 from leigh-pointer/FixTemplateNULL
Fixes Module Creator problem  #3041
2023-08-03 14:23:55 -04:00
aa3a4dca65 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-03 18:53:56 +02:00
4f65931f51 Merge pull request #3099 from sbwalker/dev
fix #3065 - redirect user if they are logged in and navigating to Login page
2023-08-03 12:47:02 -04:00
93be61e483 fix #3065 - redirect user if they are logged in and navigating to Login page 2023-08-03 12:46:42 -04:00
cb7fe364bc Merge pull request #3098 from sbwalker/dev
remove unecessary using statement added by Visual Studio
2023-08-03 11:51:38 -04:00
9cdcb4b22c remove unecessary using statement added by Visual Studio 2023-08-03 11:51:26 -04:00
c49072f9b6 Merge pull request #3097 from sbwalker/dev
fix #3082 - handle username claim as "unique_name" with "name" as fallback, improve validation logic and logging
2023-08-03 11:49:24 -04:00
61df26b667 fix #3082 - handle username claim as "unique_name" with "name" as fallback, improve validation logic and logging 2023-08-03 11:48:59 -04:00
25c935f1db Merge pull request #3096 from sbwalker/dev
fix #3093 - user email links include extra "://"
2023-08-03 11:11:31 -04:00
6b982bc0c7 fix #3093 - user email links include extra "://" 2023-08-03 11:11:14 -04:00
caccc803d3 Merge pull request #3095 from sbwalker/dev
improve appsettings.json in Maui client
2023-08-03 10:15:26 -04:00
5be640632b improve appsettings.json in Maui client 2023-08-03 10:15:12 -04:00
40912f0ffd Merge pull request #3092 from sbwalker/dev
fix WebAssembly startup alias handling
2023-08-03 08:25:59 -04:00
d518860a34 fix WebAssembly startup alias handling 2023-08-03 08:25:44 -04:00
185617ed9e Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-03 05:39:32 +02:00
5ba96b627e Merge pull request #3090 from sbwalker/dev
support for appsettings.json in Maui app
2023-08-02 17:23:41 -04:00
edf955f4cd support for appsettings.json in Maui app 2023-08-02 17:23:27 -04:00
8d0b04668e Merge pull request #3089 from sbwalker/dev
add appicon to Maui client project
2023-08-02 17:11:47 -04:00
a72e5e02da add appicon to Maui client project 2023-08-02 17:11:33 -04:00
4740404c2e Merge pull request #3088 from sbwalker/dev
add version to support url
2023-08-02 16:46:11 -04:00
34253916ea add version to support url 2023-08-02 16:45:59 -04:00
1d77ba2694 Merge pull request #3055 from leigh-pointer/AutoCompleteRequired
Extended AutoComplete control to allow the Required attribute
2023-08-02 14:53:14 -04:00
765041c587 Merge pull request #3058 from vnetonline/fix-file-manager-show-folders
[ENHANCE] - #3057 FileManager when ShowFolders = False however a Folder is set by path
2023-08-02 14:52:04 -04:00
8c6bca7666 Merge pull request #3087 from sbwalker/dev
trim dependencies for Themes (related to #3079)
2023-08-02 14:49:06 -04:00
c1b1cef590 trim dependencies for Themes (related to #3079) 2023-08-02 14:48:51 -04:00
507f7804c3 Merge pull request #3079 from vnetonline/fix-3078
[FIX] - #3078 - ModuleInfo Dependency needs to be trimmed so there is no white space (credit @maxmontgmx))
2023-08-02 14:45:23 -04:00
453bacf9bd Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-02 20:13:17 +02:00
25778458c6 Merge pull request #3086 from sbwalker/dev
Fix #3068 - support microsites in .NET MAUI
2023-08-02 13:55:23 -04:00
7a42646bed Fix #3068 - support microsites in .NET MAUI 2023-08-02 13:55:01 -04:00
755b7034d2 Merge pull request #3085 from sbwalker/dev
Fix #3068 - support microsites in .NET MAUI
2023-08-02 13:54:47 -04:00
122fcfd701 Fix #3068 - support microsites in .NET MAUI 2023-08-02 13:53:55 -04:00
2c905eddc2 Merge branch 'fix-file-manager-show-folders' of https://github.com/vnetonline/oqtane.framework into fix-file-manager-show-folders 2023-08-01 21:12:02 +10:00
3e8eb9abb5 [ENHANCE] - #3057 FileManager when ShowFolders = false however a Folder is set by path 2023-08-01 21:11:54 +10:00
d2df3b2d9a ModuleInfo Dependency needs to be trimmed so there is no white space at the end (credit @maxmontgmx)) 2023-08-01 21:02:19 +10:00
2a0c983c2e Handle if Parameter is set dynamically 2023-07-26 11:28:14 +02:00
1319432fde [Fix] - #3057 FileManager when ShowFolders = false however a Folder is set by path 2023-07-24 08:19:06 +10:00
02e2aeb6d1 Extended control to set the Required attribute
The control now allow the required attribute to be set.  This will now give better UX feedback.
2023-07-20 13:56:29 +02:00
5e35edd976 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-20 13:34:27 +02:00
4d63c6266c Merge pull request #3053 from sbwalker/dev
disable ServiceBase logic which does not work with legacy ControllerRoutes.Default routes (modules created prior to 2.1)
2023-07-19 20:07:46 -04:00
48f8d41993 disable ServiceBase logic which does not work with legacy ControllerRoutes.Default routes (modules created prior to 2.1) 2023-07-19 20:07:27 -04:00
0b41ccad83 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 22:39:24 +02:00
f994afbc11 Merge pull request #3051 from sbwalker/dev
fix #3048 - uploading to Packages folder showing unsuccessful message
2023-07-19 16:27:39 -04:00
5dea783677 fix #3048 - uploading to Packages folder showing unsuccessful message 2023-07-19 16:27:26 -04:00
347ef9a1d0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 22:16:18 +02:00
739aa5311c Merge pull request #3050 from sbwalker/dev
fix #3046 - user folders displayed in folder lists
2023-07-19 16:15:04 -04:00
805018286c fix #3046 - user folders displayed in folder lists 2023-07-19 16:14:48 -04:00
45f2a47ba5 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 21:53:10 +02:00
fe503b7bee Merge pull request #3049 from sbwalker/dev
FileManager modification to support ShowFolders = False
2023-07-19 15:45:25 -04:00
6736570ee7 FileManager modification to support ShowFolders = False 2023-07-19 15:45:12 -04:00
88c06eea6e Fixes Module Creator problem #3041
This fix is to handle a returned null from the database.  This issue came to light while using Postgresql
2023-07-19 09:58:36 +02:00
13693ef9e5 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 09:53:29 +02:00
3a54326e73 Update README.md 2023-07-18 12:42:49 -04:00
941c1c8a45 Update README.md 2023-07-18 12:35:27 -04:00
d328058075 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-18 17:40:29 +02:00
9108eb5616 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-18 12:37:53 +02:00
ea45044dc7 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-17 16:48:24 +02:00
6b16808207 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-17 11:01:10 +02:00
4071b285ce Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-15 12:34:10 +02:00
7bc80f0814 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-13 17:21:55 +02:00
6b57202421 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-13 11:40:58 +02:00
ca3edb6687 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 15:22:30 +02:00
7428f87899 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 14:47:53 +02:00
02481560a9 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 08:29:05 +02:00
2d66063763 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 21:19:27 +02:00
9158e24295 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 17:41:35 +02:00
118e177756 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 14:37:16 +02:00
88ac82a013 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-09 21:12:47 +02:00
c025013ca8 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-30 22:04:18 +02:00
115 changed files with 1247 additions and 732 deletions

View File

@ -66,6 +66,12 @@
public override string Title => "File Management";
protected override void OnParametersSet()
{
base.OnParametersSet();
base.SetModuleTitle(Localizer["ModuleTitle.Text"]);
}
protected override async Task OnInitializedAsync()
{
try

View File

@ -110,8 +110,13 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override string Title => "Folder Management";
public override string Title => "Folder Management";
protected override void OnParametersSet()
{
base.OnParametersSet();
base.SetModuleTitle(Localizer["ModuleTitle.Text"]);
}
protected override async Task OnInitializedAsync()
{
try

View File

@ -11,7 +11,7 @@
else
{
<ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" />
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">Refresh</button>
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">@Localizer["Refresh.Text"]</button>
<br />
<br />

View File

@ -16,7 +16,7 @@
else
{
<TabStrip>
<TabPanel Name="Manage" ResourceKey="Manage">
<TabPanel Name="Manage" ResourceKey="Manage" Heading="Manage">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
@ -45,7 +45,7 @@ else
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host" Heading="Upload">
<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." ResourceKey="LanguageUpload">Translation: </Label>

View File

@ -11,9 +11,6 @@
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<div>@Localizer["Info.SignedIn"]</div>
</Authorized>
<NotAuthorized>
@if (!twofactor)
{
@ -69,259 +66,265 @@
</AuthorizeView>
@code {
private bool _allowsitelogin = true;
private bool _allowexternallogin = false;
private ElementReference login;
private bool validated = false;
private bool twofactor = false;
private string _username = string.Empty;
private ElementReference username;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private bool _remember = false;
private string _code = string.Empty;
private bool _allowsitelogin = true;
private bool _allowexternallogin = false;
private ElementReference login;
private bool validated = false;
private bool twofactor = false;
private string _username = string.Empty;
private ElementReference username;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private bool _remember = false;
private string _code = string.Empty;
private string _returnUrl = string.Empty;
private string _returnUrl = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override List<Resource> Resources => new List<Resource>()
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
protected override async Task OnInitializedAsync()
{
try
{
_togglepassword = SharedLocalizer["ShowPassword"];
protected override async Task OnInitializedAsync()
{
try
{
_togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
{
_allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]);
}
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
{
_allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]);
}
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))
{
_allowexternallogin = true;
}
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))
{
_allowexternallogin = true;
}
if (PageState.QueryString.ContainsKey("returnurl"))
{
_returnUrl = PageState.QueryString["returnurl"];
}
if (PageState.QueryString.ContainsKey("returnurl"))
{
_returnUrl = PageState.QueryString["returnurl"];
}
if (PageState.QueryString.ContainsKey("name"))
{
_username = PageState.QueryString["name"];
}
if (PageState.QueryString.ContainsKey("name"))
{
_username = PageState.QueryString["name"];
}
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
{
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
{
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
if (PageState.QueryString.ContainsKey("key"))
{
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);
}
_username = "";
}
else
{
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);
}
}
}
else
{
if (PageState.QueryString.ContainsKey("status"))
{
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error);
}
}
if (PageState.QueryString.ContainsKey("key"))
{
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);
}
_username = "";
}
else
{
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);
}
}
}
else
{
if (PageState.QueryString.ContainsKey("status"))
{
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error);
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && PageState.User == null)
{
await username.FocusAsync();
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && PageState.User == null)
{
await username.FocusAsync();
}
private async Task Login()
{
try
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (!twofactor)
{
user = await UserService.LoginUserAsync(user, hybrid, _remember);
}
else
{
user = await UserService.VerifyTwoFactorAsync(user, _code);
}
// redirect logged in user to specified page
if (PageState.User != null)
{
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
}
if (user.IsAuthenticated)
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
private async Task Login()
{
try
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (hybrid)
{
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider
.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
}
else
{
// post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields);
}
}
else
{
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired)
{
twofactor = true;
validated = false;
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
}
else
{
if (!twofactor)
{
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
}
else
{
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
}
}
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Performing Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Login"], MessageType.Error);
}
}
if (!twofactor)
{
user = await UserService.LoginUserAsync(user, hybrid, _remember);
}
else
{
user = await UserService.VerifyTwoFactorAsync(user, _code);
}
private void Cancel()
{
NavigationManager.NavigateTo(_returnUrl);
}
if (user.IsAuthenticated)
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
private async Task Forgot()
{
try
{
if (_username != string.Empty)
{
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
}
if (hybrid)
{
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider
.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
}
else
{
// post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields);
}
}
else
{
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired)
{
twofactor = true;
validated = false;
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
}
else
{
if (!twofactor)
{
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
}
else
{
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
}
}
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Performing Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Login"], MessageType.Error);
}
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
}
}
private void Cancel()
{
NavigationManager.NavigateTo(_returnUrl);
}
private void Reset()
{
twofactor = false;
_username = "";
_password = "";
ClearModuleMessage();
StateHasChanged();
}
private async Task Forgot()
{
try
{
if (_username != string.Empty)
{
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
}
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await Login();
}
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
}
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
private void Reset()
{
twofactor = false;
_username = "";
_password = "";
ClearModuleMessage();
StateHasChanged();
}
private void ExternalLogin()
{
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await Login();
}
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
private void ExternalLogin()
{
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true);
}
}
}

View File

@ -10,65 +10,110 @@
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="text-center">
<div class="form-check form-check-inline">
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
</div>
<div class="form-check form-check-inline">
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<span class="input-group-text">@Localizer["Product"]</span>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</div>
</div>
</div>
@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>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
}
<div class="row mb-3">
<div class="col">
@if (_initialized)
{
<br />
<div class="row mb-3">
<div class="col-sm-4">
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
</div>
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-4">
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
<option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
<option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
@if (_price == "paid")
{
<option value="price">@SharedLocalizer["Search.Price"]</option>
}
</select>
</div>
</div>
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6">
<Row>
<div class="m-2 p-2 d-flex justify-content-center">
<div class="container-fluid px-0">
<div class="row g-0">
<div class="col-6">
@if (context.LogoFileId != null)
{
<img src="@GetLogo(context.LogoFileId.Value)" class="img-fluid" alt="@context.Name" />
}
else
{
<img src="/package.png" class="img-fluid" alt="@context.Name" />
}
</div>
<div class="col-6 text-end">
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
<br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<br /><small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
}
</div>
</div>
<div class="row g-0">
<div class="col">
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<small>@SharedLocalizer["Search.Price"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
@((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
}
<br />
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
</div>
</div>
</div>
</Row>
</Pager>
}
</div>
</div>
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
@ -116,8 +161,10 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private bool _initialized = false;
private List<Package> _packages;
private string _price = "free";
private string _sort = "popularity";
private string _search = "";
private string _productname = "";
private string _packageid = "";
@ -131,6 +178,7 @@
try
{
await LoadModuleDefinitions();
_initialized = true;
}
catch (Exception ex)
{
@ -141,8 +189,10 @@
private async Task LoadModuleDefinitions()
{
ShowProgressIndicator();
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "");
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
if (_packages != null)
{
@ -154,21 +204,22 @@
}
}
}
HideProgressIndicator();
}
private async void PriceChanged(ChangeEventArgs e)
private string GetLogo(int fileid)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadModuleDefinitions();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
var url = ImageUrl(fileid, 100, 100);
url = (!string.IsNullOrEmpty(PageState.Alias.Path)) ? url.Substring(PageState.Alias.Path.Length + 1) : url;
return Constants.PackageRegistryUrl + url;
}
private async void PriceChanged(string price)
{
_price = price;
await LoadModuleDefinitions();
StateHasChanged();
}
private async Task Search()
@ -196,6 +247,12 @@
}
}
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadModuleDefinitions();
}
private void HideModal()
{
_productname = "";

View File

@ -80,7 +80,7 @@ else
}
</td>
<td>
@((MarkupString)SupportLink(context.PackageName))
@((MarkupString)SupportLink(context.PackageName, context.Version))
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
@ -145,7 +145,7 @@ else
return link;
}
private string SupportLink(string packagename)
private string SupportLink(string packagename, string version)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -153,7 +153,7 @@ else
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{
link += "<a class=\"btn btn-success\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
}
}
return link;

View File

@ -22,6 +22,11 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Export Content";
protected override void OnParametersSet()
{
base.OnParametersSet();
base.SetModuleTitle(Localizer["ModuleTitle.Text"]);
}
private async Task ExportModule()
{

View File

@ -28,6 +28,11 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Import Content";
protected override void OnParametersSet()
{
base.OnParametersSet();
base.SetModuleTitle(Localizer["ModuleTitle.Text"]);
}
private async Task ImportModule()
{
validated = true;

View File

@ -130,6 +130,11 @@
private string modifiedby;
private DateTime modifiedon;
protected override void OnParametersSet()
{
base.OnParametersSet();
base.SetModuleTitle(Localizer["ModuleTitle.Text"]);
}
protected override void OnInitialized()
{
_module = ModuleState.ModuleDefinition.Name;

View File

@ -10,7 +10,7 @@
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings">
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
@ -126,7 +126,7 @@
</div>
</div>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
@ -158,7 +158,7 @@
</div>
</div>
</Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
@ -175,7 +175,7 @@
</div>
</Section>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading=@Localizer["Permissions.Heading"]>
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
@ -184,7 +184,7 @@
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}

View File

@ -14,7 +14,7 @@
@if (_page.UserId == null)
{
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]>
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
@ -137,7 +137,7 @@
</div>
</div>
</div>
<Section Name="Appearance" ResourceKey="Appearance">
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
@ -189,7 +189,7 @@
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" PermissionList="@_permissions" @ref="_permissionGrid" />
@ -224,7 +224,7 @@
else
{
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]>
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>

View File

@ -14,7 +14,7 @@
else
{
<TabStrip>
<TabPanel Name="Pages" ResourceKey="Pages">
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
@if (!_pages.Where(item => item.IsDeleted).Any())
{
<br />
@ -31,7 +31,7 @@ else
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td>@context.Name</td>
<td>@context.DeletedBy</td>
@ -42,7 +42,7 @@ else
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
}
</TabPanel>
<TabPanel Name="Modules" ResourceKey="Modules">
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
@if (!_modules.Where(item => item.IsDeleted).Any())
{
<br />

View File

@ -26,7 +26,7 @@
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label>
<div class="col-sm-9">
<select id="homepage" class="form-select" @bind="@_homepageid" required>
<option value="-">&lt;@Localizer["Not Specified"]&gt;</option>
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (Page page in PageState.Pages)
{
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
@ -60,7 +60,7 @@
</div>
</div>
<br />
<Section Name="Appearance" ResourceKey="Appearance">
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>

View File

@ -8,7 +8,7 @@
@if (_sites == null)
{
<p><em>Loading...</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{

View File

@ -10,61 +10,108 @@
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="text-center">
<div class="form-check form-check-inline">
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
</div>
<div class="form-check form-check-inline">
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<span class="input-group-text">Product</span>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col">
@if (_initialized)
{
<br />
<div class="row mb-3">
<div class="col-sm-4">
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
</div>
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-4">
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
<option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
<option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
@if (_price == "paid")
{
<option value="price">@SharedLocalizer["Search.Price"]</option>
}
</select>
</div>
</div>
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6">
<Row>
<div class="m-2 p-2 d-flex justify-content-center">
<div class="container-fluid px-0">
<div class="row g-0">
<div class="col-6">
@if (context.LogoFileId != null)
{
<img src="@GetLogo(context.LogoFileId.Value)" class="img-fluid" alt="@context.Name" />
}
else
{
<img src="/package.png" class="img-fluid" alt="@context.Name" />
}
</div>
<div class="col-6 text-end">
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
<br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<br />
@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;@SharedLocalizer["Search.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>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
}
<small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
}
</div>
</div>
<div class="row g-0">
<div class="col">
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<small>@SharedLocalizer["Search.Price"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
@((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
}
<br />
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
</div>
</div>
</div>
</Row>
</Pager>
}
</div>
</div>
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel>
@ -116,8 +163,10 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private bool _initialized = false;
private List<Package> _packages;
private string _price = "free";
private string _sort = "popularity";
private string _search = "";
private string _productname = "";
private string _license = "";
@ -131,6 +180,7 @@
try
{
await LoadThemes();
_initialized = true;
}
catch (Exception ex)
{
@ -141,8 +191,10 @@
private async Task LoadThemes()
{
ShowProgressIndicator();
var themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "");
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
if (_packages != null)
{
@ -154,21 +206,22 @@
}
}
}
HideProgressIndicator();
}
private async void PriceChanged(ChangeEventArgs e)
private string GetLogo(int fileid)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadThemes();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
var url = ImageUrl(fileid, 100, 100);
url = (!string.IsNullOrEmpty(PageState.Alias.Path)) ? url.Substring(PageState.Alias.Path.Length + 1) : url;
return Constants.PackageRegistryUrl + url;
}
private async void PriceChanged(string price)
{
_price = price;
await LoadThemes();
StateHasChanged();
}
private async Task Search()
@ -196,6 +249,12 @@
}
}
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadThemes();
}
private void HideModal()
{
_productname = "";

View File

@ -27,7 +27,7 @@
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information">
<Section Name="Information" ResourceKey="Information" Heading="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>

View File

@ -13,7 +13,7 @@
}
else
{
<ActionLink Action="Add" Text="Install Theme" />
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" />
@ -49,7 +49,7 @@ else
}
</td>
<td>
@((MarkupString)SupportLink(context.PackageName))
@((MarkupString)SupportLink(context.PackageName, context.Version))
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
@ -112,7 +112,7 @@ else
return link;
}
private string SupportLink(string packagename)
private string SupportLink(string packagename, string version)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -120,7 +120,7 @@ else
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{
link += "<a class=\"btn btn-success\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
}
}
return link;

View File

@ -17,11 +17,11 @@
}
else
{
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage>
<ModuleMessage Type="MessageType.Info" Message=@Localizer["Message.Text"]></ModuleMessage>
}
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<ModuleMessage Type="MessageType.Info" Message="Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade"></ModuleMessage>
<ModuleMessage Type="MessageType.Info" Message=@Localizer["MessgeUpgrade.Text"]></ModuleMessage>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>

View File

@ -42,6 +42,12 @@
public override string Title => "Send Notification";
protected override void OnParametersSet()
{
base.OnParametersSet();
base.SetModuleTitle(Localizer["ModuleTitle.Text"]);
}
private async Task Send()
{
try

View File

@ -110,6 +110,11 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override string Title => "View Notification";
protected override void OnParametersSet()
{
base.OnParametersSet();
base.SetModuleTitle(Localizer["ModuleTitle.Text"]);
}
protected override async Task OnInitializedAsync()
{
try

View File

@ -140,34 +140,26 @@
{
if (_password == confirm)
{
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user == null)
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
user = await UserService.AddUserAsync(user);
if (user != null)
{
user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
user = await UserService.AddUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Created {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", username, email);
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
}
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Created {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.Username.Exists"], MessageType.Warning);
await logger.LogError("Error Adding User {Username} {Email}", username, email);
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
}
}
else

View File

@ -2,7 +2,7 @@
@inherits LocalizableComponent
<div class="app-autocomplete">
<input class="form-control" value="@Value" @oninput="OnInput" @onkeyup="OnKeyUp" placeholder="@Placeholder" autocomplete="off" />
<input class="form-control" value="@Value" @oninput="OnInput" @onkeyup="OnKeyUp" placeholder="@Placeholder" autocomplete="off" @attributes="InputAttributes" />
@if (_results != null)
{
<select class="form-select" style="position: relative;" value="@Value" size="@Rows" @onkeyup="OnKeyUp" @onchange="(e => OnChange(e))">
@ -29,27 +29,48 @@
</div>
@code {
Dictionary<string, string> _results;
Dictionary<string, string> _results;
Dictionary<string, object> InputAttributes { get; set; } = new();
[Parameter]
public Func<string, Task<Dictionary<string, string>>> OnSearch { get; set; } // required - an async delegate method which accepts a filter string parameter and returns a dictionary
[Parameter]
public Func<string, Task<Dictionary<string, string>>> OnSearch { get; set; } // required - an async delegate method which accepts a filter string parameter and returns a dictionary
[Parameter]
public int Characters { get; set; } = 3; // optional - number of characters before search is initiated
[Parameter]
public int Characters { get; set; } = 3; // optional - number of characters before search is initiated
[Parameter]
public int Rows { get; set; } = 3; // optional - number of result rows to display
[Parameter]
public int Rows { get; set; } = 3; // optional - number of result rows to display
[Parameter]
public string Placeholder { get; set; } // optional - placeholder input text
[Parameter]
public string Placeholder { get; set; } // optional - placeholder input text
[Parameter]
public string Value { get; set; } // value of item selected
[Parameter]
public string Value { get; set; } // value of item selected
[Parameter]
public string Key { get; set; } // key of item selected
[Parameter]
public string Key { get; set; } // key of item selected
private async Task OnInput(ChangeEventArgs e)
[Parameter]
public bool Required { get; set; } // optional - if the item is required
protected override void OnParametersSet()
{
if (Required)
{
if (!InputAttributes.ContainsKey(nameof(Required)))
{
InputAttributes.Add(nameof(Required), true);
}
}
else
{
if (InputAttributes.ContainsKey(nameof(Required)))
{
InputAttributes.Remove(nameof(Required));
}
}
}
private async Task OnInput(ChangeEventArgs e)
{
Value = e.Value?.ToString();
if (Value?.Length >= Characters)

View File

@ -168,8 +168,6 @@
ShowSuccess = true;
}
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
{
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
@ -185,6 +183,22 @@
}
}
if (ShowFolders)
{
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
}
else
{
if (FolderId != -1)
{
var folder = await FolderService.GetFolderAsync(FolderId);
if (folder != null)
{
_folders = new List<Folder> { folder };
}
}
}
if (FileId != -1)
{
File file = await FileService.GetFileAsync(FileId);
@ -358,10 +372,21 @@
attempts += 1;
Thread.Sleep(1000 * attempts); // progressive retry
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[upload]);
if (file != null)
if (Folder == Constants.PackagesFolder)
{
success = true;
var files = await FileService.GetFilesAsync(folder);
if (files != null && files.Any(item => item.Name == uploads[upload]))
{
success = true;
}
}
else
{
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[upload]);
if (file != null)
{
success = true;
}
}
}
if (success)

View File

@ -42,6 +42,8 @@
protected override void OnParametersSet()
{
base.OnParametersSet(); // must be included to call method in LocalizableComponent
_heading = !string.IsNullOrEmpty(Heading) ? Localize(nameof(Heading), Heading) : Localize(nameof(Name), Name);
_expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded.ToLower() : "false";
if (_expanded == "true") { _show = "show"; }

View File

@ -68,6 +68,12 @@
private List<Models.HtmlText> _htmltexts;
private string _view = "";
protected override void OnParametersSet()
{
base.OnParametersSet();
base.SetModuleTitle(Localizer["ModuleTitle.Text"]);
}
protected override async Task OnInitializedAsync()
{
try

View File

@ -4,7 +4,7 @@
<TargetFramework>net7.0</TargetFramework>
<OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations>
<Version>4.0.1</Version>
<Version>4.0.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -12,7 +12,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/v4.0.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>

View File

@ -1,19 +1,23 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.Loader;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.UI;
@ -65,6 +69,14 @@ namespace Oqtane.Client
private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider)
{
// get alias
var navigationManager = serviceProvider.GetRequiredService<NavigationManager>();
var urlpath = GetUrlPath(navigationManager.Uri);
var json = await http.GetStringAsync($"api/Installation/installed/?path={WebUtility.UrlEncode(urlpath)}");
var installation = JsonSerializer.Deserialize<Installation>(json, new JsonSerializerOptions(JsonSerializerDefaults.Web));
urlpath = installation.Alias.Path;
urlpath = (!string.IsNullOrEmpty(urlpath)) ? urlpath + "/" : urlpath;
var dlls = new Dictionary<string, byte[]>();
var pdbs = new Dictionary<string, byte[]>();
var list = new List<string>();
@ -76,7 +88,7 @@ namespace Oqtane.Client
if (files.Count() != 0)
{
// get list of assemblies from server
var json = await http.GetStringAsync("/api/Installation/list");
json = await http.GetStringAsync($"{urlpath}api/Installation/list");
var assemblies = JsonSerializer.Deserialize<List<string>>(json);
// determine which assemblies need to be downloaded
@ -138,7 +150,7 @@ namespace Oqtane.Client
if (list.Count != 0)
{
// get assemblies from server and load into client app domain
var zip = await http.GetByteArrayAsync($"/api/Installation/load?list=" + string.Join(",", list));
var zip = await http.GetByteArrayAsync($"{urlpath}api/Installation/load?list=" + string.Join(",", list));
// asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
@ -254,5 +266,10 @@ namespace Oqtane.Client
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
}
private static string GetUrlPath(string url)
{
return new Uri(url).AbsolutePath.Substring(1);
}
}
}

View File

@ -162,4 +162,7 @@
<data name="Name.Text" xml:space="preserve">
<value>Name:</value>
</data>
<data name="DownloadFiles.Heading" xml:space="preserve">
<value>Download Files</value>
</data>
</root>

View File

@ -150,4 +150,7 @@
<data name="Description.Text" xml:space="preserve">
<value>Description:</value>
</data>
<data name="ModuleTitle.Text" xml:space="preserve">
<value>File Management</value>
</data>
</root>

View File

@ -183,4 +183,16 @@
<data name="ImageSizes.Text" xml:space="preserve">
<value>Image Sizes:</value>
</data>
<data name="FolderManagement.Title" xml:space="preserve">
<value>Folder Management!</value>
</data>
<data name="Private" xml:space="preserve">
<value>Private</value>
</data>
<data name="Public" xml:space="preserve">
<value>Public</value>
</data>
<data name="ModuleTitle.Text" xml:space="preserve">
<value>Folder Management</value>
</data>
</root>

View File

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

View File

@ -136,9 +136,12 @@
<value>No Modules Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Download</value>
<value>Marketplace</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
<data name="Product.Text" xml:space="preserve">
<value>Product</value>
</data>
</root>

View File

@ -144,6 +144,9 @@
<data name="DeleteModule.Header" xml:space="preserve">
<value>Delete Module</value>
</data>
<data name="DeleteModule.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="InUse" xml:space="preserve">
<value>In Use?</value>
</data>

View File

@ -132,4 +132,7 @@
<data name="Success.Content.Export" xml:space="preserve">
<value>Content Exported Successfully</value>
</data>
<data name="ModuleTitle.Text" xml:space="preserve">
<value>Export Content</value>
</data>
</root>

View File

@ -138,4 +138,7 @@
<data name="Message.Required.ImportContent" xml:space="preserve">
<value>You Must Enter Some Content To Import</value>
</data>
<data name="ModuleTitle.Text" xml:space="preserve">
<value>Import Content</value>
</data>
</root>

View File

@ -156,4 +156,7 @@
<data name="Module.Text" xml:space="preserve">
<value>Module:</value>
</data>
<data name="ModuleTitle.Text" xml:space="preserve">
<value>Module Settings</value>
</data>
</root>

View File

@ -249,4 +249,10 @@
<data name="ThemeChanged.Message" xml:space="preserve">
<value>Please Note That Overriding The Default Site Theme With An Unrelated Page Theme May Result In Compatibility Issues For Your Site</value>
</data>
<data name="Permissions.Heading" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="Theme.Heading" xml:space="preserve">
<value>Theme Settings</value>
</data>
</root>

View File

@ -120,9 +120,15 @@
<data name="DeleteModule.Header" xml:space="preserve">
<value>Delete Module</value>
</data>
<data name="DeleteModule.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="DeletePage.Header" xml:space="preserve">
<value>Delete Page</value>
</data>
<data name="DeletePage.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="NoPage.Deleted" xml:space="preserve">
<value>No Deleted Pages</value>
</data>

View File

@ -135,4 +135,10 @@
<data name="Search.NoResults" xml:space="preserve">
<value>No Themes Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Marketplace</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
</root>

View File

@ -141,6 +141,9 @@
<data name="CreateTheme.Text" xml:space="preserve">
<value>Create Theme</value>
</data>
<data name="InstallTheme.Text" xml:space="preserve">
<value>Install Theme</value>
</data>
<data name="ViewTheme.Text" xml:space="preserve">
<value>View</value>
</data>

View File

@ -141,4 +141,10 @@
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
<data name="Message.Text" xml:space="preserve">
<value>Framework Is Already Up To Date</value>
</data>
<data name="MessgeUpgrade.Text" xml:space="preserve">
<value>Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade</value>
</data>
</root>

View File

@ -141,4 +141,7 @@
<data name="Subject.Text" xml:space="preserve">
<value>Subject: </value>
</data>
<data name="ModuleTitle.Text" xml:space="preserve">
<value>Send Notification</value>
</data>
</root>

View File

@ -144,4 +144,7 @@
<data name="OriginalMessage" xml:space="preserve">
<value>Original Message</value>
</data>
<data name="ModuleTitle.Text" xml:space="preserve">
<value>View Notification</value>
</data>
</root>

View File

@ -156,6 +156,9 @@
<data name="Message.Content.Restored" xml:space="preserve">
<value>Version Restored</value>
</data>
<data name="ModuleTitle.Text" xml:space="preserve">
<value>Edit Html/Text</value>
</data>
<data name="Restore.Header" xml:space="preserve">
<value>Restore Version</value>
</data>

View File

@ -223,13 +223,13 @@
<value>by</value>
</data>
<data name="Search.Downloads" xml:space="preserve">
<value>downloads</value>
<value>Downloads</value>
</data>
<data name="Search.Released" xml:space="preserve">
<value>released</value>
<value>Released</value>
</data>
<data name="Search.Version" xml:space="preserve">
<value>version</value>
<value>Version</value>
</data>
<data name="Edit" xml:space="preserve">
<value>Edit</value>
@ -277,19 +277,19 @@
<value>Installed Version</value>
</data>
<data name="Search.Source" xml:space="preserve">
<value>source</value>
<value>Source</value>
</data>
<data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value>
</data>
<data name="Free" xml:space="preserve">
<value>Free</value>
<value>Open Source</value>
</data>
<data name="Paid" xml:space="preserve">
<value>Paid</value>
<value>Commercial</value>
</data>
<data name="Search.Price" xml:space="preserve">
<value>price</value>
<value>Price</value>
</data>
<data name="Accept" xml:space="preserve">
<value>Accept</value>
@ -390,4 +390,19 @@
<data name="Support" xml:space="preserve">
<value>Support</value>
</data>
<data name="Search.Alphabetical" xml:space="preserve">
<value>Alphabetical</value>
</data>
<data name="Buy" xml:space="preserve">
<value>Buy Now</value>
</data>
<data name="Search.Popularity" xml:space="preserve">
<value>Popularity</value>
</data>
<data name="Search.Results" xml:space="preserve">
<value>Results</value>
</data>
<data name="Search.RecentlyReleased" xml:space="preserve">
<value>Recently Released</value>
</data>
</root>

View File

@ -22,7 +22,7 @@ namespace Oqtane.Services
/// <inheritdoc />
public async Task<List<Alias>> GetAliasesAsync()
{
List<Alias> aliases = await GetJsonAsync<List<Alias>>(ApiUrl);
List<Alias> aliases = await GetJsonAsync<List<Alias>>(ApiUrl, Enumerable.Empty<Alias>().ToList());
return aliases.OrderBy(item => item.Name).ToList();
}

View File

@ -6,6 +6,7 @@ using Oqtane.Shared;
using Microsoft.AspNetCore.Components;
using System;
using System.Net;
using System.Linq;
namespace Oqtane.Services
{
@ -14,11 +15,13 @@ namespace Oqtane.Services
{
private readonly NavigationManager _navigationManager;
private readonly SiteState _siteState;
private readonly HttpClient _http;
public InstallationService(HttpClient http, SiteState siteState, NavigationManager navigationManager) : base(http, siteState)
{
_navigationManager = navigationManager;
_siteState = siteState;
_http = http;
}
private string ApiUrl => (_siteState.Alias == null)
@ -27,7 +30,15 @@ namespace Oqtane.Services
public async Task<Installation> IsInstalled()
{
var path = new Uri(_navigationManager.Uri).LocalPath.Substring(1);
var path = "";
if (_http.DefaultRequestHeaders.UserAgent.ToString().Contains(Constants.MauiUserAgent))
{
path = _http.DefaultRequestHeaders.GetValues(Constants.MauiAliasPath).First();
}
else
{
path = new Uri(_navigationManager.Uri).LocalPath.Substring(1);
}
return await GetJsonAsync<Installation>($"{ApiUrl}/installed/?path={WebUtility.UrlEncode(path)}");
}

View File

@ -14,6 +14,6 @@ namespace Oqtane.Services
/// </summary>
/// <param name="lastSyncDate"></param>
/// <returns></returns>
Task<Sync> GetSyncAsync(DateTime lastSyncDate);
Task<Sync> GetSyncEventsAsync(DateTime lastSyncDate);
}
}

View File

@ -145,10 +145,19 @@ namespace Oqtane.Services
{
return await response.Content.ReadFromJsonAsync<T>();
}
return default;
}
protected async Task<T> GetJsonAsync<T>(string uri, T defaultResult)
{
var response = await GetHttpClient().GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None);
if (await CheckResponse(response, uri) && ValidateJsonContent(response.Content))
{
return await response.Content.ReadFromJsonAsync<T>();
}
return defaultResult;
}
protected async Task PutAsync(string uri)
{
var response = await GetHttpClient().PutAsync(uri, null);
@ -202,17 +211,27 @@ namespace Oqtane.Services
private async Task<bool> CheckResponse(HttpResponseMessage response, string uri)
{
if (response.IsSuccessStatusCode && uri.Contains("/api/") && !response.RequestMessage.RequestUri.AbsolutePath.Contains("/api/"))
if (response.IsSuccessStatusCode)
{
await Log(uri, response.RequestMessage.Method.ToString(), response.StatusCode.ToString(), "Request {Uri} Not Mapped To An API Controller Method", uri);
// if response from api call is not from an api url then the route was not mapped correctly
if (uri.Contains("/api/") && !response.RequestMessage.RequestUri.AbsolutePath.Contains("/api/"))
{
await Log(uri, response.RequestMessage.Method.ToString(), response.StatusCode.ToString(), "Request {Uri} Not Mapped To An API Controller Method", uri);
return false;
}
else
{
return true;
}
}
else
{
if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound)
{
await Log(uri, response.RequestMessage.Method.ToString(), response.StatusCode.ToString(), "Request {Uri} Failed With Status {StatusCode} - {ReasonPhrase}", uri, response.StatusCode, response.ReasonPhrase);
}
return false;
}
if (response.IsSuccessStatusCode) return true;
if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound)
{
await Log(uri, response.RequestMessage.Method.ToString(), response.StatusCode.ToString(), "Request {Uri} Failed With Status {StatusCode} - {ReasonPhrase}", uri, response.StatusCode, response.ReasonPhrase);
}
return false;
}
private static bool ValidateJsonContent(HttpContent content)

View File

@ -16,7 +16,7 @@ namespace Oqtane.Services
private string ApiUrl => CreateApiUrl("Sync");
/// <inheritdoc />
public async Task<Sync> GetSyncAsync(DateTime lastSyncDate)
public async Task<Sync> GetSyncEventsAsync(DateTime lastSyncDate)
{
return await GetJsonAsync<Sync>($"{ApiUrl}/{lastSyncDate.ToString("yyyyMMddHHmmssfff")}");
}

View File

@ -2,6 +2,7 @@
@namespace Oqtane.Themes.Controls
@inherits ContainerBase
@attribute [OqtaneIgnore]
@inject IStringLocalizer<SharedResources> SharedLocalizer
<span class="app-moduletitle">
@((MarkupString)title)
@ -23,7 +24,7 @@
}
else
{
title = ModuleState.Title;
title = SharedLocalizer[ModuleState.Title];
}
}

View File

@ -93,12 +93,12 @@
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
private async Task Refresh()
{
Site site;
Page page;
Site site = null;
Page page = null;
User user = null;
var editmode = false;
var refresh = false;
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
var lastsyncdate = DateTime.MinValue;
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
_error = "";
@ -112,8 +112,8 @@
returnurl = WebUtility.UrlDecode(querystring["returnurl"]);
}
// reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias
if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
// reload the client application from the server if there is a forced reload
if (querystring.ContainsKey("reload"))
{
if (querystring.ContainsKey("reload") && querystring["reload"] == "post")
{
@ -126,7 +126,7 @@
}
else
{
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", "").Replace("&reload", ""), true);
return;
}
}
@ -163,31 +163,39 @@
else
{
user = PageState.User;
}
}
// process any sync events
var sync = await SyncService.GetSyncAsync(lastsyncdate);
var sync = await SyncService.GetSyncEventsAsync(lastsyncdate);
lastsyncdate = sync.SyncDate;
if (sync.SyncEvents.Any())
{
// reload client application if server was restarted or site runtime/rendermode was modified
if (PageState != null && sync.SyncEvents.Exists(item => (item.Action == SyncEventActions.Reload)))
// reload client application if server was restarted
if (sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Reload && item.EntityName == EntityNames.Host))
{
NavigationManager.NavigateTo(_absoluteUri, true);
return;
}
// when site information has changed the PageState needs to be refreshed
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
// reload client application if site runtime/rendermode was modified
if (sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Reload && item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{
refresh = true;
NavigationManager.NavigateTo(_absoluteUri, true);
return;
}
// when user information has changed the PageState needs to be refreshed as the list of pages/modules may have changed
if (user != null && sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
// reload client application if current user auth information has changed
if (user != null && sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Reload && item.EntityName == EntityNames.User && item.EntityId == user.UserId))
{
NavigationManager.NavigateTo(_absoluteUri, true);
return;
}
// refresh PageState when site information has changed
if (sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Refresh && item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{
refresh = true;
}
}
// get site
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
{
site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId);

View File

@ -70,7 +70,7 @@
var elements = (">" + content.Replace("\n", "") + "<").Split("><");
foreach (var element in elements)
{
if (!string.IsNullOrEmpty(element) && !element.Contains("script"))
if (!string.IsNullOrEmpty(element) && !element.ToLower().StartsWith("script"))
{
if (!headcontent.Contains("<" + element + ">"))
{

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>4.0.1</Version>
<Version>4.0.2</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/v4.0.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</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>4.0.1</version>
<version>4.0.2</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/v4.0.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>4.0.1</Version>
<Version>4.0.2</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/v4.0.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</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>4.0.1</version>
<version>4.0.2</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/v4.0.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>4.0.1</Version>
<Version>4.0.2</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/v4.0.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</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>4.0.1</version>
<version>4.0.2</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/v4.0.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>4.0.1</Version>
<Version>4.0.2</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/v4.0.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</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>4.0.1</version>
<version>4.0.2</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/v4.0.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -1,18 +1,52 @@
<DynamicComponent Type="@ComponentType" Parameters="@Parameters"></DynamicComponent>
@using System.Text.Json;
@using System.Text.Json.Nodes;
@code {
Type ComponentType = Type.GetType("Oqtane.App, Oqtane.Client");
private IDictionary<string, object> Parameters { get; set; }
protected override void OnInitialized()
{
Parameters = new Dictionary<string, object>();
Parameters.Add(new KeyValuePair<string, object>("AntiForgeryToken", ""));
Parameters.Add(new KeyValuePair<string, object>("Runtime", "Hybrid"));
Parameters.Add(new KeyValuePair<string, object>("RenderMode", "Hybrid"));
Parameters.Add(new KeyValuePair<string, object>("VisitorId", -1));
Parameters.Add(new KeyValuePair<string, object>("RemoteIPAddress", ""));
Parameters.Add(new KeyValuePair<string, object>("AuthorizationToken", ""));
}
@if (string.IsNullOrEmpty(message))
{
<DynamicComponent Type="@ComponentType" Parameters="@Parameters"></DynamicComponent>
}
else
{
<br /><br /><center>@message</center>
}
@code {
Type ComponentType = Type.GetType("Oqtane.App, Oqtane.Client");
private IDictionary<string, object> Parameters { get; set; }
private string message = "";
protected override void OnInitialized()
{
Parameters = new Dictionary<string, object>();
Parameters.Add(new KeyValuePair<string, object>("AntiForgeryToken", ""));
Parameters.Add(new KeyValuePair<string, object>("Runtime", "Hybrid"));
Parameters.Add(new KeyValuePair<string, object>("RenderMode", "Hybrid"));
Parameters.Add(new KeyValuePair<string, object>("VisitorId", -1));
Parameters.Add(new KeyValuePair<string, object>("RemoteIPAddress", ""));
Parameters.Add(new KeyValuePair<string, object>("AuthorizationToken", ""));
if (MauiConstants.UseAppSettings)
{
string file = Path.Combine(FileSystem.Current.AppDataDirectory, "appsettings.json");
if (File.Exists(file))
{
using FileStream stream = File.OpenRead(file);
using StreamReader reader = new StreamReader(stream);
var content = reader.ReadToEnd();
var obj = JsonSerializer.Deserialize<JsonObject>(content)!;
if (string.IsNullOrEmpty((string)obj["Url"]) && string.IsNullOrEmpty(MauiConstants.ApiUrl))
{
message = "You Must Set The Url In Either MauiConstants.cs Or " + file;
}
}
}
else
{
if (string.IsNullOrEmpty(MauiConstants.ApiUrl))
{
message = "You Must Set The Url In MauiConstants.cs";
}
}
}
}

View File

@ -0,0 +1,13 @@
namespace Oqtane.Maui;
public static class MauiConstants
{
// the API service url (used as fallback if not set in appsettings.json)
public static string ApiUrl = "";
//public static string ApiUrl = "http://localhost:44357/"; // for local development (Oqtane.Server must be already running for MAUI client to connect)
//public static string apiurl = "http://localhost:44357/sitename/"; // local microsite example
//public static string apiurl = "https://www.dnfprojects.com/"; // for testing remote site
// specify if you wish to allow users to override the url via appsettings.json in the AppDataDirectory
public static bool UseAppSettings = true;
}

View File

@ -6,20 +6,16 @@ using Oqtane.Modules;
using Oqtane.Services;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace Oqtane.Maui;
public static class MauiProgram
{
// the API service url
//static string apiurl = "https://www.dnfprojects.com"; // for testing
static string apiurl = "http://localhost:44357"; // for local development (Oqtane.Server must be already running for MAUI client to connect)
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
builder.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
@ -28,15 +24,21 @@ public static class MauiProgram
builder.Services.AddMauiBlazorWebView();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
#endif
#endif
var httpClient = new HttpClient { BaseAddress = new Uri(apiurl) };
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Shared.Constants.MauiUserAgent);
builder.Services.AddSingleton(httpClient);
builder.Services.AddHttpClient(); // IHttpClientFactory for calling remote services via RemoteServiceBase
var apiurl = LoadAppSettings();
// dynamically load client assemblies
LoadClientAssemblies(httpClient);
if (!string.IsNullOrEmpty(apiurl))
{
var httpClient = new HttpClient { BaseAddress = new Uri(GetBaseUrl(apiurl)) };
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Shared.Constants.MauiUserAgent);
httpClient.DefaultRequestHeaders.Add(Shared.Constants.MauiAliasPath, GetUrlPath(apiurl).Replace("/", ""));
builder.Services.AddSingleton(httpClient);
builder.Services.AddHttpClient(); // IHttpClientFactory for calling remote services via RemoteServiceBase
// dynamically load client assemblies
LoadClientAssemblies(httpClient, apiurl);
}
// register localization services
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
@ -60,7 +62,37 @@ public static class MauiProgram
return builder.Build();
}
private static void LoadClientAssemblies(HttpClient http)
private static string LoadAppSettings()
{
var url = MauiConstants.ApiUrl;
if (MauiConstants.UseAppSettings)
{
string file = Path.Combine(FileSystem.Current.AppDataDirectory, "appsettings.json");
if (File.Exists(file))
{
using FileStream stream = File.OpenRead(file);
using StreamReader reader = new StreamReader(stream);
var content = reader.ReadToEnd();
var obj = JsonSerializer.Deserialize<JsonObject>(content)!;
if (!string.IsNullOrEmpty((string)obj["Url"]))
{
url = (string)obj["Url"];
}
}
else
{
// create template appsettings.json file
using (StreamWriter writer = File.CreateText(file))
{
writer.WriteLine("{ \"Url\": \"\" }");
}
}
}
return url;
}
private static void LoadClientAssemblies(HttpClient http, string apiurl)
{
try
{
@ -84,7 +116,7 @@ public static class MauiProgram
if (files.Count() != 0)
{
// get list of assemblies from server
var json = Task.Run(() => http.GetStringAsync("/api/Installation/list")).GetAwaiter().GetResult();
var json = Task.Run(() => http.GetStringAsync($"{GetUrlPath(apiurl)}api/Installation/list")).GetAwaiter().GetResult();
var assemblies = JsonSerializer.Deserialize<List<string>>(json);
// determine which assemblies need to be downloaded
@ -148,7 +180,7 @@ public static class MauiProgram
if (list.Count != 0)
{
// get assemblies from server
var zip = Task.Run(() => http.GetByteArrayAsync("/api/Installation/load?list=" + string.Join(",", list))).GetAwaiter().GetResult();
var zip = Task.Run(() => http.GetByteArrayAsync($"{GetUrlPath(apiurl)}api/Installation/load?list=" + string.Join(",", list))).GetAwaiter().GetResult();
// asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
@ -199,7 +231,7 @@ public static class MauiProgram
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Loading Client Assemblies {ex}");
Debug.WriteLine($"Error Loading Client Assemblies From {apiurl} - {ex}");
}
}
@ -245,4 +277,17 @@ public static class MauiProgram
// could not interrogate assembly - likely missing dependencies
}
}
private static string GetBaseUrl(string url)
{
var uri = new Uri(url);
return uri.Scheme + "://"+ uri.Authority + "/";
}
private static string GetUrlPath(string url)
{
var path = new Uri(url).AbsolutePath.Substring(1);
path = (!string.IsNullOrEmpty(path) && !path.EndsWith("/")) ? path + "/" : path;
return path;
}
}

View File

@ -6,7 +6,7 @@
<!-- <TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType>
<Version>4.0.1</Version>
<Version>4.0.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -14,7 +14,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/v4.0.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions -->
<ApplicationDisplayVersion>4.0.1</ApplicationDisplayVersion>
<ApplicationDisplayVersion>4.0.2</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
@ -44,7 +44,7 @@
<ItemGroup>
<!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
<MauiIcon Include="Resources\AppIcon\appicon.svg" />
<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />

View File

@ -1,4 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
</svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M994 1922 c-6 -4 -25 -50 -44 -102 -80 -218 -210 -455 -391 -712 -49
-70 -100 -151 -114 -180 -105 -226 -53 -507 126 -686 190 -188 468 -234 709
-116 104 51 223 170 274 274 84 172 86 367 7 532 -16 35 -72 122 -123 193 -52
72 -111 161 -133 198 -21 37 -42 67 -46 67 -3 0 -46 -61 -94 -136 l-88 -136
106 -156 c57 -87 110 -172 116 -191 16 -50 13 -150 -6 -207 -23 -69 -110 -155
-182 -179 -68 -22 -144 -22 -212 0 -74 25 -159 110 -183 183 -19 57 -22 148
-7 200 6 19 116 193 246 387 129 195 235 358 235 363 0 5 -22 56 -49 113 -27
57 -64 145 -81 196 -31 88 -46 109 -66 95z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 1008 B

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Client</id>
<version>4.0.1</version>
<version>4.0.2</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/v4.0.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</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>4.0.1</version>
<version>4.0.2</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/v4.0.1/Oqtane.Framework.4.0.1.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.1</releaseNotes>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v4.0.2/Oqtane.Framework.4.0.2.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</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>4.0.1</version>
<version>4.0.2</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/v4.0.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</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>4.0.1</version>
<version>4.0.2</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/v4.0.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</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>4.0.1</version>
<version>4.0.2</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/v4.0.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.1.Install.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.2.Install.zip" -Force

View File

@ -14,7 +14,7 @@ dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
del /F/Q/S "..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Content" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Content"
setlocal ENABLEDELAYEDEXPANSION
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText,Templates
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText
for /D %%i in ("..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Modules\*") do (
set /A found=0
for %%j in (%retain%) do (
@ -22,7 +22,7 @@ if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme,Templates
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme
for /D %%i in ("..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Themes\*") do (
set /A found=0
for %%j in (%retain%) do (

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.1.Upgrade.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.2.Upgrade.zip" -Force

View File

@ -572,7 +572,7 @@ namespace Oqtane.Controllers
// validation
if (!Enum.TryParse(mode, true, out ResizeMode _)) mode = "crop";
if (!Enum.TryParse(position, true, out AnchorPositionMode _)) position = "center";
if (!Color.TryParseHex("#" + background, out _)) background = "000000";
if (!Color.TryParseHex("#" + background, out _)) background = "transparent";
if (!int.TryParse(rotate, out _)) rotate = "0";
rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate;
if (!bool.TryParse(recreate, out _)) recreate = "false";
@ -644,10 +644,23 @@ namespace Oqtane.Controllers
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height)
})
.BackgroundColor(Color.ParseHex("#" + background)));
}));
image.Save(imagepath, new PngEncoder());
if (background != "transparent")
{
image.Mutate(x => x
.BackgroundColor(Color.ParseHex("#" + background)));
}
PngEncoder encoder = new PngEncoder
{
ColorType = PngColorType.RgbWithAlpha,
TransparentColorMode = PngTransparentColorMode.Preserve,
BitDepth = PngBitDepth.Bit8,
CompressionLevel = PngCompressionLevel.BestSpeed
};
image.Save(imagepath, encoder);
}
}
}
@ -677,7 +690,14 @@ namespace Oqtane.Controllers
path = Utilities.PathCombine(path, folder, Path.DirectorySeparatorChar.ToString());
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
try
{
Directory.CreateDirectory(path);
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Unable To Create Folder {Folder}", path);
}
}
}
}

View File

@ -43,7 +43,8 @@ namespace Oqtane.Controllers
{
foreach (Folder folder in _folders.GetFolders(SiteId))
{
if (_userPermissions.IsAuthorized(User, PermissionNames.View, folder.PermissionList))
// note that Browse permission is used for this method
if (_userPermissions.IsAuthorized(User, PermissionNames.Browse, folder.PermissionList))
{
folders.Add(folder);
}
@ -87,6 +88,7 @@ namespace Oqtane.Controllers
public Folder GetByPath(int siteId, string path)
{
var folderPath = WebUtility.UrlDecode(path).Replace("\\", "/");
folderPath = (folderPath == "/") ? "" : folderPath;
if (!folderPath.EndsWith("/") && folderPath != "")
{
folderPath += "/";

View File

@ -34,9 +34,9 @@ namespace Oqtane.Controllers
private readonly IAliasRepository _aliases;
private readonly ILogger<InstallationController> _filelogger;
private readonly ITenantManager _tenantManager;
private readonly ServerStateManager _serverState;
private readonly IServerStateManager _serverState;
public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger<InstallationController> filelogger, ITenantManager tenantManager, ServerStateManager serverState)
public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger<InstallationController> filelogger, ITenantManager tenantManager, IServerStateManager serverState)
{
_configManager = configManager;
_installationManager = installationManager;
@ -119,9 +119,9 @@ namespace Oqtane.Controllers
private List<ClientAssembly> GetAssemblyList()
{
int siteId = _tenantManager.GetAlias().SiteId;
var siteKey = _tenantManager.GetAlias().SiteKey;
return _cache.GetOrCreate($"assemblieslist:{siteId}", entry =>
return _cache.GetOrCreate($"assemblieslist:{siteKey}", entry =>
{
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var assemblyList = new List<ClientAssembly>();
@ -134,7 +134,7 @@ namespace Oqtane.Controllers
}
// get site assemblies which should be downloaded to client
var assemblies = _serverState.GetServerState(siteId).Assemblies;
var assemblies = _serverState.GetServerState(siteKey).Assemblies;
// populate assembly list
foreach (var assembly in assemblies)
@ -179,9 +179,11 @@ namespace Oqtane.Controllers
private byte[] GetAssemblies(string list)
{
var siteKey = _tenantManager.GetAlias().SiteKey;
if (list == "*")
{
return _cache.GetOrCreate("assemblies", entry =>
return _cache.GetOrCreate($"assemblies:{siteKey}", entry =>
{
return GetZIP(list);
});

View File

@ -283,23 +283,26 @@ namespace Oqtane.Controllers
var templates = new List<Template>();
var root = Directory.GetParent(_environment.ContentRootPath);
string templatePath = Utilities.PathCombine(_environment.WebRootPath, "Modules", "Templates", Path.DirectorySeparatorChar.ToString());
foreach (string directory in Directory.GetDirectories(templatePath))
if (Directory.Exists(templatePath))
{
string name = directory.Replace(templatePath, "");
if (System.IO.File.Exists(Path.Combine(directory, "template.json")))
foreach (string directory in Directory.GetDirectories(templatePath))
{
var template = JsonSerializer.Deserialize<Template>(System.IO.File.ReadAllText(Path.Combine(directory, "template.json")));
template.Name = name;
template.Location = "";
if (template.Type.ToLower() != "internal")
string name = directory.Replace(templatePath, "");
if (System.IO.File.Exists(Path.Combine(directory, "template.json")))
{
template.Location = Utilities.PathCombine(root.Parent.ToString(), Path.DirectorySeparatorChar.ToString());
var template = JsonSerializer.Deserialize<Template>(System.IO.File.ReadAllText(Path.Combine(directory, "template.json")));
template.Name = name;
template.Location = "";
if (template.Type.ToLower() != "internal")
{
template.Location = Utilities.PathCombine(root.Parent.ToString(), Path.DirectorySeparatorChar.ToString());
}
templates.Add(template);
}
else
{
templates.Add(new Template { Name = name, Title = name, Type = "External", Version = "", Location = Utilities.PathCombine(root.Parent.ToString(), Path.DirectorySeparatorChar.ToString()) });
}
templates.Add(template);
}
else
{
templates.Add(new Template { Name = name, Title = name, Type = "External", Version = "", Location = Utilities.PathCombine(root.Parent.ToString(), Path.DirectorySeparatorChar.ToString()) });
}
}
return templates;

View File

@ -23,10 +23,16 @@ namespace Oqtane.Controllers
[HttpGet("{lastSyncDate}")]
public Sync Get(string lastSyncDate)
{
DateTime currentdate = DateTime.UtcNow;
DateTime lastdate = DateTime.ParseExact(lastSyncDate, "yyyyMMddHHmmssfff", CultureInfo.InvariantCulture);
if (lastdate == DateTime.MinValue)
{
lastdate = currentdate;
}
Sync sync = new Sync
{
SyncDate = DateTime.UtcNow,
SyncEvents = _syncManager.GetSyncEvents(_alias.TenantId, DateTime.ParseExact(lastSyncDate, "yyyyMMddHHmmssfff", CultureInfo.InvariantCulture))
SyncDate = currentdate,
SyncEvents = _syncManager.GetSyncEvents(_alias.TenantId, lastdate)
};
return sync;
}

View File

@ -137,23 +137,26 @@ namespace Oqtane.Controllers
var templates = new List<Template>();
var root = Directory.GetParent(_environment.ContentRootPath);
string templatePath = Utilities.PathCombine(_environment.WebRootPath, "Themes", "Templates", Path.DirectorySeparatorChar.ToString());
foreach (string directory in Directory.GetDirectories(templatePath))
if (Directory.Exists(templatePath))
{
string name = directory.Replace(templatePath, "");
if (System.IO.File.Exists(Path.Combine(directory, "template.json")))
foreach (string directory in Directory.GetDirectories(templatePath))
{
var template = JsonSerializer.Deserialize<Template>(System.IO.File.ReadAllText(Path.Combine(directory, "template.json")));
template.Name = name;
template.Location = "";
if (template.Type.ToLower() != "internal")
string name = directory.Replace(templatePath, "");
if (System.IO.File.Exists(Path.Combine(directory, "template.json")))
{
template.Location = Utilities.PathCombine(root.Parent.ToString(), Path.DirectorySeparatorChar.ToString());
var template = JsonSerializer.Deserialize<Template>(System.IO.File.ReadAllText(Path.Combine(directory, "template.json")));
template.Name = name;
template.Location = "";
if (template.Type.ToLower() != "internal")
{
template.Location = Utilities.PathCombine(root.Parent.ToString(), Path.DirectorySeparatorChar.ToString());
}
templates.Add(template);
}
else
{
templates.Add(new Template { Name = name, Title = name, Type = "External", Version = "", Location = Utilities.PathCombine(root.Parent.ToString(), Path.DirectorySeparatorChar.ToString()) });
}
templates.Add(template);
}
else
{
templates.Add(new Template { Name = name, Title = name, Type = "External", Version = "", Location = Utilities.PathCombine(root.Parent.ToString(), Path.DirectorySeparatorChar.ToString()) });
}
}
return templates;

View File

@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Oqtane.Models;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;
using System.Linq;
using System.Security.Claims;
@ -22,23 +21,17 @@ namespace Oqtane.Controllers
public class UserController : Controller
{
private readonly IUserRepository _users;
private readonly UserManager<IdentityUser> _identityUserManager;
private readonly SignInManager<IdentityUser> _identitySignInManager;
private readonly ITenantManager _tenantManager;
private readonly INotificationRepository _notifications;
private readonly IUserManager _userManager;
private readonly ISiteRepository _sites;
private readonly IUserPermissions _userPermissions;
private readonly IJwtManager _jwtManager;
private readonly ILogManager _logger;
public UserController(IUserRepository users, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, ILogManager logger)
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, ILogManager logger)
{
_users = users;
_identityUserManager = identityUserManager;
_identitySignInManager = identitySignInManager;
_tenantManager = tenantManager;
_notifications = notifications;
_userManager = userManager;
_sites = sites;
_userPermissions = userPermissions;

View File

@ -131,9 +131,8 @@ namespace Oqtane.Controllers
{
userRole = _userRoles.AddUserRole(userRole);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userRole.UserRoleId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId, SyncEventActions.Reload);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "User Role Added {UserRole}", userRole);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId, SyncEventActions.Refresh);
}
else
{
@ -154,7 +153,7 @@ namespace Oqtane.Controllers
{
userRole = _userRoles.UpdateUserRole(userRole);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userRole.UserRoleId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId, SyncEventActions.Refresh);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId, SyncEventActions.Reload);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Role Updated {UserRole}", userRole);
}
else
@ -171,25 +170,24 @@ namespace Oqtane.Controllers
[Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public void Delete(int id)
{
UserRole userrole = _userRoles.GetUserRole(id);
if (userrole != null && SiteValid(userrole.Role.SiteId) && RoleValid(userrole.Role.Name))
UserRole userRole = _userRoles.GetUserRole(id);
if (userRole != null && SiteValid(userRole.Role.SiteId) && RoleValid(userRole.Role.Name))
{
_userRoles.DeleteUserRole(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userrole.UserRoleId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Role Deleted {UserRole}", userrole);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userRole.UserRoleId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId, SyncEventActions.Reload);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Role Deleted {UserRole}", userRole);
if (userrole.Role.Name == RoleNames.Host)
if (userRole.Role.Name == RoleNames.Host)
{
// add site specific user roles to preserve user access
var role = _roles.GetRoles(_alias.SiteId).FirstOrDefault(item => item.Name == RoleNames.Registered);
userrole = _userRoles.AddUserRole(new UserRole { UserId = userrole.UserId, RoleId = role.RoleId, EffectiveDate = null, ExpiryDate = null });
_logger.Log(LogLevel.Information, this, LogFunction.Create, "User Role Added {UserRole}", userrole);
userRole = _userRoles.AddUserRole(new UserRole { UserId = userRole.UserId, RoleId = role.RoleId, EffectiveDate = null, ExpiryDate = null });
_logger.Log(LogLevel.Information, this, LogFunction.Create, "User Role Added {UserRole}", userRole);
role = _roles.GetRoles(_alias.SiteId).FirstOrDefault(item => item.Name == RoleNames.Admin);
userrole = _userRoles.AddUserRole(new UserRole { UserId = userrole.UserId, RoleId = role.RoleId, EffectiveDate = null, ExpiryDate = null });
_logger.Log(LogLevel.Information, this, LogFunction.Create, "User Role Added {UserRole}", userrole);
userRole = _userRoles.AddUserRole(new UserRole { UserId = userRole.UserId, RoleId = role.RoleId, EffectiveDate = null, ExpiryDate = null });
_logger.Log(LogLevel.Information, this, LogFunction.Create, "User Role Added {UserRole}", userRole);
}
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userrole.UserId, SyncEventActions.Refresh);
}
else
{

View File

@ -62,7 +62,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
services.AddSingleton<AutoValidateAntiforgeryTokenFilter>();
services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
services.AddSingleton<ServerStateManager>();
services.AddSingleton<IServerStateManager, ServerStateManager>();
return services;
}

View File

@ -0,0 +1,7 @@
namespace Oqtane.Infrastructure
{
public interface IServerStateManager
{
ServerState GetServerState(string siteKey);
}
}

View File

@ -40,9 +40,13 @@ namespace Oqtane.Infrastructure
public string[] GetInstalledCultures()
{
var cultures = new List<string>();
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{Constants.ClientId}{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
{
cultures.Add(Path.GetFileName(Path.GetDirectoryName(file)));
var culture = Path.GetFileName(Path.GetDirectoryName(file));
if (!cultures.Contains(culture))
{
cultures.Add(culture);
}
}
return cultures.OrderBy(c => c).ToArray();
}

View File

@ -37,20 +37,46 @@ namespace Oqtane.Infrastructure
var identity = jwtManager.ValidateToken(token, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""));
if (identity != null && identity.Claims.Any())
{
// create user identity using jwt claims (note the difference in claimtype names)
var user = new User
var idclaim = "nameid";
var nameclaim = "unique_name";
var legacynameclaim = "name"; // this was a breaking change in System.IdentityModel.Tokens.Jwt in .NET 7
// get jwt claims for userid and username
var userid = identity.Claims.FirstOrDefault(item => item.Type == idclaim)?.Value;
if (userid != null)
{
UserId = int.Parse(identity.Claims.FirstOrDefault(item => item.Type == "nameid")?.Value),
Username = identity.Claims.FirstOrDefault(item => item.Type == "name")?.Value
};
// jwt already contains the roles - we are reloading to ensure most accurate permissions
var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
if (!int.TryParse(userid, out _))
{
userid = null;
}
}
var username = identity.Claims.FirstOrDefault(item => item.Type == nameclaim)?.Value;
if (username == null)
{
// fallback for legacy clients
username = identity.Claims.FirstOrDefault(item => item.Type == legacynameclaim)?.Value;
}
// set claims identity
var claimsidentity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList());
context.User = new ClaimsPrincipal(claimsidentity);
if (userid != null && username != null)
{
// create user identity
var user = new User
{
UserId = int.Parse(userid),
Username = username
};
logger.Log(alias.SiteId, LogLevel.Information, "TokenValidation", Enums.LogFunction.Security, "Token Validated For User {Username}", user.Username);
// set claims identity (note jwt already contains the roles - we are reloading to ensure most accurate permissions)
var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
var claimsidentity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList());
context.User = new ClaimsPrincipal(claimsidentity);
logger.Log(alias.SiteId, LogLevel.Information, "TokenValidation", Enums.LogFunction.Security, "Token Validated For UserId {UserId} And Username {Username}", user.UserId, user.Username);
}
else
{
logger.Log(alias.SiteId, LogLevel.Error, "TokenValidation", Enums.LogFunction.Security, "Token Validated But Could Not Locate UserId Or Username In Claims {Claims}", identity.Claims.ToString());
}
}
else
{

View File

@ -22,6 +22,7 @@ namespace Oqtane.Infrastructure
var config = context.RequestServices.GetService(typeof(IConfigManager)) as IConfigManager;
string path = context.Request.Path.ToString();
if (config.IsInstalled() && !path.StartsWith("/_blazor"))
{
// get alias (note that this also sets SiteState.Alias)
@ -43,6 +44,14 @@ namespace Oqtane.Infrastructure
});
context.Items.Add(Constants.HttpContextSiteSettingsKey, sitesettings);
// handle first request to site
var serverState = context.RequestServices.GetService(typeof(IServerStateManager)) as IServerStateManager;
if (!serverState.GetServerState(alias.SiteKey).IsInitialized)
{
var sites = context.RequestServices.GetService(typeof(ISiteRepository)) as ISiteRepository;
sites.InitializeSite(alias);
}
// rewrite path by removing alias path prefix from reserved route (api,pages,files) requests for consistent routes
if (!string.IsNullOrEmpty(alias.Path))
{

View File

@ -5,9 +5,9 @@ namespace Oqtane.Infrastructure
{
public class ServerState
{
public int SiteId { get; set; }
public string SiteKey { get; set; }
public List<string> Assemblies { get; set; } = new List<string>();
public List<Resource>Scripts { get; set; } = new List<Resource>();
public bool IsMigrated { get; set; } = false;
public bool IsInitialized { get; set; } = false;
}
}

View File

@ -5,7 +5,7 @@ using Oqtane.Models;
namespace Oqtane.Infrastructure
{
// singleton
public class ServerStateManager
public class ServerStateManager : IServerStateManager
{
private List<ServerState> _serverStates { get; set; }
@ -14,36 +14,19 @@ namespace Oqtane.Infrastructure
_serverStates = new List<ServerState>();
}
public ServerState GetServerState(int siteId)
public ServerState GetServerState(string siteKey)
{
var serverState = _serverStates.FirstOrDefault(item => item.SiteId == siteId);
var serverState = _serverStates.FirstOrDefault(item => item.SiteKey == siteKey);
if (serverState == null)
{
serverState = new ServerState();
serverState.SiteId = siteId;
serverState.SiteKey = siteKey;
serverState.Assemblies = new List<string>();
serverState.Scripts = new List<Resource>();
return serverState;
}
else
{
return serverState;
}
}
public void SetServerState(int siteId, ServerState serverState)
{
var serverstate = _serverStates.FirstOrDefault(item => item.SiteId == siteId);
if (serverstate == null)
{
serverState.SiteId = siteId;
serverState.IsInitialized = false;
_serverStates.Add(serverState);
}
else
{
serverstate.Assemblies = serverState.Assemblies;
serverstate.Scripts = serverState.Scripts;
}
return serverState;
}
}
}

View File

@ -133,35 +133,6 @@ namespace Oqtane.SiteTemplates
}
}
});
_pageTemplates.Add(new PageTemplate
{
Name = "Develop",
Parent = "",
Order = 7,
Path = "develop",
Icon = "oi oi-wrench",
IsNavigation = true,
IsPersonalizable = false,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Software Development", Pane = PaneNames.Default,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
Content = "<p>Oqtane offers a Module Creator which allows you to create new modules to extend the framework with additional capabilities. Simply provide some basic information and the system will scaffold a completely functional module which includes all of the necessary code files and assets to get you up and running as quickly as possible.</p>"
},
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.ModuleCreator, Oqtane.Client", Title = "Module Creator", Pane = PaneNames.Default,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
}
}
}
});
if (System.IO.File.Exists(Path.Combine(_environment.WebRootPath, "images", "logo-white.png")))
{

View File

@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Oqtane.Models;
@ -57,7 +58,7 @@ namespace Oqtane.Infrastructure
alias.BaseUrl = "";
if (httpcontext.Request.Headers.ContainsKey("User-Agent") && httpcontext.Request.Headers["User-Agent"] == Shared.Constants.MauiUserAgent)
{
alias.BaseUrl = alias.Protocol + alias.Name;
alias.BaseUrl = alias.Protocol + alias.Name.Replace("/" + alias.Path, "");
}
_siteState.Alias = alias;
}

View File

@ -231,7 +231,7 @@ namespace Oqtane.Infrastructure
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = "<p>The page you requested does not exist.</p>"
Content = "<p>The page you requested does not exist or you do not have sufficient rights to view it.</p>"
}
}
});

View File

@ -130,14 +130,14 @@ namespace Oqtane.Managers
if (!user.EmailConfirmed)
{
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = alias.Protocol + "://" + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, User, "User Account Verification", body);
_notifications.AddNotification(notification);
}
else
{
string url = alias.Protocol + "://" + alias.Name;
string url = alias.Protocol + alias.Name;
string body = "Dear " + user.DisplayName + ",\n\nA User Account Has Been Successfully Created For You. Please Use The Following Link To Access The Site:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, User, "User Account Notification", body);
_notifications.AddNotification(notification);
@ -178,7 +178,7 @@ namespace Oqtane.Managers
user = _users.UpdateUser(user);
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Refresh);
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Reload);
user.Password = ""; // remove sensitive information
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
}
@ -228,6 +228,7 @@ namespace Oqtane.Managers
// delete user
_users.DeleteUser(userid);
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, userid, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, userid, SyncEventActions.Reload);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Deleted {UserId}", userid, result.ToString());
}
else
@ -299,7 +300,7 @@ namespace Oqtane.Managers
var alias = _tenantManager.GetAlias();
user = _users.GetUser(user.Username);
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
string url = alias.Protocol + "://" + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nYou attempted multiple times unsuccessfully to log in to your account and it is now locked out. Please wait a few minutes and then try again... or use the link below to reset your password:\n\n" + url +
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
"\n\nThank You!";
@ -348,7 +349,7 @@ namespace Oqtane.Managers
var alias = _tenantManager.GetAlias();
user = _users.GetUser(user.Username);
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
string url = alias.Protocol + "://" + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nYou recently requested to reset your password. Please use the link below to complete the process:\n\n" + url +
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
"\n\nIf you did not request to reset your password you can safely ignore this message." +

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>4.0.1</Version>
<Version>4.0.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,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/v4.0.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>

View File

@ -3,6 +3,9 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Managers;
using Oqtane.Shared;
namespace Oqtane.Pages
@ -10,9 +13,28 @@ namespace Oqtane.Pages
[Authorize]
public class LogoutModel : PageModel
{
private readonly IUserManager _userManager;
private readonly ISyncManager _syncManager;
public LogoutModel(IUserManager userManager, ISyncManager syncManager)
{
_userManager = userManager;
_syncManager = syncManager;
}
public async Task<IActionResult> OnPostAsync(string returnurl)
{
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
if (HttpContext.User != null)
{
var alias = HttpContext.GetAlias();
var user = _userManager.GetUser(HttpContext.User.Identity.Name, alias.SiteId);
if (user != null)
{
_syncManager.AddSyncEvent(alias.TenantId, EntityNames.User, user.UserId, SyncEventActions.Reload);
}
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
}
returnurl = (returnurl == null) ? "/" : returnurl;
returnurl = (!returnurl.StartsWith("/")) ? "/" + returnurl : returnurl;

View File

@ -4,7 +4,6 @@ using Oqtane.Shared;
using Oqtane.Models;
using System;
using System.Linq;
using System.Reflection;
using Oqtane.Repository;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Configuration;
@ -38,10 +37,11 @@ namespace Oqtane.Pages
private readonly IVisitorRepository _visitors;
private readonly IAliasRepository _aliases;
private readonly ISettingRepository _settings;
private readonly ServerStateManager _serverState;
private readonly IThemeRepository _themes;
private readonly IServerStateManager _serverState;
private readonly ILogManager _logger;
public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, IJwtManager jwtManager, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, ServerStateManager serverState, ILogManager logger)
public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, IJwtManager jwtManager, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, IThemeRepository themes, IServerStateManager serverState, ILogManager logger)
{
_configuration = configuration;
_tenantManager = tenantManager;
@ -55,6 +55,7 @@ namespace Oqtane.Pages
_visitors = visitors;
_aliases = aliases;
_settings = settings;
_themes = themes;
_serverState = serverState;
_logger = logger;
}
@ -113,7 +114,7 @@ namespace Oqtane.Pages
}
}
var site = _sites.InitializeSite(alias);
var site = _sites.GetSite(alias.SiteId);
if (site != null && (!site.IsDeleted || url.Contains("admin/site")) && site.Runtime != "Hybrid")
{
Route route = new Route(url, alias.Path);
@ -167,12 +168,13 @@ namespace Oqtane.Pages
}
// stylesheets
var themes = _themes.GetThemes().ToList();
var resources = new List<Resource>();
if (string.IsNullOrEmpty(page.ThemeType))
{
page.ThemeType = site.DefaultThemeType;
}
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
var theme = themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
if (theme?.Resources != null)
{
resources.AddRange(theme.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet).ToList());
@ -199,7 +201,7 @@ namespace Oqtane.Pages
}
HeadResources += ParseScripts(site.HeadContent);
BodyResources += ParseScripts(site.BodyContent);
var scripts = _serverState.GetServerState(site.SiteId).Scripts;
var scripts = _serverState.GetServerState(alias.SiteKey).Scripts;
foreach (var script in scripts)
{
AddScript(script, alias);

View File

@ -11,7 +11,7 @@ namespace Oqtane.Repository
Site GetSite(int siteId);
Site GetSite(int siteId, bool tracking);
void DeleteSite(int siteId);
Site InitializeSite(Alias alias);
void InitializeSite(Alias alias);
void CreatePages(Site site, List<PageTemplate> pageTemplates, Alias alias);
}
}

View File

@ -21,10 +21,10 @@ namespace Oqtane.Repository
private readonly IPermissionRepository _permissions;
private readonly ITenantManager _tenants;
private readonly ISettingRepository _settings;
private readonly ServerStateManager _serverState;
private readonly IServerStateManager _serverState;
private readonly string settingprefix = "SiteEnabled:";
public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ITenantManager tenants, ISettingRepository settings, ServerStateManager serverState)
public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ITenantManager tenants, ISettingRepository settings, IServerStateManager serverState)
{
_db = context;
_cache = cache;
@ -179,6 +179,8 @@ namespace Oqtane.Repository
if (siteId != -1)
{
var siteKey = _tenants.GetAlias().SiteKey;
// get all module definition permissions for site
List<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList();
@ -186,7 +188,7 @@ namespace Oqtane.Repository
var settings = _settings.GetSettings(EntityNames.ModuleDefinition).ToList();
// populate module definition site settings and permissions
var serverState = _serverState.GetServerState(siteId);
var serverState = _serverState.GetServerState(siteKey);
foreach (ModuleDefinition moduledefinition in ModuleDefinitions)
{
moduledefinition.SiteId = siteId;
@ -212,9 +214,9 @@ namespace Oqtane.Repository
{
foreach (var assembly in moduledefinition.Dependencies.Replace(".dll", "").Split(',', StringSplitOptions.RemoveEmptyEntries).Reverse())
{
if (!serverState.Assemblies.Contains(assembly))
if (!serverState.Assemblies.Contains(assembly.Trim()))
{
serverState.Assemblies.Insert(0, assembly);
serverState.Assemblies.Insert(0, assembly.Trim());
}
}
}
@ -251,7 +253,6 @@ namespace Oqtane.Repository
}
}
}
_serverState.SetServerState(siteId, serverState);
// clean up any orphaned permissions
var ids = new HashSet<int>(ModuleDefinitions.Select(item => item.ModuleDefinitionId));

View File

@ -27,13 +27,13 @@ namespace Oqtane.Repository
private readonly IThemeRepository _themeRepository;
private readonly IServiceProvider _serviceProvider;
private readonly IConfigurationRoot _config;
private readonly ServerStateManager _serverState;
private readonly IServerStateManager _serverState;
private readonly ILogManager _logger;
private static readonly object _lock = new object();
public SiteRepository(TenantDBContext context, IRoleRepository roleRepository, IProfileRepository profileRepository, IFolderRepository folderRepository, IPageRepository pageRepository,
IModuleRepository moduleRepository, IPageModuleRepository pageModuleRepository, IModuleDefinitionRepository moduleDefinitionRepository, IThemeRepository themeRepository, IServiceProvider serviceProvider,
IConfigurationRoot config, ServerStateManager serverState, ILogManager logger)
IConfigurationRoot config, IServerStateManager serverState, ILogManager logger)
{
_db = context;
_roleRepository = roleRepository;
@ -95,23 +95,25 @@ namespace Oqtane.Repository
_db.SaveChanges();
}
public Site InitializeSite(Alias alias)
public void InitializeSite(Alias alias)
{
var site = GetSite(alias.SiteId);
// load themes and module definitions
site.Themes = _themeRepository.GetThemes().ToList();
var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId);
// site migrations
var serverstate = _serverState.GetServerState(alias.SiteId);
if (!serverstate.IsMigrated)
var serverstate = _serverState.GetServerState(alias.SiteKey);
if (!serverstate.IsInitialized)
{
// ensure migrations are only executed once
// ensure site initialization is only executed once
lock (_lock)
{
if (!serverstate.IsMigrated)
if (!serverstate.IsInitialized)
{
var site = GetSite(alias.SiteId);
// initialize theme Assemblies and Scripts
site.Themes = _themeRepository.GetThemes().ToList();
// initialize module Assemblies and Scripts
var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId);
// execute migrations
var version = ProcessSiteMigrations(alias, site);
version = ProcessPageTemplates(alias, site, moduleDefinitions, version);
if (site.Version != version)
@ -119,13 +121,11 @@ namespace Oqtane.Repository
site.Version = version;
UpdateSite(site);
}
serverstate.IsMigrated = true;
_serverState.SetServerState(alias.SiteId, serverstate);
serverstate.IsInitialized = true;
}
}
}
return site;
}
}
private string ProcessSiteMigrations(Alias alias, Site site)
@ -640,7 +640,7 @@ namespace Oqtane.Repository
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = "<p>The page you requested does not exist.</p>"
Content = "<p>The page you requested does not exist or you do not have sufficient rights to view it.</p>"
}
}
});

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