Compare commits

..

449 Commits

Author SHA1 Message Date
8d7845a44d Merge pull request #4944 from oqtane/master
6.0.1 Release
2024-12-20 16:52:31 -05:00
3a15e6e5e9 Merge pull request #4943 from oqtane/dev
6.0.1 Release
2024-12-20 16:52:10 -05:00
3b8a51e855 Merge pull request #4942 from sbwalker/dev
fix reload script to use static array rather than a live HtmlCollection
2024-12-20 15:39:50 -05:00
f2cb34cc35 fix reload script to use static array rather than a live HtmlCollection 2024-12-20 15:39:35 -05:00
723ce62a34 Merge pull request #4940 from sbwalker/dev
write upgrade errors to log rather than console
2024-12-20 14:38:34 -05:00
2c9a2ea021 write upgrade errors to log rather than console 2024-12-20 14:38:21 -05:00
2be008d6d1 Merge pull request #4939 from sbwalker/dev
fix compilation issues in PR #4782
2024-12-20 12:36:24 -05:00
7fb51bdd0a fix compilation issues in PR #4782 2024-12-20 12:36:10 -05:00
abdbe3694f Merge pull request #4782 from thabaum/edit-add-page-scrolltotoppage-error
Fixes #4781: Adds Edit + Add Page ScrollToTopPage() On Settings Tab Form Error Messages
2024-12-20 12:26:55 -05:00
bd87e5012f Merge pull request #4938 from sbwalker/dev
fix documentation
2024-12-20 11:55:50 -05:00
55e18f2364 fix documentation 2024-12-20 11:55:35 -05:00
655e84072d Merge pull request #4937 from sbwalker/dev
use CompressionEnabled switch to disable static asset compression during publish - eliminates need to cleanup files manually in release.cmd
2024-12-20 11:46:52 -05:00
ab5409d5b6 use CompressionEnabled switch to disable static asset compression during publish - eliminates need to cleanup files manually in release.cmd 2024-12-20 11:46:30 -05:00
5a5da6486c Merge pull request #4933 from sbwalker/dev
remove *.br, *.gz files from wwwroot content folders in published release (as MapStaticAssets is not enabled)
2024-12-19 19:56:36 -05:00
7e99252429 remove *.br, *.gz files from wwwroot content folders in published release (as MapStaticAssets is not enabled) 2024-12-19 19:56:13 -05:00
10e0dcef8b Merge pull request #4931 from sbwalker/dev
ensure Pages collection is always returned in the same order by moving GetPagesHierarchy method to the repository
2024-12-19 14:45:34 -05:00
80c8433aad ensure Pages collection is always returned in the same order by moving GetPagesHierarchy method to the repository. 2024-12-19 14:45:12 -05:00
5b0ae372f8 Merge pull request #4930 from sbwalker/dev
add support for data-reload=false attribute in Interactive rendering
2024-12-19 13:04:59 -05:00
b5a1b529ab add support for data-reload=false attribute in Interactive rendering 2024-12-19 13:04:43 -05:00
af821dcd9a Merge pull request #4914 from leigh-pointer/userSettings
User Profile Update: Utilizing User Settings Object
2024-12-18 15:27:34 -05:00
10d3c81520 Merge pull request #4918 from leigh-pointer/ProjectNameTemplates
Implement Dynamic ProjectName Parameter Across Build Process
2024-12-18 15:27:24 -05:00
e3811b453a Merge pull request #4927 from sbwalker/dev
Improvements to add support for script type and data-* attributes. Also added Script and Stylesheet classes to simplify Resource declarations.
2024-12-18 15:16:16 -05:00
ca0fb05baa Improvements to add support for script type and data-* attributes. Also added Script and Stylesheet classes to simplify Resource declarations. 2024-12-18 15:15:54 -05:00
2b4b01bf6e Merge pull request #4926 from sbwalker/dev
always render page-script elements in body
2024-12-18 11:56:18 -05:00
3a1244bddc always render page-script elements in body 2024-12-18 11:56:03 -05:00
b8fd922b19 Merge pull request #4925 from sbwalker/dev
improve reload script to replicate all attributes
2024-12-18 10:32:12 -05:00
03f856025e improve reload script to replicate all attributes 2024-12-18 10:31:59 -05:00
45f04d24c3 Merge pull request #4924 from sbwalker/dev
refactor Static Blazor script processing
2024-12-18 10:09:20 -05:00
2435d610c7 refactor Static Blazor script processing 2024-12-18 10:09:02 -05:00
06572bcd14 Merge pull request #4922 from sbwalker/dev
page-script optimization
2024-12-17 14:57:17 -05:00
3fab79afc0 page-script optimization 2024-12-17 14:57:01 -05:00
2b7dd3fed5 Merge pull request #4921 from sbwalker/dev
render page-script elements at end of head content
2024-12-17 12:31:40 -05:00
d65efed032 render page-script elements at end of head content 2024-12-17 12:31:26 -05:00
c6896ea936 Merge pull request #4920 from sbwalker/dev
fix page-script to support type attribute on inline scripts
2024-12-17 09:13:42 -05:00
b7a41bddec fix page-script to support type attribute on inline scripts 2024-12-17 09:13:26 -05:00
6fd80c3737 Merge pull request #4919 from sbwalker/dev
page-script improvements
2024-12-17 08:34:55 -05:00
0aa690b3b1 page-script improvements 2024-12-17 08:34:39 -05:00
6d5bcfc6ed Implement Dynamic ProjectName Parameter Across Build Process
This pull request introduces a dynamic ProjectName parameter across our build process, enhancing flexibility and reducing hardcoding in our module and theme development workflow. The changes affect the project file, NuSpec file, command scripts.
2024-12-17 10:35:14 +01:00
b60de69fa5 Merge pull request #4917 from sbwalker/dev
improvements to page-script
2024-12-16 12:44:23 -05:00
d991b57d08 improvements to page-script 2024-12-16 12:44:07 -05:00
1133d7fcba User Profile Update: Utilizing User Settings Object
This pull request updates the User Profile implementation to utilize the existing User Settings object on the user object. While the current implementation is functional, this change offers several benefits:
Improved code consistency
Better guidance for developers accessing User Settings
Alignment with the framework's best practices
2024-12-14 11:09:54 +01:00
6fbf0383bb Merge pull request #4911 from leigh-pointer/FixGlow
This is a cosmetic fix to the Oqtane Glow image
2024-12-13 16:46:49 -05:00
0296230219 Merge pull request #4913 from sbwalker/dev
page-script enhancements
2024-12-13 16:46:23 -05:00
dedfbba27a page-script enhancements 2024-12-13 16:46:08 -05:00
dc5441da07 This is a cosmetic fix to the Oqtane Glow image
The glow on the original is cropped at the left by a few pixcels.  This update fixes that and sizes the image to a 600 x 150
2024-12-13 11:45:13 +01:00
585648b7f3 Merge pull request #4907 from leigh-pointer/ProfileMaxLength
FIX for #4906 Profile Field does not allow max length
2024-12-10 09:12:52 -05:00
cd0ee1c26d FIX for #4906 Profile Field does not allow max length
This update applies the maxlength attribute fix to all input and textarea elements. The maxlength attribute will only be added if p.MaxLength is greater than 0, allowing unlimited characters when it's 0 or less.
2024-12-10 09:25:01 +01:00
d7a7be5af4 Merge pull request #4904 from sbwalker/dev
add sync events for user login/logout
2024-12-09 10:55:55 -05:00
13e4267c11 add sync events for user login/logout 2024-12-09 10:55:40 -05:00
15bc47e3e8 Merge pull request #4898 from leigh-pointer/TemplateParam
Implement Dynamic TargetFramework in Debug Scripts #4897
2024-12-09 07:36:00 -05:00
1a4380dcd7 Variable update 2024-12-07 15:02:27 +01:00
5ace34b5cd Add the $TargetFramework macro to the Release Builds
Add the $TargetFramework macro to the Release Builds
2024-12-06 13:37:37 +01:00
f010c0f1fa Implement Dynamic TargetFramework in Debug Scripts #4897
This PR updates our debug scripts (both .cmd and .sh) to dynamically use the current TargetFramework passed from the build process. This change improves flexibility and future-proofs our build process for different .NET versions.
2024-12-05 12:43:38 +01:00
2c721ad5dd Merge pull request #4895 from tvatavuk/enh-4883-control-panel
Add ShowEditMode parameter to ControlPanel to allow hiding the Edit Mode toggle button
2024-12-04 08:25:17 -05:00
8a7c2ce2c2 Merge pull request #4894 from W6HBR/dev
Fix #4885 - Pass userid as int to GetUser for JWT authentication
2024-12-04 08:24:27 -05:00
b2a7b813de Remove redundant IServiceProvider injection 2024-12-04 09:39:02 +01:00
e85cf04b99 Fix #4883: Add ShowEditMode parameter to ControlPanel to allow hiding the Edit Mode toggle button 2024-12-04 09:37:21 +01:00
ab6fa48172 Fix #4885 - Pass userid as int to GetUser for JWT authentication 2024-12-03 10:34:44 -08:00
c81905882f Merge pull request #4881 from sbwalker/dev
improve security of UserRole API
2024-11-27 14:59:41 -05:00
f0d31c1114 improve security of UserRole API 2024-11-27 14:59:25 -05:00
497b255216 Merge pull request #4880 from sbwalker/dev
User Settings should only be accessible to individual users or administrators
2024-11-27 13:16:04 -05:00
d96286d771 User Settings should only be accessible to individual users or administrators 2024-11-27 13:15:43 -05:00
2441647d75 Merge pull request #4879 from sbwalker/dev
update EFCore.NamingConventions to .NET 9 package version
2024-11-27 13:06:53 -05:00
77b780d631 update EFCore.NamingConventions to .NET 9 package version 2024-11-27 13:06:37 -05:00
cdd03bf3d4 Merge pull request #4878 from sbwalker/dev
User Settings should only be accessible to individual users or administrators
2024-11-27 13:04:27 -05:00
e786c35f7d User Settings should only be accessible to individual users or administrators 2024-11-27 13:04:06 -05:00
e83399acb1 Merge pull request #4876 from sbwalker/dev
prevent notifications from being accessed by other users
2024-11-26 14:30:55 -05:00
ffea9e3210 prevent notifications from being accessed by other users 2024-11-26 14:30:41 -05:00
f71a3a1ce3 Merge pull request #4875 from oqtane/revert-4828-TabChange
Revert "Fix for Tabpanel is not updating the UI. #4778"
2024-11-26 13:36:18 -05:00
a5ccc23604 Revert "Fix for Tabpanel is not updating the UI. #4778" 2024-11-26 13:36:05 -05:00
1ed4c8a094 Merge pull request #4874 from oqtane/revert-4871-HTMLTabError
Revert "Rework for Tabstrip regression issue"
2024-11-26 13:35:37 -05:00
4a74549c1b Revert "Rework for Tabstrip regression issue" 2024-11-26 13:35:21 -05:00
a499cfb98f Merge pull request #4871 from leigh-pointer/HTMLTabError
Rework for Tabstrip regression issue
2024-11-26 11:27:52 -05:00
01038c8296 Merge pull request #4873 from sbwalker/dev
include SECURITY.md
2024-11-26 11:25:35 -05:00
7407f79b3d include SECURITY.md 2024-11-26 11:25:23 -05:00
a845dd1976 Rework for Tabstrip regression issue
Fix for Tabpanel is not updating the UI. #4778 #4828
2024-11-26 15:39:51 +01:00
9d7549da70 Reverted TabStrip and Panel 2024-11-26 10:33:18 +01:00
f5cc61384f Merge pull request #4870 from sbwalker/dev
reference Quill CSS theme using BaseUrl so that it works in .NET MAUI as well as web
2024-11-25 14:20:15 -05:00
844778d36a reference Quill CSS theme using BaseUrl so that it works in .NET MAUI as well as web 2024-11-25 14:19:58 -05:00
871b0a274e Merge pull request #4869 from sbwalker/dev
improve message grammar
2024-11-25 13:48:41 -05:00
737740a3ca improve message grammar 2024-11-25 13:48:28 -05:00
ae8d600600 Update README.md 2024-11-25 09:11:12 -05:00
2f1691bfb0 Update README.md 2024-11-25 09:10:20 -05:00
a3d25f91c8 Update README.md 2024-11-25 09:09:52 -05:00
ff84b50817 Update README.md 2024-11-25 09:09:13 -05:00
0be8242284 Merge pull request #4862 from leigh-pointer/SettingsSetTabOnSave
Updated the Module Settings to use the new ActiveTab parameter
2024-11-25 08:39:45 -05:00
e25a6259ea Merge pull request #4861 from zyhfish/task/fix-4841
Fix #4841: force 2FA validation when it's required in site level.
2024-11-25 08:39:26 -05:00
1578f82efb Updated the Module Settings to use the new ActiveTab parameter 2024-11-23 11:42:22 +01:00
Ben
b5f75f0c5e Fix #4841: force 2FA validation when it's required in site level. 2024-11-23 13:04:27 +08:00
601caab3b6 Merge pull request #4860 from sbwalker/dev
fix #4760 - display update confirmation message in Site Settings
2024-11-22 16:34:46 -05:00
6d3092f440 fix #4760 - display update confirmation message in Site Settings 2024-11-22 16:34:35 -05:00
ef27937c7a Merge pull request #4785 from thabaum/refactored-heading-ifelse
Fixes #4784: Refactor TabPanel Heading Assignment Logic
2024-11-22 15:34:51 -05:00
f4a7b79c4f Merge pull request #4828 from leigh-pointer/TabChange
Fix for Tabpanel is not updating the UI. #4778
2024-11-22 15:30:51 -05:00
2531776a48 Merge pull request #4859 from sbwalker/dev
prepare for 6.0.1
2024-11-22 12:29:56 -05:00
ced80419aa prepare for 6.0.1 2024-11-22 12:29:44 -05:00
ad2816f4e8 Merge pull request #4858 from sbwalker/dev
fix #4848 - remove assemblies from /bin which have been moved to /bin/refs in .NET 9
2024-11-22 12:14:04 -05:00
3528b8c674 fix #4848 - remove assemblies from /bin which have been moved to /bin/refs in .NET 9 2024-11-22 12:13:45 -05:00
80c83c626d Merge pull request #4853 from leigh-pointer/PagerAlignment
Fix for #4852 align the Page numbers container
2024-11-22 11:56:12 -05:00
6a355f2aea Merge pull request #4857 from sbwalker/dev
fix #4855 - dropping required column causes issue on SQLite
2024-11-22 11:55:56 -05:00
7d94e4a53a fix #4855 - dropping required column causes issue on SQLite 2024-11-22 11:55:43 -05:00
823c04742e Merge pull request #4854 from sbwalker/dev
resolve .NET version issue in nuspec files
2024-11-21 10:54:06 -05:00
043fb1abd1 resolve .NET version issue in nuspec files 2024-11-21 10:53:52 -05:00
f01e85b690 Fix for #4852 align the Page numbers container 2024-11-21 16:05:32 +01:00
7eb1298847 Merge pull request #4847 from sbwalker/dev
do not include Oqtane.Server.staticwebassets.endpoints.json in release packages
2024-11-19 13:50:05 -05:00
a5480c9a96 do not include Oqtane.Server.staticwebassets.endpoints.json in release packages 2024-11-19 13:49:45 -05:00
f948600e86 Merge pull request #4845 from sbwalker/dev
fix 2 factor authentication email
2024-11-18 15:04:01 -05:00
420182b9bf fix 2 factor authentication email 2024-11-18 15:03:48 -05:00
8c430ce1a6 Merge pull request #4837 from Trifoia/4803-Add-a-CONTRIBUTING.md
4803 add a contributing.md
2024-11-15 15:02:28 -05:00
d3717dbe19 Remove DefaultDBType value and InstallationId from appsettings.json
The commit removes the value for DefaultDBType and InstallationId from the appsettings.json file.
2024-11-15 10:31:49 -08:00
caa83d769f Create CONTRIBUTING.md 2024-11-15 10:27:53 -08:00
365f87828f Merge remote-tracking branch 'oqtane/dev' into dev 2024-11-15 09:52:50 -08:00
f780887866 Update README.md 2024-11-14 15:46:26 -05:00
43627d4bb8 Merge pull request #4832 from oqtane/master
6.0.0 release
2024-11-14 15:40:38 -05:00
5d7b276cd1 Merge pull request #4831 from oqtane/dev
6.0.0 release
2024-11-14 15:40:17 -05:00
23597eb997 Merge pull request #4827 from leigh-pointer/Search-4824
Fix: for Disabling search does not hide search icon #4824
2024-11-14 07:54:52 -05:00
b6948367f8 Merge pull request #4826 from leigh-pointer/swash
Swashbuckle.AspNetCore updated to 7.0
2024-11-14 07:52:49 -05:00
db6dd5abee Fix for TabPanel is not updating the UI. #4778
Modified that TabStrip and TabPane, now when the ActiveTab is changed the TabPanel is selected
2024-11-14 10:35:15 +01:00
702eb9e466 Revert "Fix for Page Management tab panel is not updating the UI. #4778"
This reverts commit 3c99006226.
2024-11-14 10:33:10 +01:00
3c99006226 Fix for Page Management tab panel is not updating the UI. #4778 2024-11-14 10:31:53 +01:00
aaaf5683a5 Fix: "Search_Enabled" was being saved as Private so unauthorized users can not access this value. 2024-11-14 09:50:05 +01:00
92aa2236c0 Swashbuckle.AspNetCore updated to 7.0 2024-11-14 09:16:55 +01:00
d2592f72d6 Merge pull request #4825 from sbwalker/dev
modifications to get .NET MAUI working on .NET 9 official release
2024-11-13 20:37:24 -05:00
27120d6cc9 modifications to get .NET MAUI working on .NET 9 official release 2024-11-13 20:37:10 -05:00
a2669d35c3 Merge pull request #4823 from sbwalker/dev
exclude wwwroot/_content from official release build
2024-11-13 14:14:47 -05:00
574164081b exclude wwwroot/_content from official release build 2024-11-13 14:14:35 -05:00
00c2f8dcd8 Merge pull request #4822 from sbwalker/dev
adjust gitignore to exclude wwwroot/_content subfolders
2024-11-13 13:52:08 -05:00
0202bf60e5 adjust gitignore to exclude wwwroot/_content subfolders 2024-11-13 13:51:54 -05:00
16436a171b Merge pull request #4821 from zyhfish/task/fix-build-error
add reference to Microsoft.EntityFrameworkCore package.
2024-11-13 10:10:19 -05:00
Ben
4cf4e0eabd add reference to Microsoft.EntityFrameworkCore package. 2024-11-13 23:06:43 +08:00
5edb98dfb4 Merge pull request #4818 from sbwalker/dev
update PostgreSQL provider to official .NET 9 release
2024-11-12 15:50:27 -05:00
899bf22e15 update PostgreSQL provider to official .NET 9 release 2024-11-12 15:50:13 -05:00
9a33167a6c Merge pull request #4817 from sbwalker/dev
Update to official .NET 9 release
2024-11-12 15:43:42 -05:00
2dc068aa21 upgrade to official .NET 9 release 2024-11-12 15:42:57 -05:00
0f2aa4d2e1 update to official .NET 9 release 2024-11-12 15:40:54 -05:00
a699f5c7bc Merge pull request #4815 from mdmontesinos/dev
Fix: Accesibility issue for Search Button
2024-11-12 15:08:23 -05:00
ab807de3c0 Fix: Accesibility issue for Search Button
The search button in Search component does not have the aria-label.
2024-11-12 17:15:56 +01:00
68514bcb36 Merge pull request #4813 from sbwalker/dev
use HttpClient rather than IHttpClientFactory as IHttpClientFactory does not pass cookies in .NET MAUI
2024-11-11 08:15:14 -05:00
422bf8da59 use HttpClient rather than IHttpClientFactory as IHttpClientFactory does not pass cookies in .NET MAUI 2024-11-11 08:14:53 -05:00
de92dc93dd Merge pull request #4811 from sbwalker/dev
resolved issue when setting initial culture cookie
2024-11-08 15:41:36 -05:00
5a91b143b6 resolved issue when setting initial culture cookie 2024-11-08 15:41:24 -05:00
c745e85706 Merge pull request #4810 from sbwalker/dev
make indexing of Files opt-in rather than opt-out
2024-11-08 15:16:45 -05:00
0f698e0c50 make indexing of Files opt-in rather than opt-out 2024-11-08 15:16:32 -05:00
cd16d77bf0 Merge pull request #4809 from sbwalker/dev
Added defensive logic to File Indexer for scenarios where file does not exist on disk. Added ability to reset the search index prior to reindexing.
2024-11-07 17:05:48 -05:00
fdbdd0ef4c Added defensive logic to File Indexer for scenarios where file does not exist on disk. Added ability to reset the search index prior to reindexing. 2024-11-07 17:05:28 -05:00
71485f4a82 Merge pull request #4808 from sbwalker/dev
fix compilation warning
2024-11-07 15:45:47 -05:00
013056a6e5 fix compilation warning 2024-11-07 15:45:34 -05:00
263498fbdc Merge pull request #4807 from sbwalker/dev
remove icrosoft.AspNetCore.Localization from .NET MAUI project
2024-11-07 12:38:31 -05:00
f46ac2c007 remove icrosoft.AspNetCore.Localization from .NET MAUI project 2024-11-07 12:38:16 -05:00
c23841b6db Merge pull request #4806 from sbwalker/dev
default Description to Module Name if not specified in Module Creator
2024-11-07 11:53:15 -05:00
18b1b5fca5 default Description to Module Name if not specified in Module Creator 2024-11-07 11:52:53 -05:00
6707ac2697 Merge pull request #4802 from sbwalker/dev
remove custom JavaScript reconnection script for SignalR
2024-11-05 11:30:32 -05:00
d85a2fc8ce remove custom JavaScript reconnection script for SignalR 2024-11-05 11:30:18 -05:00
a8997e8f17 Merge pull request #4800 from sbwalker/dev
fix #4795 - ensure deterministic ordering of file parts when merging files after upload (credit @HQuast)
2024-11-04 15:29:16 -05:00
c8a22d9537 fix #4795 - ensure deterministic ordering of file parts when merging files after upload (credit @HQuast) 2024-11-04 15:28:59 -05:00
910669f786 Merge pull request #4793 from thabaum/patch-12
Fixes #4792: Update Oqtane.Server.csproj Package Dependencies
2024-11-01 15:40:14 -04:00
89312c6796 Update Oqtane.Server.csproj Package Dependencies 2024-10-30 18:43:42 -07:00
a6aa96fdb0 Merge pull request #4786 from sbwalker/dev
fix #4770 - set a default value for PrincipalSchema to ensure backward compatibility
2024-10-24 17:08:42 -04:00
7deb0b06af fix #4770 - set a default value for PrincipalSchema to ensure backward compatibility 2024-10-24 17:08:25 -04:00
784f3771b3 Remove Blank Page Specific Error Message 2024-10-24 13:22:08 -07:00
80316824f7 Remove Blank Page Specific Error Message 2024-10-24 13:21:40 -07:00
cbaebb7b7c Remove Blank Page Specific Error Message 2024-10-24 13:21:15 -07:00
97d3764b6e Remove Blank Page Specific Error Message 2024-10-24 13:20:52 -07:00
002f8117cb Merge pull request #4783 from sbwalker/dev
get language using CookieRequestCultureProvider
2024-10-24 15:48:28 -04:00
0dfdb12431 get language using CookieRequestCultureProvider 2024-10-24 15:48:14 -04:00
d77e898929 Refactor TabPanel Heading Assignment Logic
- Simplified the logic for setting the Heading property in the TabPanel component.
- Replaced the if-else statement with a ternary operator for improved readability and maintainability.
- Ensured that the functionality remains unchanged and verified correct assignment of headings.
2024-10-24 12:43:20 -07:00
4c937be884 Clarify Page Name Required Message 2024-10-24 12:38:51 -07:00
c25cce4398 Clarify Page Name Required Message 2024-10-24 12:38:16 -07:00
58c422285a Adds Message.Required.PageName 2024-10-24 12:28:17 -07:00
f2f22f35e8 Adds Message.Required.PageName 2024-10-24 12:26:36 -07:00
15867a7807 Adds await ScrollToPageTop(); to error messages + Page Name Error Message 2024-10-24 12:23:29 -07:00
22e3161a9b Merge pull request #4780 from sbwalker/dev
remove Microsoft.AspNetCore.Localization from default module template
2024-10-24 15:20:20 -04:00
b0c8203b24 remove Microsoft.AspNetCore.Localization from default module template 2024-10-24 15:20:07 -04:00
e2c404d8bb Adds await ScrollToPageTop(); to error messages + Page Name Error Message
- Adds await ScrollToPageTop(); to error messages
- Adds Blank Page Name Error Message
2024-10-24 12:18:30 -07:00
5ee1731c92 Merge pull request #4777 from thabaum/update-html-agility-pack-1.11.69
Fixes #4776: Updates HtmlAgilityPack to 1.11.69
2024-10-24 15:12:17 -04:00
06e8d3b660 Merge pull request #4779 from sbwalker/dev
remove Microsoft.AspNetCore.Http
2024-10-24 15:11:07 -04:00
f09709aedb remove Microsoft.AspNetCore.Http 2024-10-24 15:10:52 -04:00
598d5decac Update HtmlAgilityPack to 1.11.69 2024-10-24 10:18:24 -07:00
7832a6053e Merge pull request #4775 from sbwalker/dev
remove some dependencies on Microsoft.AspNetCore.Http
2024-10-24 13:16:21 -04:00
588748230e remove some dependencies on Microsoft.AspNetCore.Http 2024-10-24 13:16:08 -04:00
8668165c72 Merge pull request #4774 from sbwalker/dev
remove Microsoft.AspNetCore.Localization
2024-10-24 12:25:04 -04:00
a967332f89 remove Microsoft.AspNetCore.Localization 2024-10-24 12:24:46 -04:00
6719d242bd Merge pull request #4756 from zyhfish/task/fix-4752
Fix #4752: validate the username and email.
2024-10-24 09:53:24 -04:00
Ben
3565185808 update the error message. 2024-10-24 20:13:43 +08:00
Ben
ce51262197 update the code to use simple validation. 2024-10-24 20:04:18 +08:00
992a786c2b Merge pull request #4763 from thabaum/ScrollTo-interop-modal
Fixes #4762: Adds logic ScrollTo interop.js method detecting if method is executed inside a modal
2024-10-22 16:28:45 -04:00
038df95aa0 Merge pull request #4759 from thabaum/scroll-to-top-user-settings
Scroll to top after saving settings - Fixes #4758
2024-10-22 16:28:33 -04:00
4ebd660de2 Merge pull request #4766 from sbwalker/dev
resolve compiler warning in .NET MAUI
2024-10-21 15:42:08 -04:00
a9ea41a488 resolve compiler warning in .NET MAUI 2024-10-21 15:41:57 -04:00
196594b490 Merge pull request #4765 from sbwalker/dev
update MAUI project to .NET 9 RC2
2024-10-21 15:34:34 -04:00
1516d5af10 update MAUI project to .NET 9 RC2 2024-10-21 15:34:22 -04:00
ffcd1595a9 Merge pull request #4764 from sbwalker/dev
update Updater to .NET 9
2024-10-21 15:25:27 -04:00
42e5c6e111 update Updater to .NET 9 2024-10-21 15:25:14 -04:00
8a9651dc50 Adds logic for .modal class ScrollTo function 2024-10-21 12:22:01 -07:00
4be2f4f2d9 Update README.md 2024-10-21 15:21:29 -04:00
369bf7a235 Adds logic for .modal class ScrollTo function
fixes #4762
2024-10-21 12:20:24 -07:00
136545b404 Merge pull request #4761 from sbwalker/dev
changes to migrate Oqtane to .NET 9 and version 6.0.0
2024-10-21 14:51:38 -04:00
73ea17ae0f changes to migrate Oqtane to .NET 9 and version 6.0.0 2024-10-21 14:51:20 -04:00
23010acef4 Scroll to top after saving settings - Fixes #4758 2024-10-21 10:36:12 -07:00
Ben
4f74962ce2 Fix #4752: validate the username and email. 2024-10-21 23:11:57 +08:00
7f978c7845 Update README.md 2024-10-17 13:52:21 -04:00
731fd46ea2 Merge pull request #4747 from oqtane/master
5.2.4 release
2024-10-17 13:46:40 -04:00
859759d691 Merge pull request #4746 from oqtane/dev
5.2.4 release
2024-10-17 13:46:22 -04:00
5e9567158f Merge pull request #4744 from sbwalker/dev
add additional external login providers
2024-10-16 08:35:14 -04:00
51d244f3aa add additional external login providers 2024-10-16 08:35:01 -04:00
4c5a07edd5 Merge pull request #4743 from sbwalker/dev
add missing localization keys
2024-10-16 07:52:49 -04:00
8113a754a1 add missing localization keys 2024-10-16 07:52:36 -04:00
3cd40c6195 Merge pull request #4742 from sbwalker/dev
fix sorting of Site.Languages property
2024-10-15 16:46:16 -04:00
56cfb2ce06 fix sorting of Site.Languages property 2024-10-15 16:46:05 -04:00
72087823ac Merge pull request #4741 from sbwalker/dev
fix #4733 - remove Name column from Language table and populate value dynamically
2024-10-15 16:44:44 -04:00
bcf7866fe2 fix #4733 - remove Name column from Language table and populate value dynamically 2024-10-15 15:58:17 -04:00
b64772e484 Merge pull request #4739 from thabaum/add-discord-community
Fixes #4738 - Add Discord community button to README.md
2024-10-15 15:26:32 -04:00
088d665942 Update Discord Community Link For Consistency 2024-10-15 10:27:59 -07:00
7d6c10befb Add Discord community button to README.md
- Added a button for joining the Oqtane Discord server
- Included a brief description encouraging community engagement
2024-10-15 10:17:01 -07:00
f88e3d04b8 Merge pull request #4736 from thabaum/mysql.data-9.1.0
Fixes #4735 - Update MySQL.Data to 9.1.0
2024-10-15 12:06:50 -04:00
f57676a22b Update MySQL.Data to 9.1.0 2024-10-15 08:15:52 -07:00
8618cb62e4 Merge pull request #4732 from sbwalker/dev
fix #4711 - full page refresh required to affect language changes
2024-10-15 08:32:08 -04:00
c31c88ed1f fix #4711 - full page refresh required to affect language changes 2024-10-15 08:31:54 -04:00
6022acd21f Merge pull request #4731 from sbwalker/dev
fix #4716 - sort recycle bin items by DeletedOn date descending
2024-10-15 07:56:07 -04:00
b3071b9272 fix #4716 - sort recycle bin items by DeletedOn date descending 2024-10-15 07:55:54 -04:00
ec6a6d6653 Merge pull request #4729 from sbwalker/dev
localize names of languages based on user's UI culture
2024-10-14 17:18:09 -04:00
52f552b4de localize names of languages based on user's UI culture 2024-10-14 17:17:54 -04:00
2643d3396b Merge pull request #4728 from sbwalker/dev
set HttpOnly to false for Localization cookie in static rendering
2024-10-14 16:49:27 -04:00
62d59a09cf set HttpOnly to false for Localization cookie in static rendering 2024-10-14 16:49:14 -04:00
a68ff8a4f0 Merge pull request #4727 from sbwalker/dev
When displaying Database Type use SQL Server rather than LocalDB to avoid confusion
2024-10-14 16:21:25 -04:00
93d4bfcd7a When displaying Database Type use SQL Server rather than LocalDB to avoid confusion 2024-10-14 16:21:03 -04:00
5fb80c1a7b Merge pull request #4726 from sbwalker/dev
fix #4722 - support PrincipalSchema when creating foreign keys (credit @Hypnodude)
2024-10-14 15:36:54 -04:00
04b38444ce fix #4722 - support PrincipalSchema when creating foreign keys (credit @Hypnodude) 2024-10-14 15:36:32 -04:00
ca3df02002 Merge pull request #4723 from mdmontesinos/dev
Image Resizing and Format via QueryString in Files Page (enhances #4692)
2024-10-14 15:20:37 -04:00
d952c33fab Merge pull request #4725 from sbwalker/dev
fix #4714 as well as breaking change in #4712
2024-10-14 15:05:59 -04:00
93bc1cd5af fix #4714 as well as breaking change in #4712 2024-10-14 15:05:46 -04:00
0e5b370ee8 Merge pull request #4712 from thabaum/language-switcher-cookie
Fix #4710 - Adds language switcher component cookie set options for secure, httpOnly, sameSite + interop.cs/interop.js methods samesite and secure options
2024-10-14 14:41:18 -04:00
f4fd4e28c9 Merge pull request #4724 from sbwalker/dev
update MAUI project to .NET 8.0.10
2024-10-14 13:12:09 -04:00
ec8433eb45 update MAUI project to .NET 8.0.10 2024-10-14 13:11:55 -04:00
0d4a40e9bb Merge pull request #4715 from leigh-pointer/CompUpdate-8.0.10
Updated Microsoft Components for 8.0.8 to 8.0.10
2024-10-14 13:04:54 -04:00
c3668f4179 Merge pull request #4708 from thabaum/patch-10
fix typo in startup.cs
2024-10-14 12:48:38 -04:00
3adb7ecb1c Enhances image manipulation with format (webp encoder, defaults to png)
- computes etag with all manipulation parameters
2024-10-13 17:20:18 +02:00
aa5b84a214 Implements Image Manipulation in Files Page via QueryString
- Extracts the image creation into a service
- Refactors Files Page GET action for better readability and cyclomatic complexity
2024-10-13 12:38:43 +02:00
3d83fccbf1 remove appsettings 2024-10-09 15:07:57 +02:00
4c4255be6b Updated Template files with new component version 8.0.10 2024-10-09 14:42:07 +02:00
ed6054b082 Updated Microsoft Components for 8.0.8 to 8.0.10
Tested on upgrade and new Oqtane instance creation.
2024-10-09 14:35:27 +02:00
f60f7a4dc1 Remove httpOnly setting from setCookie 2024-10-05 14:17:20 -07:00
998dc95cb2 Removed extra attribute for interop.SetCookie 2024-10-05 14:08:16 -07:00
12f5d7b846 Remove extra attribute for SetCookie 2024-10-05 14:06:35 -07:00
906ae0a43e Remove extra attribute for SetCookie 2024-10-05 14:06:18 -07:00
485b774876 Remove httpOnly cooking attribute from SetCookie 2024-10-05 14:03:20 -07:00
3121cf5b75 Remove unnecessary httpOnly setCookie option 2024-10-05 14:00:18 -07:00
ce7570dae2 Remove Unnecessary httpOnly setting setCookie Option 2024-10-05 13:58:55 -07:00
b5ea0dfbc7 Update Cleanup "setCookie" function notes options: secure, httpOnly, Samesite 2024-10-05 13:57:36 -07:00
dd0f8f4772 Update SetCookie function to include secure, httpOnly and sameSite 2024-10-05 13:55:35 -07:00
9d0ab34274 Update 'SetCookie" option settings "secure, httpOnly, sameSite" 2024-10-05 13:28:33 -07:00
e6038be6f7 Update SetCookie Option Settings Secure, HttpOnly, SameSite 2024-10-05 13:27:14 -07:00
bd2153a0ed Update cookie options to set SameSite, HttpOnly, Secure settings 2024-10-05 13:23:09 -07:00
e526deac20 Update Cookie Settings Secure, httpOnly, sameSite 2024-10-05 13:19:52 -07:00
b65f165dcf Update adds SameSite, Secure and httpOnly SetCookie Settings 2024-10-05 13:17:31 -07:00
f9fbe5adc2 Update NavigateTo() to use "true" instead of "forceLoad: true" 2024-10-05 12:11:52 -07:00
d1e73571a1 fix typo 2024-10-05 11:12:01 -07:00
1047058676 Merge pull request #4705 from maurocavallin/dev
Hard deletion of page more robust use of entity framework contexts
2024-10-04 17:14:00 -04:00
29a1e77da8 Hard deletion of page more robust use of contexts (fixes issue: presence of stale records of deleted page on db). 2024-10-04 22:46:40 +02:00
290547e482 Merge pull request #4702 from sbwalker/dev
add defensive logic if ModuleState is null  in ModuleMessage
2024-10-02 16:53:46 -04:00
3df45ca20f add defensive logic if ModuleState is null in ModuleMessage 2024-10-02 16:53:36 -04:00
cc06258484 Merge pull request #4701 from sbwalker/dev
updated version to 5.2.4
2024-10-02 16:39:45 -04:00
2c262d0655 updated version to 5.2.4 2024-10-02 16:39:31 -04:00
1875e1e158 Merge pull request #4696 from sbwalker/dev
fix #4695 - null reference exception deleting a setting which does not exist
2024-10-02 08:30:58 -04:00
1c95967b31 fix #4695 - null reference exception deleting a setting which does not exist 2024-10-02 08:30:34 -04:00
2ae98929de Merge pull request #4689 from thabaum/Update-Dependencies-and-Prepare-Release-5.2.4
Fixes #4688 - Update dependencies and prepare release 5.2.4
2024-10-01 11:21:35 -04:00
6a1dd38cbb Merge pull request #4691 from zyhfish/task/fix-4690
Fix #4690: prevent invalid parsing.
2024-10-01 11:20:01 -04:00
Ben
c458a77d27 Fix #4690: prevent invalid parsing. 2024-10-01 22:13:32 +08:00
352c23f389 Prepare Release 5.2.4 2024-10-01 06:15:46 -07:00
6c5a1dc2e1 Prepare Release 5.2.4 2024-10-01 06:14:57 -07:00
004ff1e91d Update Dependencies and Prepare Release 5.2.4 2024-10-01 06:14:07 -07:00
f7de4c567b Prepare Release 5.2.4 2024-10-01 06:12:07 -07:00
b2ad1010ac Prepare Release 5.2.4 2024-10-01 06:11:36 -07:00
660e164ff8 Prepare Release 5.2.4 2024-10-01 06:11:04 -07:00
eb6dc80b50 Prepare Release 5.2.4 - removed whitespace 2024-10-01 06:10:21 -07:00
a9882cc96a Update Oqtane.Shared.nuspec 2024-10-01 06:09:35 -07:00
fb5a2ce178 Prepare Release 5.2.4 2024-10-01 06:09:12 -07:00
d441b31dc7 Prepare Release 5.2.4 2024-10-01 06:08:46 -07:00
d4239fe7e0 Prepare Release 5.2.4 2024-10-01 06:07:55 -07:00
6a98999105 Prepare Release 5.2.4 2024-10-01 06:06:03 -07:00
5caa1fe7d4 Prepare Release 5.2.4 2024-10-01 06:05:07 -07:00
ef33bdb65e Prepare Release 5.2.4 2024-10-01 06:04:11 -07:00
14a463382b Prepare Release 5.2.4 2024-10-01 06:03:20 -07:00
1ad79874c8 Update Dependencies and Prepare Release 5.2.4 2024-10-01 06:02:40 -07:00
1a61a58d28 Prepare Release 5.2.4 2024-10-01 06:00:20 -07:00
3e3c973679 Prepare Release 5.2.4 2024-10-01 05:59:47 -07:00
9dede84d20 Update Dependencies and Prepare Release 5.2.4 2024-10-01 05:57:17 -07:00
e78b11cf62 Merge pull request #4687 from sbwalker/dev
add some clarity to the database fields help text
2024-09-30 13:32:08 -04:00
341ca5a330 add some clarity to the database fields help text 2024-09-30 13:31:57 -04:00
e5d8c02def Merge pull request #4686 from sbwalker/dev
add validation of recipient email address to Notification job
2024-09-30 11:27:59 -04:00
947aa08c42 add validation of recipient email address to Notification job 2024-09-30 11:27:47 -04:00
9a04d436bd Merge pull request #4683 from sbwalker/dev
simplify configuration of external login providers
2024-09-27 16:21:23 -04:00
be0754f568 simplify configuration of external login providers 2024-09-27 16:21:06 -04:00
93c4bbc0d1 Merge pull request #4682 from sbwalker/dev
fix #4654 - show progress indicator during download
2024-09-27 12:01:07 -04:00
b98535810b fix #4654 - show progress indicator during download 2024-09-27 12:00:49 -04:00
393cf8da1f Merge pull request #4681 from sbwalker/dev
add disclaimer to System Update feature
2024-09-27 09:00:15 -04:00
ea2846973a add disclaimer to System Update feature 2024-09-27 09:00:04 -04:00
3398c1cbfe Merge pull request #4678 from sbwalker/dev
remove unnecessary using statement
2024-09-26 15:54:35 -04:00
39c79ea68e remove unnecessary using statement 2024-09-26 15:54:22 -04:00
66900f4a32 Merge pull request #4677 from sbwalker/dev
sign out the principal when it is rejected due to security stamp changes
2024-09-26 15:53:55 -04:00
df71dd14f7 sign out the principal when it is rejected due to security stamp changes 2024-09-26 15:53:14 -04:00
8113ca3069 Merge pull request #4676 from sbwalker/dev
fix #4657 - Cannot add new site to existing installation using separate database On IIS
2024-09-26 14:07:11 -04:00
d468e675c2 fix #4657 - Cannot add new site to existing installation using separate database On IIS 2024-09-26 14:06:51 -04:00
1e84cedf82 Merge pull request #4674 from sbwalker/dev
fix localization spelling mistake
2024-09-26 13:46:41 -04:00
7f4087e3de fix localization spelling mistake 2024-09-26 13:46:29 -04:00
facd3c8956 Merge pull request #4673 from sbwalker/dev
fix remote login issue which could occut if multiple users have the same email address
2024-09-26 13:38:30 -04:00
3e50deecb7 fix remote login issue which could occut if multiple users have the same email address 2024-09-26 13:37:39 -04:00
628c504f84 Merge pull request #4672 from sbwalker/dev
fix #4667 - installation issues when running on IIS
2024-09-26 13:33:31 -04:00
e1ada78c1f fix #4667 - installation issues when running on IIS 2024-09-26 13:33:16 -04:00
09fa1e365c Merge pull request #4670 from thabaum/remove-unnecessary-usings
Fixes #4669 - Remove unnecessary usings in IMultiDatabase.cs
2024-09-26 13:32:33 -04:00
28b6b03d06 Remove unnecessary usings 2024-09-25 15:13:06 -07:00
a4395b62ff Merge pull request #4668 from sbwalker/dev
fix #4666 - scroll position in enhanced navigation
2024-09-25 17:06:03 -04:00
4511acf273 fix #4666 - scroll position in enhanced navigation 2024-09-25 17:05:53 -04:00
fde53a2d83 Update README.md 2024-09-23 16:33:41 -04:00
f6cd04fdb8 Merge pull request #4662 from oqtane/master
Merge pull request #4661 from oqtane/dev
2024-09-23 16:26:39 -04:00
a5eede6c7a Merge pull request #4661 from oqtane/dev
5.2.3 release
2024-09-23 16:26:24 -04:00
2e83817c83 Merge pull request #4660 from sbwalker/dev
prepare for 5.2.3
2024-09-23 16:18:10 -04:00
82aea40ae4 prepare for 5.2.3 2024-09-23 16:17:55 -04:00
1b289eae24 Merge pull request #4659 from sbwalker/dev
use RoleName rather than RoleId for consistency
2024-09-23 16:08:02 -04:00
81420b2c88 use RoleName rather than RoleId for consistency 2024-09-23 16:07:49 -04:00
775731b745 Merge pull request #4657 from thabaum/patch-9
FIX #4655 - autocomplete="off" for SMTP Username/Password
2024-09-23 15:44:06 -04:00
489e7d4a67 Merge pull request #4658 from sbwalker/dev
fix RoleId missing from Permission Clone method
2024-09-23 15:38:51 -04:00
b6508764d8 fix RoleId missing from Permission Clone method 2024-09-23 15:38:35 -04:00
6dedd87305 autocomplete="off" for SMTP Username/Password 2024-09-23 12:19:49 -07:00
89fa29b310 Merge pull request #4653 from sbwalker/dev
improve scroll position script
2024-09-23 09:55:59 -04:00
be5df9c22a improve scroll position script 2024-09-23 09:55:44 -04:00
db17739716 Update README.md 2024-09-23 07:48:14 -04:00
1439362a5e Merge pull request #4651 from oqtane/master
5.2.2 Release
2024-09-23 07:39:21 -04:00
65c1b04772 Merge pull request #4650 from oqtane/dev
5.2.2 Release
2024-09-23 07:39:02 -04:00
83d30ebdc4 Merge pull request #4645 from sbwalker/dev
fix #4638 - add Logout Everywhere option to User Profile
2024-09-20 15:18:38 -04:00
b7928a5255 fix #4638 - add Logout Everywhere option to User Profile 2024-09-20 15:18:25 -04:00
a83ff9253d Merge pull request #4644 from sbwalker/dev
fix code formatting
2024-09-20 12:54:44 -04:00
8cdcdaf6d9 fix code formatting 2024-09-20 12:54:34 -04:00
d208edf153 Merge pull request #4643 from sbwalker/dev
fix #4641 - highlight default site theme in list of themes in page management
2024-09-20 12:28:06 -04:00
8ada4765b1 fix #4641 - highlight default site theme in list of themes in page management 2024-09-20 12:27:49 -04:00
9d3a808c24 Merge pull request #4642 from sbwalker/dev
fix #4826 - improve url handling
2024-09-20 12:09:16 -04:00
828eb80266 fix #4826 - improve url handling 2024-09-20 12:09:04 -04:00
0639f6c1d1 Merge pull request #4640 from sbwalker/dev
fix #4628 - preserve Url for all ModuleActions
2024-09-19 14:09:55 -04:00
49b971280f fix #4628 - preserve Url for all ModuleActions 2024-09-19 14:09:45 -04:00
3682a1010d Merge pull request #4637 from sbwalker/dev
synchronize interop.js with .NET MAUI
2024-09-19 12:14:36 -04:00
f8b58866dc synchronize interop.js with .NET MAUI 2024-09-19 12:14:24 -04:00
5d8d815d84 Merge pull request #4636 from zyhfish/task/4610-loadjs-issue
Fix #4610: wait for js loaded.
2024-09-19 12:10:55 -04:00
Ben
67743c7597 Fix #4610: wait for js loaded. 2024-09-19 23:34:59 +08:00
b33cc90447 Merge pull request #4635 from sbwalker/dev
use deep cloning to not muttate cache
2024-09-19 09:41:24 -04:00
78177f7890 use deep cloning to not muttate cache 2024-09-19 09:41:11 -04:00
3c417bfa99 Merge pull request #4633 from sbwalker/dev
fix trimming of site, page, and module settings
2024-09-18 18:32:04 -04:00
f2c8d80ff8 fix trimming of site, page, and module settings 2024-09-18 18:31:40 -04:00
8c1eb1e19f Merge pull request #4632 from sbwalker/dev
fix #4257 - load settings resources
2024-09-18 15:22:58 -04:00
eb7188e81b fix #4257 - load settings resources 2024-09-18 15:22:44 -04:00
f352fc5c67 Merge pull request #4631 from sbwalker/dev
on .NET MAUI provide message to user if Security Token has not been configured
2024-09-18 11:28:30 -04:00
ac313722f9 on .NET MAUI provide message to user if Security Token has not been configured 2024-09-18 11:28:11 -04:00
64c7f1962c Merge pull request #4630 from zyhfish/task/sync-maui-resources
sync changes to maui resources.
2024-09-18 10:46:34 -04:00
Ben
c83f994b21 sync changes to maui resources. 2024-09-18 22:44:14 +08:00
10f38a72f7 Merge pull request #4629 from sbwalker/dev
fix #4628 - preserve ReturnUrl when navigating to Settings component
2024-09-18 09:43:11 -04:00
da35434f58 fix #4628 - preserve ReturnUrl when navigating to Settings component 2024-09-18 09:42:56 -04:00
c79409e094 Merge pull request #4627 from sbwalker/dev
use FileLogger as fallback in LogManager when site cannot be determined
2024-09-18 07:38:20 -04:00
355ce00968 use FileLogger as fallback in LogManager when site cannot be determined 2024-09-18 07:37:52 -04:00
56c832f3ba Merge pull request #4626 from sbwalker/dev
fix logic to force authenticated user to provide email address so it works in static rendering
2024-09-17 17:29:30 -04:00
40abb2720e fix logic to force authenticated user to provide email address so it works in static rendering 2024-09-17 17:29:11 -04:00
9b6051afee Merge pull request #4625 from sbwalker/dev
update nuget.exe to latest and fix nuspec specification of readme
2024-09-17 15:19:57 -04:00
dcf6f26792 update nuget.exe to latest and fix nuspec specification of readme 2024-09-17 15:19:40 -04:00
85734c1146 Merge pull request #4624 from sbwalker/dev
remove unecessary using
2024-09-17 11:54:33 -04:00
c3fb8fcb6e remove unecessary using 2024-09-17 11:54:21 -04:00
013bbc1638 Merge pull request #4623 from sbwalker/dev
fix external login
2024-09-17 11:53:47 -04:00
b0669a3b60 fix external login 2024-09-17 11:53:34 -04:00
6f5da1ce7c Merge pull request #4622 from sbwalker/dev
allow external login to support SecurityStamp
2024-09-17 09:28:47 -04:00
3351732a2f allow external login to support SecurityStamp 2024-09-17 09:28:35 -04:00
d4d4034ecd Merge pull request #4621 from sbwalker/dev
remove unecessary usings
2024-09-17 09:07:57 -04:00
b45e2742c3 remove unecessary usings 2024-09-17 09:07:45 -04:00
42eba290e4 Merge pull request #4620 from sbwalker/dev
allow JwtMiddleware to support SecurityStamp
2024-09-17 09:07:14 -04:00
32d1e08b57 allow JwtMiddleware to support SecurityStamp 2024-09-17 09:06:49 -04:00
7b7d19da7c Merge pull request #4619 from sbwalker/dev
fix commented logic
2024-09-17 08:55:09 -04:00
f78e400918 fix commented logic 2024-09-17 08:54:52 -04:00
be4e9bf7e9 Merge pull request #4618 from sbwalker/dev
fix #4580 - add logout everywhere support using SecurityStamp
2024-09-17 08:45:41 -04:00
48f2079f88 fix #4580 - add logout everywhere support using SecurityStamp 2024-09-17 08:45:27 -04:00
36ad1ceef2 Merge pull request #4617 from sbwalker/dev
fix #4607 - site level scripts added twice in static rendering
2024-09-16 13:22:10 -04:00
1f2e2148d5 fix #4607 - scripts added twice 2024-09-16 13:21:43 -04:00
76f3d345f9 Merge pull request #4616 from sbwalker/dev
include SecurityStamp in User object
2024-09-16 13:03:32 -04:00
f2c854b53a include SecurityStamp in User object 2024-09-16 13:03:21 -04:00
e2d336d90b Merge pull request #4613 from sbwalker/dev
further modifications for #4604 - support for site name in logo component
2024-09-15 09:30:24 -04:00
c74065ff26 further modifications for #4604 - support for site name in logo component 2024-09-15 09:30:04 -04:00
b6d97dc5d5 Merge pull request #4612 from sbwalker/dev
fix #4606 - allow logo to show site name as fallback (credit @JanOlsmar)
2024-09-13 16:13:14 -04:00
1c1c26948a fix #4606 - allow logo to show site name as fallback (credit @JanOlsmar) 2024-09-13 16:13:01 -04:00
d954e3ffb7 Merge pull request #4608 from hishamco/toggle-edit-mode
Fix issue in toggle edit mode
2024-09-13 15:54:37 -04:00
d9759a95eb Merge pull request #4611 from sbwalker/dev
fix #4607 - Site HeadContent scripts being added twice
2024-09-13 15:19:31 -04:00
d196402dd0 fix #4607 - Site HeadContent scripts being added twice 2024-09-13 15:18:12 -04:00
0f2b5531de Merge pull request #4601 from hishamco/module-settings
Add null operator in ModuleState.ModuleDefinition
2024-09-13 14:13:40 -04:00
26e4398905 Address feedback 2024-09-13 21:12:18 +03:00
06995f22fe Merge pull request #4609 from sbwalker/dev
improve support for external login roles
2024-09-13 07:35:10 -04:00
caa2073d48 improve support for external login roles 2024-09-13 07:34:57 -04:00
d9466fe4bc Fix issue in toggle edit mode 2024-09-13 14:15:34 +03:00
72e623a3a7 Merge pull request #4606 from sbwalker/dev
fix #4598 - user experience improvements for file upload
2024-09-12 14:04:48 -04:00
69bc06685f fix #4598 - user experience improvements for file upload 2024-09-12 14:04:35 -04:00
bf175984f3 Merge remote-tracking branch 'origin/dev' into dev 2024-09-11 18:23:41 -07:00
4d4a7bfd0d Merge pull request #4605 from sbwalker/dev
fix #4600 - filter user settings in API layer
2024-09-11 17:21:24 -04:00
044cee30a5 fix #4600 - filter user settings in API layer 2024-09-11 17:21:12 -04:00
3c0c5aed60 Add null operator in ModuleState.ModuleDefinition 2024-09-11 02:09:02 +03:00
e194971727 Merge pull request #4597 from sbwalker/dev
fix #4503 - ensure all state is initialized before rendering
2024-09-09 09:17:40 -04:00
bbe85def23 fix #4503 - ensure all state is initialized before rendering 2024-09-09 09:17:27 -04:00
9c2d53f2ae Merge pull request #4596 from sbwalker/dev
fix #4575 - add support for DateOnly and TimeOnly columns in migrations
2024-09-08 11:43:50 -04:00
1cf36e2156 fix #4575 - add support for DateOnly and TimeOnly columns in migrations 2024-09-08 11:43:36 -04:00
ce96e309af Merge pull request #4594 from sbwalker/dev
reverting #4507 - permission grid
2024-09-06 11:57:38 -04:00
9ea7dc8b3c reverting #4507 2024-09-06 11:57:15 -04:00
282242efd2 Merge pull request #4593 from mdmontesinos/dev
Fix IconOnly not working
2024-09-06 10:47:45 -04:00
351ba22d64 Fix IconOnly not working 2024-09-06 16:35:44 +02:00
2916625747 Merge pull request #4588 from thabaum/patch-8
Update MySQL Project File Version - Prepare for 5.2.2
2024-09-05 10:32:54 -04:00
744dbeb7a3 Prepare for 5.2.2 2024-09-04 17:43:20 -07:00
fbb3c309ee Merge pull request #4583 from sbwalker/dev
prepare for 5.2.2
2024-09-02 12:09:24 -04:00
473c265bac prepare for 5.2.2 2024-09-02 12:09:06 -04:00
01b06c2c3c Merge pull request #4582 from sbwalker/dev
remove reference to HttpContext as it is not used
2024-09-02 11:08:38 -04:00
8b1f95c743 remove reference to HttpContext as it is not used 2024-09-02 11:08:25 -04:00
ff5dbec579 Merge pull request #4578 from sbwalker/dev
fix issue adding existing user to a new site
2024-08-29 17:53:27 -04:00
9620c5a98f fix issue adding existing user to a new site 2024-08-29 17:53:11 -04:00
7e849a2e95 Merge pull request #4577 from zyhfish/task/update-bootstrap-reference 2024-08-29 09:35:32 -04:00
Ben
229aed306e update bootstrap reference. 2024-08-29 19:41:27 +08:00
d718969cbd Merge pull request #4574 from zyhfish/task/display-upgrade-progress
display the upgrade progress.
2024-08-28 21:22:08 -04:00
8593f4c3a9 Merge pull request #4576 from sbwalker/dev
improve developer experience for Url helper methods
2024-08-28 21:21:57 -04:00
d4f71d5026 improve developer experience for Url helper methods 2024-08-28 21:21:41 -04:00
a683a5f206 Merge pull request #4573 from leigh-pointer/AddExisitng
Fix for Deleted Modules showing in AddExisting & CopyExisting dropdow…
2024-08-28 21:17:54 -04:00
Ben
7917cc3eb5 display the upgrade progress. 2024-08-28 21:48:40 +08:00
6cd7ca755e Fix for Deleted Modules showing in AddExisting & CopyExisting dropdown #4572 2024-08-28 10:57:42 +02:00
fb7dfdc800 Merge pull request #4571 from sbwalker/dev
provide better support for AllowTextInput on Search component
2024-08-27 12:16:50 -04:00
e096af320f provide better support for AllowTextInput on Search component 2024-08-27 12:16:36 -04:00
65bff8f511 Merge pull request #4570 from sbwalker/dev
allow progress indicator to be displayed in search results
2024-08-27 10:22:53 -04:00
ca19d8a842 allow progress indicator to be displayed in search results 2024-08-27 10:22:41 -04:00
cde08c64ce Update README.md 2024-08-26 17:00:07 -04:00
3d6d7b7cb9 Update README.md 2024-08-26 16:56:08 -04:00
a56ef6f398 Update README.md 2024-08-26 16:55:30 -04:00
5a38b6614d Update README.md 2024-08-26 16:54:45 -04:00
3d5c44e8aa Update README.md 2024-08-26 16:53:25 -04:00
46a68bd5e7 Update README.md 2024-08-26 16:51:53 -04:00
c77ded51a9 Merge pull request #4568 from sbwalker/dev
fix #4559 - prevednt Log fields from exceeding column length
2024-08-26 12:05:05 -04:00
59bd9fdc22 fix #4559 - prevednt Log fields from exceeding column length 2024-08-26 12:04:50 -04:00
b81941394c Merge pull request #4567 from sbwalker/dev
change parameter name to AllowTextInput for clarity
2024-08-26 10:47:29 -04:00
197d5ca1f2 change parameter name to AllowTextInput for clarity 2024-08-26 10:47:18 -04:00
97eb986fa6 Merge pull request #4566 from sbwalker/dev
fix Search theme control so that it checks if search is enabled for site, and include AllowInput parameter to control input textbox
2024-08-26 10:41:39 -04:00
aba3110e31 fix Search theme control so that it checks if search is enabled for site, and include AllowInput parameter to control input textbox 2024-08-26 10:41:22 -04:00
51c27be236 Merge pull request #4565 from sbwalker/dev
fix #4562 - module template issue caused by gitignore
2024-08-26 09:41:01 -04:00
592255284f fix #4562 - module template issue caused by gitignore 2024-08-26 09:38:10 -04:00
82fd41dd4c Merge pull request #4555 from sbwalker/dev
add readme.md to Oqtane nuget packages
2024-08-22 14:10:42 -04:00
4ae8df9652 add readme.md to Oqtane nuget packages 2024-08-22 14:09:59 -04:00
759b19e444 Update README.md 2024-08-22 12:23:21 -04:00
36f705e46f Update README.md 2024-08-22 12:23:09 -04:00
4b17847ea5 Merge remote-tracking branch 'oqtane/dev' into dev 2024-07-05 21:53:14 -07:00
2addcc3ab5 Merge branch 'dev' of https://github.com/Trifoia/oqtane.framework into dev 2024-03-28 13:47:43 -07:00
370b39a139 Merge branch 'release/v5.0.1' into dev 2024-01-27 21:18:58 -08:00
7b7e64576f Update appsettings.json 2024-01-27 20:30:20 -08:00
286928d59e Merge tag 'v5.0.1' into dev 2023-12-29 11:54:39 -08:00
cc65555c3d minor fix to routing 2023-12-05 12:17:02 -08:00
63e3923349 Merge remote-tracking branch 'oqtane/dev' into dev 2023-11-17 12:55:40 -08:00
195 changed files with 4235 additions and 2084 deletions

11
.gitignore vendored
View File

@ -22,10 +22,17 @@ Oqtane.Server/Packages
Oqtane.Server/wwwroot/Content
Oqtane.Server/wwwroot/Packages/*.log
Oqtane.Server/wwwroot/Modules
Oqtane.Server/wwwroot/_content/*
!Oqtane.Server/wwwroot/_content/Placeholder.txt
Oqtane.Server/wwwroot/Modules/*
!Oqtane.Server/wwwroot/Modules/Oqtane.Modules.*
!Oqtane.Server/wwwroot/Modules/Templates
Oqtane.Server/wwwroot/Modules/Templates/*
!Oqtane.Server/wwwroot/Modules/Templates/External
Oqtane.Server/wwwroot/Themes
Oqtane.Server/wwwroot/Themes/*
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
!Oqtane.Server/wwwroot/Themes/Templates
Oqtane.Server/wwwroot/Themes/Templates/*
Oqtane.Server/wwwroot/Themes/Templates/External

24
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,24 @@
# Contributing to Oqtane
## How to Contribute
We track all of our issues on Github. If you want to contribute, everything starts with an issue. If you don't have an issue yet, you can add one. Then a core contributor will tag it as either an enhancement [ENH] or a bug [BUG]. Tagged issues are open for contribution.
## Use GitHub-flow process
- Make a comment on the issue that you intend to work on it and read all the comments to gain a full understanding.
- Fork the repository
- Create a new branch and update your comment on the issue with a llink to the branch
- Make your changes and commit them
- Push to the branch
- Create a pull request
## Reporting Bugs
- Check if the issue has already been reported.
- Open a new issue if it hasnt been reported.
## Requesting Features
- Use the feature request template in the Issues tab.
Thank you for contributing!

View File

@ -51,6 +51,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<IUrlMappingService, UrlMappingService>();
services.AddScoped<IVisitorService, VisitorService>();
services.AddScoped<ISyncService, SyncService>();
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
// providers
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();

View File

@ -15,7 +15,7 @@
<div class="row">
<div class="mx-auto text-center">
<img src="oqtane-black.png" />
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 8)</div>
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 9)</div>
</div>
</div>
<hr class="app-rule" />
@ -156,129 +156,130 @@
private List<SiteTemplate> _templates;
private string _template = Constants.DefaultSiteTemplate;
private bool _register = true;
private string _message = string.Empty;
private string _loadingDisplay = "display: none;";
private string _message = string.Empty;
private string _loadingDisplay = "display: none;";
protected override async Task OnInitializedAsync()
{
protected override async Task OnInitializedAsync()
{
// include CSS
var content = "<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css\" integrity=\"sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==\" crossorigin=\"anonymous\" type=\"text/css\"/>";
var content = $"<link rel=\"stylesheet\" href=\"{Constants.BootstrapStylesheetUrl}\" integrity=\"{Constants.BootstrapStylesheetIntegrity}\" crossorigin=\"anonymous\" type=\"text/css\"/>";
SiteState.AppendHeadContent(content);
_togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
{
_databaseName = _databases.Find(item => item.IsDefault).Name;
}
else
{
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
{
_databaseName = _databases.Find(item => item.IsDefault).Name;
}
else
{
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
_templates = await SiteTemplateService.GetSiteTemplatesAsync();
}
private void DatabaseChanged(ChangeEventArgs eventArgs)
{
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
{
_message = Localizer["Error.DbConfig.Load"];
}
}
private void DatabaseChanged(ChangeEventArgs eventArgs)
{
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
{
_message = Localizer["Error.DbConfig.Load"];
}
}
private void LoadDatabaseConfigComponent()
{
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (database != null)
{
_databaseConfigType = Type.GetType(database.ControlType);
DatabaseConfigComponent = builder =>
{
builder.OpenComponent(0, _databaseConfigType);
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
builder.CloseComponent();
};
}
}
private void LoadDatabaseConfigComponent()
{
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (database != null)
{
_databaseConfigType = Type.GetType(database.ControlType);
DatabaseConfigComponent = builder =>
{
builder.OpenComponent(0, _databaseConfigType);
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
builder.CloseComponent();
};
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// include JavaScript
var interop = new Interop(JSRuntime);
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", "anonymous", "", "head");
}
}
var interop = new Interop(JSRuntime);
await interop.IncludeScript("", Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous", "", "head");
}
}
private async Task Install()
{
var connectionString = String.Empty;
if (_showConnectionString)
{
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
}
private async Task Install()
{
var connectionString = String.Empty;
if (_showConnectionString)
{
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
}
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
{
if (await UserService.ValidatePasswordAsync(_hostPassword))
{
_loadingDisplay = "";
StateHasChanged();
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
{
var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
if (result.Succeeded)
{
_loadingDisplay = "";
StateHasChanged();
Uri uri = new Uri(NavigationManager.Uri);
Uri uri = new Uri(NavigationManager.Uri);
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var config = new InstallConfig
{
DatabaseType = database.DBType,
ConnectionString = connectionString,
Aliases = uri.Authority,
HostUsername = _hostUsername,
HostPassword = _hostPassword,
HostEmail = _hostEmail,
HostName = _hostUsername,
TenantName = TenantNames.Master,
IsNewTenant = true,
SiteName = Constants.DefaultSite,
Register = _register,
SiteTemplate = _template,
RenderMode = RenderModes.Static,
Runtime = Runtimes.Server
};
var config = new InstallConfig
{
DatabaseType = database.DBType,
ConnectionString = connectionString,
Aliases = uri.Authority,
HostUsername = _hostUsername,
HostPassword = _hostPassword,
HostEmail = _hostEmail,
HostName = _hostUsername,
TenantName = TenantNames.Master,
IsNewTenant = true,
SiteName = Constants.DefaultSite,
Register = _register,
SiteTemplate = _template,
RenderMode = RenderModes.Static,
Runtime = Runtimes.Server
};
var installation = await InstallationService.Install(config);
if (installation.Success)
{
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
}
else
{
_message = installation.Message;
_loadingDisplay = "display: none;";
}
}
else
{
_message = Localizer["Message.Password.Invalid"];
var installation = await InstallationService.Install(config);
if (installation.Success)
{
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
}
else
{
_message = installation.Message;
_loadingDisplay = "display: none;";
}
}
else
{
_message = string.Join("<br />", result.Errors.Select(i => !string.IsNullOrEmpty(i.Value) ? i.Value : Localizer[i.Key]));
}
}
else

View File

@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Admin.Languages
@inherits ModuleBase
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@inject NavigationManager NavigationManager
@inject ILocalizationService LocalizationService
@inject ILanguageService LanguageService
@ -94,7 +93,6 @@ else
var language = new Language
{
SiteId = PageState.Page.SiteId,
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
Code = _code,
IsDefault = (_default == null ? false : Boolean.Parse(_default))
};
@ -130,7 +128,7 @@ else
{
var interop = new Interop(JSRuntime);
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
}
}

View File

@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Admin.Languages
@inherits ModuleBase
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@inject NavigationManager NavigationManager
@inject ILocalizationService LocalizationService
@inject ILanguageService LanguageService
@ -103,7 +102,7 @@ else
{
var interop = new Interop(JSRuntime);
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
}
}

View File

@ -8,74 +8,77 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<AuthorizeView Roles="@RoleNames.Registered">
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized>
@if (!twofactor)
{
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
@if (_allowexternallogin)
{
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
<br /><br />
}
@if (_allowsitelogin)
{
<div class="form-group">
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required />
</div>
<div class="form-group mt-2">
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
<div class="input-group">
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" required />
@if (PageState.User != null)
{
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
}
else
{
@if (!twofactor)
{
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
@if (_allowexternallogin)
{
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
<br />
<br />
}
@if (_allowsitelogin)
{
<div class="form-group">
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required />
</div>
<div class="form-group mt-2">
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
<div class="input-group">
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
<div class="form-group mt-2">
@if (!_alwaysremember)
{
<div class="form-check">
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
</div>
}
</div>
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<br /><br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
@if (PageState.Site.AllowRegistration)
</div>
</div>
<div class="form-group mt-2">
@if (!_alwaysremember)
{
<br /><br />
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
<div class="form-check">
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
</div>
}
</div>
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<br />
<br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
@if (PageState.Site.AllowRegistration)
{
<br />
<br />
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
}
</div>
</form>
}
else
{
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container Oqtane-Modules-Admin-Login">
<div class="form-group">
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
</div>
<br />
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
</div>
</form>
}
</NotAuthorized>
</AuthorizeView>
}
</div>
</form>
}
else
{
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container Oqtane-Modules-Admin-Login">
<div class="form-group">
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
</div>
<br />
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
</div>
</form>
}
}
@code {
private bool _allowsitelogin = true;
@ -204,9 +207,9 @@
user = await UserService.VerifyTwoFactorAsync(user, _code);
}
if (user.IsAuthenticated)
if (user != null && user.IsAuthenticated)
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
// return url is not specified if user navigated directly to login page
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
@ -228,7 +231,7 @@
}
else
{
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired)
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
{
twofactor = true;
validated = false;
@ -239,12 +242,12 @@
if (!twofactor)
{
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
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);
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
}
}
}

View File

@ -27,7 +27,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
<div class="col-sm-9">
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000" required></textarea>
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
@ -118,6 +118,7 @@
{
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
{
if (string.IsNullOrEmpty(_description)) _description = _module;
if (IsValidXML(_description))
{
var template = _templates.FirstOrDefault(item => item.Name == _template);

View File

@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Admin.ModuleDefinitions
@inherits ModuleBase
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
@inject ILanguageService LanguageService

View File

@ -15,7 +15,7 @@
</div>
<button type="button" class="btn btn-success" @onclick="ExportModule">@Localizer["Export"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _content = string.Empty;

View File

@ -17,7 +17,7 @@
</div>
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
</form>
@code {

View File

@ -9,129 +9,133 @@
@inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
@if (_containers != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="module" HelpText="The name of the module" ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<input id="module" type="text" class="form-control" @bind="@_module" disabled />
@if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip ActiveTab="@_activetab">
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
@if (_containers != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="module" HelpText="The name of the module" ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<input id="module" type="text" class="form-control" @bind="@_module" disabled />
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" type="text" class="form-control" @bind="@_title" required />
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" type="text" class="form-control" @bind="@_title" required />
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pane" HelpText="The pane where the module will be displayed" ResourceKey="Pane">Pane: </Label>
<div class="col-sm-9">
<select class="form-select" @bind="@_pane">
@foreach (string pane in PageState.Page.Panes)
{
<option value="@pane">@pane Pane</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containerType" required>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this module is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this module expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
<div class="col-sm-9">
<select id="allpages" class="form-select" @bind="@_allPages" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
<div class="col-sm-9">
<select id="page" class="form-select" @bind="@_pageId" required>
@if (PageState.Page.UserId != null)
{
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
}
else
{
if (_pages != null)
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pane" HelpText="The pane where the module will be displayed" ResourceKey="Pane">Pane: </Label>
<div class="col-sm-9">
<select class="form-select" @bind="@_pane">
@foreach (string pane in PageState.Page.Panes)
{
foreach (Page p in _pages)
<option value="@pane">@pane Pane</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containerType" required>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this module is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this module expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
<div class="col-sm-9">
<select id="allpages" class="form-select" @bind="@_allPages" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
<div class="col-sm-9">
<select id="page" class="form-select" @bind="@_pageId" required>
@if (PageState.Page.UserId != null)
{
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
}
else
{
if (_pages != null)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
foreach (Page p in _pages)
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
}
}
}
}
}
</select>
</select>
</div>
</div>
</div>
</div>
}
</TabPanel>
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" PermissionList="@_permissions" @ref="_permissionGrid" />
}
</TabPanel>
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" PermissionList="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</div>
}
</TabPanel>
@if (_moduleSettingsType != null)
{
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
@ModuleSettingsComponent
</TabPanel>
}
</TabPanel>
@if (_moduleSettingsType != null)
{
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
@ModuleSettingsComponent
</TabPanel>
}
@if (_containerSettingsType != null)
{
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
@ContainerSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
</form>
@if (_containerSettingsType != null)
{
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
@ContainerSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
</form>
}
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private List<ThemeControl> _containers = new List<ThemeControl>();
@ -158,12 +162,12 @@
private DateTime? _effectivedate = null;
private DateTime? _expirydate = null;
private List<Page> _pages;
private string _activetab = "";
protected override async Task OnInitializedAsync()
{
SetModuleTitle(Localizer["ModuleSettings.Title"]);
_module = ModuleState.ModuleDefinition.Name;
_title = ModuleState.Title;
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
_pane = ModuleState.Pane;
@ -182,6 +186,7 @@
if (ModuleState.ModuleDefinition != null)
{
_module = ModuleState.ModuleDefinition.Name;
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
@ -231,10 +236,13 @@
};
}
}
_initialized = true;
}
private async Task SaveModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
@ -255,21 +263,21 @@
pagemodule.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
{
pagemodule.ContainerType = string.Empty;
}
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.PageModuleId = ModuleState.PageModuleId;
module.PermissionList = _permissionGrid.GetPermissionList();
await ModuleService.UpdateModuleAsync(module);
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.PageModuleId = ModuleState.PageModuleId;
module.PermissionList = _permissionGrid.GetPermissionList();
await ModuleService.UpdateModuleAsync(module);
if (_moduleSettingsType != null)
{
@ -294,11 +302,13 @@
}
else
{
_activetab = "Settings";
AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
}
}
else
{
_activetab = "Settings";
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}

View File

@ -156,7 +156,14 @@
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
@if (theme.TypeName == PageState.Site.DefaultThemeType)
{
<option value="@theme.TypeName">*@theme.Name*</option>
}
else
{
<option value="@theme.TypeName">@theme.Name</option>
}
}
</select>
</div>
@ -309,10 +316,11 @@
{
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
await ScrollToPageTop();
}
}
private void ThemeChanged(ChangeEventArgs e)
private async Task ThemeChanged(ChangeEventArgs e)
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
@ -323,6 +331,7 @@
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
await ScrollToPageTop();
}
}
@ -338,6 +347,7 @@
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
{
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
await ScrollToPageTop();
return;
}
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
@ -388,12 +398,14 @@
if (_pages.Any(item => item.Path == page.Path))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
await ScrollToPageTop();
return;
}
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
await ScrollToPageTop();
return;
}
@ -461,6 +473,7 @@
else
{
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
await ScrollToPageTop();
}
}
@ -468,11 +481,13 @@
{
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
await ScrollToPageTop();
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
await ScrollToPageTop();
}
}

View File

@ -172,7 +172,14 @@
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
@if (theme.TypeName == PageState.Site.DefaultThemeType)
{
<option value="@theme.TypeName">*@theme.Name*</option>
}
else
{
<option value="@theme.TypeName">@theme.Name</option>
}
}
</select>
</div>
@ -262,7 +269,14 @@
<select id="theme" class="form-select" @bind="@_themetype" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
@if (theme.TypeName == PageState.Site.DefaultThemeType)
{
<option value="@theme.TypeName">*@theme.Name*</option>
}
else
{
<option value="@theme.TypeName">@theme.Name</option>
}
}
</select>
</div>
@ -452,7 +466,7 @@
_parentid = (string)e.Value;
_children = new List<Page>();
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{
{
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
@ -465,10 +479,11 @@
{
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
await ScrollToPageTop();
}
}
private void ThemeChanged(ChangeEventArgs e)
private async Task ThemeChanged(ChangeEventArgs e)
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
@ -480,6 +495,7 @@
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
await ScrollToPageTop();
}
}
@ -517,6 +533,7 @@
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
{
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
await ScrollToPageTop();
return;
}
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
@ -567,12 +584,14 @@
if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
{
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
await ScrollToPageTop();
return;
}
if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning);
await ScrollToPageTop();
return;
}
@ -657,17 +676,20 @@
else
{
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
await ScrollToPageTop();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
await ScrollToPageTop();
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
await ScrollToPageTop();
}
}

View File

@ -22,7 +22,7 @@ else
}
else
{
<Pager Items="@_pages.Where(item => item.IsDeleted)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
@ -50,7 +50,7 @@ else
}
else
{
<Pager Items="@_modules.Where(item => item.IsDeleted)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>

View File

@ -11,65 +11,64 @@
{
if (!_userCreated)
{
<AuthorizeView Roles="@RoleNames.Registered">
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized>
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
</div>
if (PageState.User != null)
{
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
}
else
{
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
@if (_allowsitelogin)
{
<br />
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
@if (_allowsitelogin)
{
<br /><br />
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
}
</form>
</NotAuthorized>
</AuthorizeView>
<br />
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
}
</form>
}
}
}
else

View File

@ -63,7 +63,7 @@
private string _enabled = "True";
private string _lastIndexedOn = "";
private string _ignorePages = "";
private string _ignoreEntities = "";
private string _ignoreEntities = "File";
private string _minimumWordLength = "3";
private string _ignoreWords = "the,be,to,of,and,a,i,in,that,have,it,for,not,on,with,he,as,you,do,at,this,but,his,by,from,they,we,say,her,she,or,an,will,my,one,all,would,there,their,what,so,up,out,if,about,who,get,which,go,me,when,make,can,like,time,no,just,him,know,take,people,into,year,your,good,some,could,them,see,other,than,then,now,look,only,come,its,over,think,also,back,after,use,two,how,our,work,first,well,way,even,new,want,because,any,these,give,day,most,us";
@ -85,7 +85,7 @@
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "Search_SearchProvider", _searchProvider);
settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled, true);
settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled);
settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true);
settings = SettingService.SetSetting(settings, "Search_IgnorePages", _ignorePages, true);
settings = SettingService.SetSetting(settings, "Search_IgnoreEntities", _ignoreEntities, true);
@ -106,9 +106,7 @@
try
{
_lastIndexedOn = DateTime.MinValue.ToString();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await Save();
AddModuleMessage(Localizer["Message.Reindex"], MessageType.Success);
}
catch (Exception ex)

View File

@ -7,6 +7,7 @@
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@attribute [StreamRendering] // attribute allows the progress indicator to be displayed
<div class="search-result-container">
<div class="row">

View File

@ -32,32 +32,32 @@
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (Page page in _pages)
{
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing" ResourceKey="SiteMap">Site Map: </Label>
<div class="col-sm-9">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing" ResourceKey="SiteMap">Site Map: </Label>
<div class="col-sm-9">
<div class="input-group">
<input id="sitemap" class="form-control" @bind="@_sitemap" disabled />
<a href="@_sitemap" class="btn btn-secondary" target="_new">@Localizer["Browse"]</a>
</div>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="siteguid" HelpText="The Unique Identifier For The Site" ResourceKey="SiteGuid">ID: </Label>
<div class="col-sm-9">
@ -159,8 +159,8 @@
</div>
</Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
<div class="container">
<div class="row mb-1 align-items-center">
<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>
<div class="col-sm-9">
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
@ -207,16 +207,16 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_smtpusername" />
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off"/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" />
<div class="input-group">
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off"/>
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
</div>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
@ -225,15 +225,15 @@
<input id="sender" class="form-control" @bind="@_smtpsender" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above." ResourceKey="SmtpRelay">Relay Configured? </Label>
<div class="col-sm-9">
<select id="relay" class="form-select" @bind="@_smtprelay" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above." ResourceKey="SmtpRelay">Relay Configured? </Label>
<div class="col-sm-9">
<select id="relay" class="form-select" @bind="@_smtprelay" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SMTPEnabled">Enabled? </Label>
<div class="col-sm-9">
@ -244,10 +244,10 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
</div>
</div>
</div>
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
<br /><br />
@ -280,57 +280,57 @@
</Section>
@if (_aliases != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliases" HelpText="The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
<Pager Items="@_aliases">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["AliasName"]</th>
<th>@Localizer["AliasDefault"]</th>
</Header>
<Row>
@if (context.AliasId != _aliasid)
{
<td>
@if (_aliasid == -1)
{
<button type="button" class="btn btn-primary" @onclick="@(() => EditAlias(context))">@SharedLocalizer["Edit"]</button>
}
</td>
<td>
@if (_aliasid == -1)
{
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
}
</td>
<td>@context.Name</td>
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliases" HelpText="The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
<Pager Items="@_aliases">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["AliasName"]</th>
<th>@Localizer["AliasDefault"]</th>
</Header>
<Row>
@if (context.AliasId != _aliasid)
{
<td>
@if (_aliasid == -1)
{
<button type="button" class="btn btn-primary" @onclick="@(() => EditAlias(context))">@SharedLocalizer["Edit"]</button>
}
</td>
<td>
@if (_aliasid == -1)
{
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
}
</td>
<td>@context.Name</td>
<td>@((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"])</td>
}
else
{
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SaveAlias())">@SharedLocalizer["Save"]</button></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelAlias())">@SharedLocalizer["Cancel"]</button></td>
<td>
<input id="aliasname" class="form-control" @bind="@_aliasname" />
</td>
<td>
<select id="defaultalias" class="form-select" @bind="@_defaultalias" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
}
</Row>
</Pager>
</div>
</div>
</div>
</Section>
}
else
{
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SaveAlias())">@SharedLocalizer["Save"]</button></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelAlias())">@SharedLocalizer["Cancel"]</button></td>
<td>
<input id="aliasname" class="form-control" @bind="@_aliasname" />
</td>
<td>
<select id="defaultalias" class="form-select" @bind="@_defaultalias" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
}
</Row>
</Pager>
</div>
</div>
</div>
</Section>
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
<div class="container">
<div class="row mb-1 align-items-center">
@ -376,7 +376,7 @@
<Section Name="TenantInformation" Heading="Database" ResourceKey="TenantInformation">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="The name of the database used for the site" ResourceKey="Tenant">Database: </Label>
<Label Class="col-sm-3" For="tenant" HelpText="The name of the database used for the site. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database." ResourceKey="Tenant">Database: </Label>
<div class="col-sm-9">
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</div>
@ -388,9 +388,9 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
<Label Class="col-sm-3" For="connectionstring" HelpText="The name of the connection string in appsettings.json which will be used to connect to the database" ResourceKey="ConnectionString">Connection: </Label>
<div class="col-sm-9">
<input id="connectionstring" class="form-control" @bind="@_connectionstring" readonly />
<input id="connectionstring" class="form-control" @bind="@_connectionstring" readonly />
</div>
</div>
</div>
@ -481,6 +481,11 @@
{
try
{
if (PageState.QueryString.ContainsKey("updated"))
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
}
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
@ -571,7 +576,7 @@
if (tenant != null)
{
_tenant = tenant.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType && item.Name != "LocalDB")?.Name;
_connectionstring = tenant.DBConnectionString;
}
}
@ -736,7 +741,7 @@
await logger.LogInformation("Site Settings Saved {Site}", site);
NavigationManager.NavigateTo(NavigateUrl(), true); // reload
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "updated=true"), true); // reload
}
}
else

View File

@ -109,7 +109,7 @@ else
<hr class="app-rule" />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the database" ResourceKey="TenantName">Name: </Label>
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the database. Note that this will be the tenant name which is used within the framework to identify the database." ResourceKey="TenantName">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
</div>

View File

@ -83,24 +83,15 @@ else
{
@if (_connection != "-")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
<div class="col-sm-9">
@if (_databases != null)
{
<select id="databasetype" class="form-select" @bind="@_databasetype" required>
<option value="-">&lt;@Localizer["Type.Select"]&gt;</option>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
}
</div>
</div>
@if (!string.IsNullOrEmpty(_tenant))
{
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
<div class="col-sm-9">
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="The database using this connection" ResourceKey="Tenant">Database: </Label>
<div class="col-sm-9">
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
@ -204,12 +195,12 @@ else
{
_connectionstring = _connections[_connection].ToString();
_tenant = "";
_databasetype = "-";
_databasetype = "";
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
if (tenant != null)
{
_tenant = tenant.Name;
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType).Name;
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name;
}
}
else

View File

@ -54,6 +54,8 @@
}
else
{
AddModuleMessage(Localizer["Disclaimer.Text"], MessageType.Warning);
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
if (packages != null)
{
@ -97,13 +99,16 @@
{
try
{
ShowProgressIndicator();
await PackageService.DownloadPackageAsync(packageid, version);
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
HideProgressIndicator();
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
HideProgressIndicator();
AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
}
}

View File

@ -9,6 +9,8 @@
@inject INotificationService NotificationService
@inject IFileService FileService
@inject IFolderService FolderService
@inject IJSRuntime jsRuntime
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -84,6 +86,7 @@
<br />
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
<div class="container">
@ -102,7 +105,7 @@
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9">
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
{
@if (!string.IsNullOrEmpty(p.Autocomplete))
@ -146,22 +149,26 @@
{
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
}
else
{
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
}
}
@ -171,22 +178,26 @@
{
@if (p.IsRequired)
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
}
else
{
@if (p.IsRequired)
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
}
}
@ -215,11 +226,11 @@
{
<Pager Items="@notifications">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["From"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Received"]</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["From"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Received"]</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
@ -227,13 +238,13 @@
@if (context.IsRead)
{
<td>@context.FromDisplayName</td>
<td>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
}
else
{
<td><b>@context.FromDisplayName</b></td>
<td><b>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</b></td>
<td><b>@context.Subject</b></td>
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
}
@ -278,11 +289,11 @@
{
<Pager Items="@notifications">
<Header>
<th style="width: 1px;"></th>
<th style="width: 1px;"></th>
<th>@Localizer["To"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Sent"]</th>
<th style="width: 1px;"></th>
<th style="width: 1px;"></th>
<th>@Localizer["To"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Sent"]</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
@ -360,7 +371,7 @@
private File photo = null;
private string _ImageFiles = string.Empty;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private Dictionary<string, string> userSettings;
private string category = string.Empty;
private string filter = "to";
@ -408,9 +419,9 @@
photo = null;
}
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
userSettings = PageState.User.Settings;
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
_ImageFiles = SettingService.GetSetting(userSettings, "ImageFiles", Constants.ImageFiles);
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
await LoadNotificationsAsync();
@ -437,7 +448,7 @@
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
@ -480,7 +491,7 @@
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
@ -518,6 +529,32 @@
}
}
private async Task Logout()
{
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
var url = NavigateUrl(""); // home page
if (PageState.Runtime == Shared.Runtime.Hybrid)
{
if (PageState.User != null)
{
// hybrid apps utilize an interactive logout
await UserService.LogoutUserEverywhereAsync(PageState.User);
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(url, true);
}
}
else
{
// post to the Logout page to complete the logout process
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url, everywhere = true };
var interop = new Interop(jsRuntime);
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
}
}
private bool ValidateProfiles()
{
foreach (Profile profile in profiles)
@ -525,7 +562,7 @@
var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue);
}
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
@ -557,7 +594,7 @@
private void ProfileChanged(ChangeEventArgs e, string SettingName)
{
var value = (string)e.Value;
settings = SettingService.SetSetting(settings, SettingName, value);
userSettings = SettingService.SetSetting(userSettings, SettingName, value);
}
private async Task Delete(Notification Notification)

View File

@ -128,7 +128,7 @@
createdon = notification.CreatedOn.ToString();
body = notification.Body;
if (title == "From")
if (title == "From" && !notification.IsRead)
{
notification.IsRead = true;
notification = await NotificationService.UpdateNotificationAsync(notification);

View File

@ -14,7 +14,6 @@
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null)
{
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
@ -22,24 +21,6 @@
<input id="username" class="form-control" @bind="@_username" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
@ -100,11 +81,11 @@
{
@if (p.Rows == 1)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
}
</div>
@ -123,12 +104,7 @@
@code {
private bool _initialized = false;
private string _passwordrequirements;
private string _username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
private string _email = string.Empty;
private string _displayname = string.Empty;
private string _notify = "True";
@ -142,8 +118,6 @@
{
try
{
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = new Dictionary<string, string>();
_initialized = true;
@ -169,39 +143,32 @@
{
try
{
if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty && _email != string.Empty)
if (_username != string.Empty && _email != string.Empty)
{
if (_password == _confirm)
if (ValidateProfiles())
{
if (ValidateProfiles())
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
user.Password = ""; // will be auto generated
user.Email = _email;
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
user.PhotoFileId = null;
user.SuppressNotification = !bool.Parse(_notify);
user = await UserService.AddUserAsync(user);
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.SuppressNotification = !bool.Parse(_notify);
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
{
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
}
}
else
@ -252,18 +219,4 @@
var value = (string)e.Value;
settings = SettingService.SetSetting(settings, SettingName, value);
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -89,8 +89,8 @@
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
{
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
@ -110,11 +110,11 @@
{
@if (p.Rows == 1)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
}
</div>
@ -132,7 +132,7 @@
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
}
@code {
@code {
private bool _initialized = false;
private string _passwordrequirements;
private int userid;
@ -148,7 +148,7 @@
private string lastipaddress;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private Dictionary<string, string> userSettings;
private string category = string.Empty;
private string createdby;
@ -181,7 +181,7 @@
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId);
userSettings = user.Settings;
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
@ -202,7 +202,7 @@
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
@ -232,7 +232,7 @@
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
@ -266,7 +266,7 @@
var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue);
}
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
@ -293,7 +293,7 @@
private void ProfileChanged(ChangeEventArgs e, string SettingName)
{
var value = (string)e.Value;
settings = SettingService.SetSetting(settings, SettingName, value);
userSettings = SettingService.SetSetting(userSettings, SettingName, value);
}
private void TogglePassword()

View File

@ -182,13 +182,31 @@ else
</div>
</Section>
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label>
<div class="col-sm-9">
<div class="input-group">
<select id="provider" class="form-select" value="@_provider" @onchange="(e => ProviderChanged(e))">
@foreach (var provider in Shared.ExternalLoginProviders.Providers)
{
<option value="@provider.Name">@Localizer[provider.Name]</option>
}
</select>
@if (!string.IsNullOrEmpty(_providerurl))
{
<a href="@_providerurl" class="btn btn-secondary" target="_new">@Localizer["Info"]</a>
}
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
<div class="col-sm-9">
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
<option value="" selected>@Localizer["Not Specified"]</option>
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OpenID Connect"]</option>
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth 2.0"]</option>
<option value="" selected>&lt;@Localizer["Not Specified"]&gt;</option>
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
</select>
</div>
</div>
@ -333,12 +351,29 @@ else
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the role claim provided by the provider" ResourceKey="RoleClaimType">Role Claim:</Label>
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the roles claim provided by the provider" ResourceKey="RoleClaimType">Roles Claim:</Label>
<div class="col-sm-9">
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="roleclaimmappings" HelpText="Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles." ResourceKey="RoleClaimMappings">Role Claim Mappings:</Label>
<div class="col-sm-9">
<input id="roleclaimmappings" class="form-control" @bind="@_roleclaimmappings" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="synchronizeroles" HelpText="This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider" ResourceKey="SynchronizeRoles">Synchronize Roles?</Label>
<div class="col-sm-9">
<div class="input-group">
<select id="synchronizeroles" class="form-select" @bind="@_synchronizeroles" required>
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
<div class="col-sm-9">
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
@ -435,6 +470,8 @@ else
private string _maximumfailures;
private string _lockoutduration;
private string _provider;
private string _providerurl;
private string _providertype;
private string _providername;
private string _authority;
@ -457,6 +494,8 @@ else
private string _nameclaimtype;
private string _emailclaimtype;
private string _roleclaimtype;
private string _roleclaimmappings;
private string _synchronizeroles;
private string _profileclaimtypes;
private string _domainfilter;
private string _createusers;
@ -500,31 +539,7 @@ else
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = SharedLocalizer["ShowPassword"];
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false");
_externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
LoadExternalLoginSettings(settings);
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
_togglesecret = SharedLocalizer["ShowPassword"];
@ -534,6 +549,39 @@ else
}
}
private void LoadExternalLoginSettings(Dictionary<string, string> settings)
{
_provider = SettingService.GetSetting(settings, "ExternalLogin:Provider", "<Custom>");
_providerurl = SettingService.GetSetting(settings, "ExternalLogin:ProviderUrl", "");
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = SharedLocalizer["ShowPassword"];
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false");
_externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
}
private async Task LoadUsersAsync(bool load)
{
if (load)
@ -546,103 +594,121 @@ else
users = users.OrderBy(u => u.User.DisplayName).ToList();
}
}
}
}
private async Task DeleteUser(UserRole UserRole)
{
try
{
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
if (user != null)
{
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
await logger.LogInformation("User Deleted {User}", UserRole.User);
await LoadUsersAsync(true);
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async Task DeleteUser(UserRole UserRole)
{
try
{
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
if (user != null)
{
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
await logger.LogInformation("User Deleted {User}", UserRole.User);
await LoadUsersAsync(true);
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async Task SaveSiteSettings()
{
try
{
var site = PageState.Site;
site.AllowRegistration = bool.Parse(_allowregistration);
await SiteService.UpdateSiteAsync(site);
private async Task SaveSiteSettings()
{
try
{
var site = PageState.Site;
site.AllowRegistration = bool.Parse(_allowregistration);
await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Provider", _provider, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ReviewClaims", _reviewclaims, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:NameClaimType", _nameclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
}
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
}
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await SettingService.ClearSiteSettingsCacheAsync();
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await SettingService.ClearSiteSettingsCacheAsync();
if (!string.IsNullOrEmpty(_secret))
{
SiteState.AuthorizationToken = await UserService.GetTokenAsync();
}
if (!string.IsNullOrEmpty(_secret))
{
SiteState.AuthorizationToken = await UserService.GetTokenAsync();
}
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
}
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
}
finally
{
await ScrollToPageTop();
}
}
private void ProviderChanged(ChangeEventArgs e)
{
_provider = (string)e.Value;
var provider = Shared.ExternalLoginProviders.Providers.FirstOrDefault(item => item.Name == _provider);
if (provider != null)
{
LoadExternalLoginSettings(provider.Settings);
}
StateHasChanged();
}
private void ProviderTypeChanged(ChangeEventArgs e)
private void ProviderTypeChanged(ChangeEventArgs e)
{
_providertype = (string)e.Value;
if (string.IsNullOrEmpty(_providername))

View File

@ -173,6 +173,12 @@ else
_editmode = bool.Parse(EditMode);
}
Text = Localize(nameof(Text), Text);
Header = Localize(nameof(Header), Header);
Message = Localize(nameof(Message), Message);
_openText = Text;
if (!string.IsNullOrEmpty(IconName))
{
if (IconOnly)
@ -190,12 +196,7 @@ else
_openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
_iconSpan = $"<span class=\"{IconName}\"></span>&nbsp";
}
Text = Localize(nameof(Text), Text);
Header = Localize(nameof(Header), Header);
Message = Localize(nameof(Message), Message);
_openText = Text;
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_authorized = IsAuthorized();

View File

@ -196,7 +196,7 @@
else
{
FolderId = -1;
_message = "Folder Path " + Folder + "Does Not Exist";
_message = "Folder Path " + Folder + " Does Not Exist";
_messagetype = MessageType.Error;
}
}
@ -226,9 +226,9 @@
}
else
{
FileId = -1; // file does not exist
_message = "FileId " + FileId.ToString() + "Does Not Exist";
_message = "FileId " + FileId.ToString() + " Does Not Exist";
_messagetype = MessageType.Error;
FileId = -1; // file does not exist
}
}
@ -359,12 +359,6 @@
}
if (restricted == "")
{
if (!ShowProgress)
{
_uploading = true;
StateHasChanged();
}
try
{
// upload the files
@ -374,7 +368,21 @@
if (PageState.Runtime == Shared.Runtime.Hybrid)
{
jwt = await UserService.GetTokenAsync();
if (string.IsNullOrEmpty(jwt))
{
await logger.LogInformation("File Upload Failed From .NET MAUI Due To Missing Security Token. Token Options Must Be Set In User Settings.");
_message = "Security Token Not Specified";
_messagetype = MessageType.Error;
return;
}
}
if (!ShowProgress)
{
_uploading = true;
StateHasChanged();
}
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt);
// uploading is asynchronous so we need to poll to determine if uploads are completed
@ -387,7 +395,7 @@
var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes
var megabits = (size / 1048576.0) * 8; // binary conversion
var uploadspeed = 2; // 2 Mbps (3G ranges from 300Kbps to 3Mbps)
var uploadspeed = (PageState.Alias.Name.Contains("localhost")) ? 100 : 3; // 3 Mbps is FCC minimum for broadband upload
var uploadtime = (megabits / uploadspeed); // seconds
var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds)
var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds

View File

@ -10,13 +10,16 @@
{
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
}
@if (ModuleState.RenderMode == RenderModes.Static)
@if (ModuleState != null)
{
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
}
else
{
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
@if (ModuleState.RenderMode == RenderModes.Static)
{
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
}
else
{
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
}
}
</div>
}

View File

@ -21,7 +21,7 @@
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{
<ul class="pagination justify-content-center my-2">
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@ -84,7 +84,7 @@
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{
<ul class="pagination justify-content-center my-2">
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@ -200,7 +200,7 @@
{
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{
<ul class="pagination justify-content-center my-2">
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@ -248,7 +248,7 @@
}
else
{
<ul class="pagination justify-content-center my-2">
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@ -368,6 +368,12 @@
[SupplyParameterFromForm(FormName = "PagerForm")]
public string _Search { get => ""; set => _search = value; }
/// <summary>
/// Accepted values are Start or Center or End. The default value is Center
/// </summary>
[Parameter]
public string PaginationAlignment { get; set; } = "center"; // Alignment of the Page Numbering start, center, end
private IEnumerable<TableItem> ItemList { get; set; }
protected override void OnInitialized()
@ -452,9 +458,9 @@
_displayPages = int.Parse(DisplayPages);
}
if (PageState.QueryString.ContainsKey("page"))
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
{
_page = int.Parse(PageState.QueryString["page"]);
_page = page;
}
else
{

View File

@ -111,7 +111,7 @@
[Parameter]
public List<Permission> PermissionList { get; set; }
protected override async Task OnParametersSetAsync()
protected override async Task OnInitializedAsync()
{
if (!string.IsNullOrEmpty(Permissions))
{

View File

@ -277,7 +277,7 @@
{
// include CSS theme
var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", $"css/quill/quill.{_theme}.css", "text/css", "", "", "");
await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/quill/quill.{_theme}.css", "text/css", "", "", "");
}
await base.OnAfterRenderAsync(firstRender);

View File

@ -36,14 +36,7 @@ else
Parent.AddTabPanel((TabPanel)this);
if (string.IsNullOrEmpty(Heading))
{
Heading = Localize(nameof(Name), Name);
}
else
{
Heading = Localize(nameof(Heading), Heading);
}
Heading = string.IsNullOrEmpty(Heading) ? Localize(nameof(Name), Name) : Localize(nameof(Heading), Heading);
}
public string DisplayHeading()

View File

@ -98,17 +98,17 @@ namespace Oqtane.Modules
var inline = 0;
foreach (Resource resource in resources)
{
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
{
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
}
}
}
@ -134,6 +134,7 @@ namespace Oqtane.Modules
// url methods
// navigate url
public string NavigateUrl()
{
return NavigateUrl(PageState.Page.Path);
@ -149,24 +150,65 @@ namespace Oqtane.Modules
return NavigateUrl(PageState.Page.Path, refresh);
}
public string NavigateUrl(string path, string parameters)
public string NavigateUrl(string path, string querystring)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);
return Utilities.NavigateUrl(PageState.Alias.Path, path, querystring);
}
public string NavigateUrl(string path, Dictionary<string, string> querystring)
{
return NavigateUrl(path, Utilities.CreateQueryString(querystring));
}
public string NavigateUrl(string path, bool refresh)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, refresh ? "refresh" : "");
return NavigateUrl(path, refresh ? "refresh" : "");
}
public string NavigateUrl(int moduleId, string action)
{
return EditUrl(PageState.Page.Path, moduleId, action, "");
}
public string NavigateUrl(int moduleId, string action, string querystring)
{
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
}
public string NavigateUrl(int moduleId, string action, Dictionary<string, string> querystring)
{
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
}
public string NavigateUrl(string path, int moduleId, string action)
{
return EditUrl(path, moduleId, action, "");
}
public string NavigateUrl(string path, int moduleId, string action, string querystring)
{
return EditUrl(path, moduleId, action, querystring);
}
public string NavigateUrl(string path, int moduleId, string action, Dictionary<string, string> querystring)
{
return EditUrl(path, moduleId, action, querystring);
}
// edit url
public string EditUrl(string action)
{
return EditUrl(ModuleState.ModuleId, action);
}
public string EditUrl(string action, string parameters)
public string EditUrl(string action, string querystring)
{
return EditUrl(ModuleState.ModuleId, action, parameters);
return EditUrl(ModuleState.ModuleId, action, querystring);
}
public string EditUrl(string action, Dictionary<string, string> querystring)
{
return EditUrl(ModuleState.ModuleId, action, querystring);
}
public string EditUrl(int moduleId, string action)
@ -174,16 +216,27 @@ namespace Oqtane.Modules
return EditUrl(moduleId, action, "");
}
public string EditUrl(int moduleId, string action, string parameters)
public string EditUrl(int moduleId, string action, string querystring)
{
return EditUrl(PageState.Page.Path, moduleId, action, parameters);
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
}
public string EditUrl(string path, int moduleid, string action, string parameters)
public string EditUrl(int moduleId, string action, Dictionary<string, string> querystring)
{
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
}
public string EditUrl(string path, int moduleid, string action, string querystring)
{
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, querystring);
}
public string EditUrl(string path, int moduleid, string action, Dictionary<string, string> querystring)
{
return EditUrl(path, moduleid, action, Utilities.CreateQueryString(querystring));
}
// file url
public string FileUrl(string folderpath, string filename)
{
return FileUrl(folderpath, filename, false);
@ -203,6 +256,8 @@ namespace Oqtane.Modules
return Utilities.FileUrl(PageState.Alias, fileid, download);
}
// image url
public string ImageUrl(int fileid, int width, int height)
{
return ImageUrl(fileid, width, height, "");

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations>
<Version>5.2.1</Version>
<Version>6.0.1</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/v5.2.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -22,11 +22,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
@ -13,13 +12,13 @@ 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.Shared;
using Oqtane.UI;
namespace Oqtane.Client
@ -258,7 +257,7 @@ namespace Oqtane.Client
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime);
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICulture.Name;
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync(false);

View File

@ -183,4 +183,7 @@
<data name="Template" xml:space="preserve">
<value>Select a site template</value>
</data>
<data name="Message.Username.Invalid" xml:space="preserve">
<value>The Username Provided Does Not Meet The System Requirement, It Can Only Contains Letters Or Digits.</value>
</data>
</root>

View File

@ -267,4 +267,4 @@
<data name="ExpiryDate.Text" xml:space="preserve">
<value>Expiry Date: </value>
</data>
</root>
</root>

View File

@ -297,4 +297,4 @@
<data name="ExpiryDate.Text" xml:space="preserve">
<value>Expiry Date: </value>
</data>
</root>
</root>

View File

@ -139,7 +139,7 @@
<value>Ignore Entities: </value>
</data>
<data name="IgnoreEntities.HelpText" xml:space="preserve">
<value>Comma delimited list of entities which should be ignored</value>
<value>Comma delimited list of entities which should be ignored. By default File entities are ignored.</value>
</data>
<data name="MinimumWordLength.Text" xml:space="preserve">
<value>Word Length: </value>
@ -154,7 +154,7 @@
<value>Comma delimited list of words which should be ignored</value>
</data>
<data name="Success.Save" xml:space="preserve">
<value>Search Settings Saved Successfully</value>
<value>Search Settings Saved Successfully. You Will Need Reindex For Your Changes To Be Reflected In The Search Results.</value>
</data>
<data name="Error.Save" xml:space="preserve">
<value>Error Saving Search Settings</value>

View File

@ -163,7 +163,7 @@
<value>Enter the site name</value>
</data>
<data name="Tenant.HelpText" xml:space="preserve">
<value>The name of the database used for the site</value>
<value>The name of the database used for the site. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database.</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve">
<value>The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder).</value>
@ -307,7 +307,7 @@
<value>Type:</value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>The connection information for the database</value>
<value>The name of the connection string in appsettings.json which will be used to connect to the database</value>
</data>
<data name="Database.HelpText" xml:space="preserve">
<value>The type of database</value>
@ -435,4 +435,7 @@
<data name="Functionality" xml:space="preserve">
<value>Functionality</value>
</data>
<data name="System" xml:space="preserve">
<value>System</value>
</data>
</root>

View File

@ -187,7 +187,7 @@
<value>Select the database for the site</value>
</data>
<data name="TenantName.HelpText" xml:space="preserve">
<value>Enter the name for the database</value>
<value>Enter the name for the database. Note that this will be the tenant name which is used within the framework to identify the database.</value>
</data>
<data name="DatabaseType.HelpText" xml:space="preserve">
<value>Select the database type</value>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -150,4 +150,7 @@
<data name="Localhost.Text" xml:space="preserve">
<value>You Cannot Perform A System Update In A Development Environment</value>
</data>
<data name="Disclaimer.Text" xml:space="preserve">
<value>Please Note That The System Update Capability Is A Simplified Upgrade Process Intended For Small To Medium Sized Installations. For Larger Enterprise Installations You Will Want To Use A Manual Upgrade Process. Also Note That The System Update Capability Is Not Recommended When Using Microsoft Azure Due To Environmental Limitations.</value>
</data>
</root>

View File

@ -243,4 +243,7 @@
<data name="NoNotificationsSent.Text" xml:space="preserve">
<value>No notifications have been sent</value>
</data>
<data name="Logout Everywhere" xml:space="preserve">
<value>Logout Everywhere</value>
</data>
</root>

View File

@ -117,12 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Error.User.AddCheckPass" xml:space="preserve">
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use.</value>
</data>
<data name="Message.Password.NoMatch" xml:space="preserve">
<value>Passwords Entered Do Not Match</value>
</data>
<data name="Error.User.Add" xml:space="preserve">
<value>Error Adding User</value>
</data>
@ -133,17 +127,11 @@
<value>Identity</value>
</data>
<data name="Message.Required.ProfileInfo" xml:space="preserve">
<value>You Must Provide A Username, Password, Email Address And All Required Profile Information</value>
<value>You Must Provide A Username, Email Address And All Required Profile Information</value>
</data>
<data name="Message.Username.Exists" xml:space="preserve">
<value>Username Already Exists</value>
</data>
<data name="Confirm.HelpText" xml:space="preserve">
<value>Please enter the password again to confirm it matches with the value above</value>
</data>
<data name="Confirm.Text" xml:space="preserve">
<value>Confirm Password:</value>
</data>
<data name="DisplayName.HelpText" xml:space="preserve">
<value>The full name of the user</value>
</data>
@ -156,21 +144,12 @@
<data name="Email.Text" xml:space="preserve">
<value>Email:</value>
</data>
<data name="Password.HelpText" xml:space="preserve">
<value>The user's password. Please choose a password which is sufficiently secure.</value>
</data>
<data name="Password.Text" xml:space="preserve">
<value>Password:</value>
</data>
<data name="Username.HelpText" xml:space="preserve">
<value>A unique username for a user. Note that this field can not be modified once it is saved.</value>
</data>
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="Password.Placeholder" xml:space="preserve">
<value>Password</value>
</data>
<data name="Notify.HelpText" xml:space="preserve">
<value>Indicate if new users should receive an email notification</value>
</data>

View File

@ -385,10 +385,22 @@
<value>Parameters:</value>
</data>
<data name="RoleClaimType.HelpText" xml:space="preserve">
<value>Optionally provide the type name of the role claim provided by the identity provider. These roles will be used in addition to any internal user roles assigned within the site.</value>
<value>Optionally provide the type name of the roles claim provided by the identity provider (the standard default is 'roles'). If role names from the identity provider do not exactly match your site role names, please use the Role Claim Mappings.</value>
</data>
<data name="RoleClaimType.Text" xml:space="preserve">
<value>Role Claim:</value>
<value>Roles Claim:</value>
</data>
<data name="RoleClaimMappings.HelpText" xml:space="preserve">
<value>Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles. For example if the identity provider includes an 'Admin' role name and you want it to map to the 'Administrators' site role you should specify 'Admin:Administrators'.</value>
</data>
<data name="RoleClaimMappings.Text" xml:space="preserve">
<value>Role Claim Mappings:</value>
</data>
<data name="SynchronizeRoles.HelpText" xml:space="preserve">
<value>This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider for a user</value>
</data>
<data name="SynchronizeRoles.Text" xml:space="preserve">
<value>Synchronize Roles?</value>
</data>
<data name="ProfileClaimTypes.HelpText" xml:space="preserve">
<value>Optionally provide a comma delimited list of user profile claim type names provided by the identity provider, as well as mappings to your user profile definition. For example if the identity provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'.</value>
@ -459,13 +471,28 @@
<data name="ReviewClaims.Text" xml:space="preserve">
<value>Review Claims?</value>
</data>
<data name="ReviewClaims.HelpText" xml:space="preserve">
<data name="ReviewClaims.HelpText" xml:space="preserve">
<value>This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled.</value>
</data>
<data name="NameClaimType.HelpText" xml:space="preserve">
<value>Optionally specify the type name of the user's name claim provided by the identity provider. The typical value is 'name'.</value>
</data>
<data name="NameClaimType.Text" xml:space="preserve">
<data name="NameClaimType.Text" xml:space="preserve">
<value>Name Claim:</value>
</data>
<data name="Provider.HelpText" xml:space="preserve">
<value>Select the external login provider</value>
</data>
<data name="Provider.Text" xml:space="preserve">
<value>Provider:</value>
</data>
<data name="Info" xml:space="preserve">
<value>Info</value>
</data>
<data name="OAuth2" xml:space="preserve">
<value>OAuth 2.0</value>
</data>
<data name="OIDC" xml:space="preserve">
<value>OpenID Connect (OIDC)</value>
</data>
</root>

View File

@ -0,0 +1,17 @@
using System.Threading.Tasks;
namespace Oqtane.Services
{
/// <summary>
/// Service to set localization cookie
/// </summary>
public interface ILocalizationCookieService
{
/// <summary>
/// Set the localization cookie
/// </summary>
/// <param name="culture"></param>
/// <returns></returns>
Task SetLocalizationCookieAsync(string culture);
}
}

View File

@ -75,6 +75,13 @@ namespace Oqtane.Services
/// <returns></returns>
Task LogoutUserAsync(User user);
/// <summary>
/// Logout a <see cref="User"/>
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
Task LogoutUserEverywhereAsync(User user);
/// <summary>
/// Update e-mail verification status of a user.
/// </summary>
@ -106,6 +113,15 @@ namespace Oqtane.Services
/// <returns></returns>
Task<User> VerifyTwoFactorAsync(User user, string token);
/// <summary>
/// Validate identity user info.
/// </summary>
/// <param name="username"></param>
/// <param name="email"></param>
/// <param name="password"></param>
/// <returns></returns>
Task<UserValidateResult> ValidateUserAsync(string username, string email, string password);
/// <summary>
/// Validate a users password against the password policy
/// </summary>

View File

@ -0,0 +1,18 @@
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Documentation;
using Oqtane.Shared;
namespace Oqtane.Services
{
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class LocalizationCookieService : ServiceBase, ILocalizationCookieService
{
public LocalizationCookieService(HttpClient http, SiteState siteState) : base(http, siteState) { }
public Task SetLocalizationCookieAsync(string culture)
{
return Task.CompletedTask; // only used in server side rendering
}
}
}

View File

@ -4,7 +4,6 @@ using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
using Oqtane.Shared;
namespace Oqtane.Services
@ -28,9 +27,9 @@ namespace Oqtane.Services
private HttpClient GetHttpClient(string AuthorizationToken)
{
var httpClient = _httpClientFactory.CreateClient("Remote");
if (!httpClient.DefaultRequestHeaders.Contains(HeaderNames.Authorization) && !string.IsNullOrEmpty(AuthorizationToken))
if (!httpClient.DefaultRequestHeaders.Contains("Authorization") && !string.IsNullOrEmpty(AuthorizationToken))
{
httpClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + AuthorizationToken);
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + AuthorizationToken);
}
return httpClient;
}

View File

@ -61,10 +61,14 @@ namespace Oqtane.Services
public async Task LogoutUserAsync(User user)
{
// best practices recommend post is preferrable to get for logout
await PostJsonAsync($"{Apiurl}/logout", user);
}
public async Task LogoutUserEverywhereAsync(User user)
{
await PostJsonAsync($"{Apiurl}/logouteverywhere", user);
}
public async Task<User> VerifyEmailAsync(User user, string token)
{
return await PostJsonAsync<User>($"{Apiurl}/verify?token={token}", user);
@ -85,6 +89,11 @@ namespace Oqtane.Services
return await PostJsonAsync<User>($"{Apiurl}/twofactor?token={token}", user);
}
public async Task<UserValidateResult> ValidateUserAsync(string username, string email, string password)
{
return await GetJsonAsync<UserValidateResult>($"{Apiurl}/validateuser?username={WebUtility.UrlEncode(username)}&email={WebUtility.UrlEncode(email)}&password={WebUtility.UrlEncode(password)}");
}
public async Task<bool> ValidatePasswordAsync(string password)
{
return await GetJsonAsync<bool>($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}");

View File

@ -9,7 +9,7 @@
<div class="row flex-xl-nowrap gx-0">
<div class="sidebar">
<nav class="navbar">
<Logo />
<Logo UseSiteNameAsFallback="true" />
<Menu Orientation="Vertical" />
</nav>
</div>
@ -37,13 +37,8 @@
public override List<Resource> Resources => new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css",
Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==",
CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js",
Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==",
CrossOrigin = "anonymous", Location = ResourceLocation.Body },
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", "anonymous"),
new Stylesheet(ThemePath() + "Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
};
}

View File

@ -11,9 +11,6 @@ using System.Net;
using Microsoft.Extensions.Localization;
using Oqtane.UI;
// ReSharper disable UnassignedGetOnlyAutoProperty
// ReSharper disable MemberCanBePrivate.Global
namespace Oqtane.Themes.Controls
{
public class ModuleActionsBase : ComponentBase
@ -92,20 +89,21 @@ namespace Oqtane.Themes.Controls
return actionList;
}
private async Task<string> EditUrlAsync(string url, int moduleId, string import)
{
await Task.Yield();
return Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, moduleId, import, "");
}
protected async Task ModuleAction(ActionViewModel action)
{
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList))
{
PageModule pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
string url = Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, "edit=true&refresh");
var url = NavigationManager.Uri.Substring(NavigationManager.BaseUri.Length - 1);
if (!url.Contains("edit="))
{
url += (!url.Contains("?") ? "?" : "&") + "edit=true";
}
if (!url.Contains("refresh="))
{
url += (!url.Contains("?") ? "?" : "&") + "refresh=true";
}
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
if (action.Action != null)
{
url = await action.Action(url, pagemodule);
@ -115,31 +113,10 @@ namespace Oqtane.Themes.Controls
}
}
private async Task<string> MoveToPane(string url, string newPane, PageModule pagemodule)
private Task<string> Settings(string url, PageModule pagemodule)
{
string oldPane = pagemodule.Pane;
pagemodule.Pane = newPane;
pagemodule.Order = int.MaxValue; // add to bottom of pane
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane);
return url;
}
private async Task<string> DeleteModule(string url, PageModule pagemodule)
{
pagemodule.IsDeleted = true;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return url;
}
private async Task<string> Settings(string url, PageModule pagemodule)
{
await Task.Yield();
var returnurl = Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, "edit=true");
url = Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, pagemodule.ModuleId, "Settings", "returnurl=" + WebUtility.UrlEncode(returnurl));
return url;
url = Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, pagemodule.ModuleId, "Settings", "returnurl=" + WebUtility.UrlEncode(url));
return Task.FromResult(url);
}
private async Task<string> Publish(string url, PageModule pagemodule)
@ -174,6 +151,20 @@ namespace Oqtane.Themes.Controls
return url;
}
private async Task<string> DeleteModule(string url, PageModule pagemodule)
{
pagemodule.IsDeleted = true;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return url;
}
private Task<string> EditUrlAsync(string url, int moduleId, string import)
{
url = Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, moduleId, import, "returnurl=" + WebUtility.UrlEncode(url));
return Task.FromResult(url);
}
private async Task<string> MoveTop(string url, PageModule pagemodule)
{
pagemodule.Order = 0;
@ -206,6 +197,17 @@ namespace Oqtane.Themes.Controls
return url;
}
private async Task<string> MoveToPane(string url, string newPane, PageModule pagemodule)
{
string oldPane = pagemodule.Pane;
pagemodule.Pane = newPane;
pagemodule.Order = int.MaxValue; // add to bottom of pane
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane);
return url;
}
public class ActionViewModel
{
public string Icon { get; set; }

View File

@ -9,7 +9,7 @@
<LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" />
}
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
@if (ShowEditMode && (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))))
{
<form method="post" class="app-form-inline" @formname="EditModeForm" @onsubmit="@(async () => await ToggleEditMode(PageState.EditMode))" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
@ -59,9 +59,15 @@
[Parameter]
public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
/// <summary>
/// Ability to hide the Edit Mode toggle button
/// </summary>
[Parameter]
public bool ShowEditMode { get; set; } = true;
private PageState _pageState;
private bool _canViewAdminDashboard = false;
private bool _showEditMode = false;
private bool _showEditMode = false; // internal state (not the same as ShowEditMode parameter)
protected override void OnParametersSet()
{
@ -147,8 +153,7 @@
{
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
{
PageState.EditMode = true;
NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false")));
NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + PageState.EditMode.ToString()));
}
}
}

View File

@ -15,7 +15,6 @@
@inject ILogService LoggingService
@inject IStringLocalizer<ControlPanelInteractive> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IServiceProvider ServiceProvider
<button type="button" class="btn @ButtonClass ms-1" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel" @onclick="ClearMessage">
<span class="oi oi-cog"></span>
@ -331,7 +330,7 @@
if (_pageId != "-")
{
_modules = await ModuleService.GetModulesAsync(PageState.Page.SiteId);
_modules = _modules.Where(module => module.PageId == int.Parse(_pageId) &&
_modules = _modules.Where(module => module.PageId == int.Parse(_pageId) && module.IsDeleted == false &&
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList) &&
(_moduleType == "add" || module.ModuleDefinition.IsPortable))
.ToList();
@ -454,7 +453,7 @@
{
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))
{
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleName = permission.RoleName, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
}
return permissions;
}

View File

@ -1,10 +1,9 @@
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Http
@using Oqtane.Models
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inject ILanguageService LanguageService
@inject ILocalizationCookieService LocalizationCookieService
@inject NavigationManager NavigationManager
@if (_supportedCultures?.Count() > 1)
@ -22,7 +21,7 @@
}
else
{
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + culture.Name)">@culture.DisplayName</a>
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + culture.Name)" data-enhance-nav="false">@culture.DisplayName</a>
}
}
</div>
@ -38,25 +37,20 @@
[Parameter]
public string ButtonClass { get; set; } = "btn-outline-secondary";
[CascadingParameter]
HttpContext HttpContext { get; set; }
protected override void OnParametersSet()
protected override async Task OnParametersSetAsync()
{
MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty;
var languages = PageState.Languages;
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
_supportedCultures = PageState.Languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
if (PageState.QueryString.ContainsKey("culture"))
{
var culture = PageState.QueryString["culture"];
if (_supportedCultures.Any(item => item.Name == culture))
{
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions { Path = "/", Expires = DateTimeOffset.UtcNow.AddYears(365) });
await LocalizationCookieService.SetLocalizationCookieAsync(culture);
}
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""), forceLoad: true);
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""));
}
}
@ -66,8 +60,8 @@
{
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
var interop = new Interop(JSRuntime);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}
}

View File

@ -4,31 +4,28 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
<span class="app-login">
<AuthorizeView Roles="@RoleNames.Registered">
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
@if (PageState.Runtime == Runtime.Hybrid)
{
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button>
}
else
{
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<input type="hidden" name="returnurl" value="@returnurl" />
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
</form>
}
</Authorized>
<NotAuthorized>
@if (ShowLogin)
{
<a href="@loginurl" class="btn btn-primary">@SharedLocalizer["Login"]</a>
}
</NotAuthorized>
</AuthorizeView>
@if (PageState.User != null)
{
@if (PageState.Runtime == Runtime.Hybrid)
{
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button>
}
else
{
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<input type="hidden" name="returnurl" value="@returnurl" />
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
</form>
}
}
else
{
@if (ShowLogin)
{
<a href="@loginurl" class="btn btn-primary">@SharedLocalizer["Login"]</a>
}
}
</span>
@code

View File

@ -8,4 +8,19 @@
<img class="img-fluid" src="@Utilities.FileUrl(PageState.Alias, PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" />
</a>
</span>
}
}
else
{
if (UseSiteNameAsFallback)
{
<span class="app-logo">
<a class="navbar-brand" href="@PageState.Alias.Path">@PageState.Site.Name</a>
</span>
}
}
@code {
[Parameter]
public bool UseSiteNameAsFallback { get; set; } = false; // indicates if the site name should be displayed in scenarios where a site does not have a logo defined
}

View File

@ -1,30 +1,32 @@
@namespace Oqtane.Themes.Controls
@using System.Net
@using Microsoft.AspNetCore.Http
@inherits ThemeControlBase
@inject ISettingService SettingService
@inject IStringLocalizer<Search> Localizer
@inject NavigationManager NavigationManager
@if (_searchResultsPage != null)
{
<span class="app-search @CssClass">
<span class="@_defaultCssClass @CssClass">
<form method="post" class="app-form-inline" @formname="@($"SearchForm")" @onsubmit="@PerformSearch" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<input type="text" name="keywords" maxlength="50"
class="form-control d-inline-block pe-5 shadow-none"
@bind="_keywords"
placeholder="@Localizer["SearchPlaceHolder"]"
aria-label="Search" />
<button type="submit" class="btn btn-search">
@if (AllowTextInput)
{
<input type="text" name="keywords" maxlength="50"
class="form-control d-inline-block pe-5 shadow-none"
@bind="_keywords"
placeholder="@Localizer["SearchPlaceHolder"]"
aria-label="Search" />
}
<button type="submit" class="btn btn-search" aria-label="Search Button">
<span class="oi oi-magnifying-glass align-middle"></span>
</button>
</form>
</span>
}
@code {
private string _defaultCssClass;
private Page _searchResultsPage;
private string _keywords = "";
@ -32,19 +34,23 @@
public string CssClass { get; set; }
[Parameter]
public string SearchResultPagePath { get; set; } = "search";
public bool AllowTextInput { get; set; } = true; // setting to false will display only the search icon button - not the textbox
[CascadingParameter]
HttpContext HttpContext { get; set; }
[Parameter]
public string SearchResultPagePath { get; set; } = "search"; // setting to "" will disable search
[SupplyParameterFromForm(FormName = "SearchForm")]
public string KeyWords { get => ""; set => _keywords = value; }
protected override void OnInitialized()
{
if(!string.IsNullOrEmpty(SearchResultPagePath))
if (bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "Search_Enabled", "True")))
{
_searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath);
_defaultCssClass = (AllowTextInput) ? "app-search" : "app-search-noinput";
if (!string.IsNullOrEmpty(SearchResultPagePath))
{
_searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath);
}
}
}

View File

@ -6,20 +6,17 @@
@inject NavigationManager NavigationManager
<span class="app-profile">
<AuthorizeView Roles="@RoleNames.Registered">
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="btn btn-primary">@context.User.Identity.Name</a>
</Authorized>
<NotAuthorized>
@if (ShowRegister && PageState.Site.AllowRegistration)
{
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="btn btn-primary">@Localizer["Register"]</a>
}
</NotAuthorized>
</AuthorizeView>
@if (PageState.User != null)
{
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="btn btn-primary">@PageState.User.Username</a>
}
else
{
@if (ShowRegister && PageState.Site.AllowRegistration)
{
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="btn btn-primary">@Localizer["Register"]</a>
}
}
</span>
@code {

View File

@ -17,13 +17,9 @@ namespace Oqtane.Themes.OqtaneTheme
Resources = new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css",
Integrity = "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==",
CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js",
Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==",
CrossOrigin = "anonymous", Location = ResourceLocation.Body },
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css", "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==", "anonymous"),
new Stylesheet("~/Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
}
};
}

View File

@ -4,7 +4,7 @@
<main role="main">
<nav class="navbar navbar-dark bg-primary fixed-top">
<Logo /><Menu Orientation="Horizontal" />
<Logo UseSiteNameAsFallback="true" /><Menu Orientation="Horizontal" />
<div class="controls ms-auto">
<div class="controls-group">
<Search CssClass="me-3 text-center bg-primary" />

View File

@ -62,17 +62,17 @@ namespace Oqtane.Themes
var inline = 0;
foreach (Resource resource in resources)
{
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
{
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
}
}
}
@ -93,6 +93,7 @@ namespace Oqtane.Themes
// url methods
// navigate url
public string NavigateUrl()
{
return NavigateUrl(PageState.Page.Path);
@ -108,31 +109,78 @@ namespace Oqtane.Themes
return NavigateUrl(PageState.Page.Path, refresh);
}
public string NavigateUrl(string path, string querystring)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, querystring);
}
public string NavigateUrl(string path, Dictionary<string, string> querystring)
{
return NavigateUrl(path, Utilities.CreateQueryString(querystring));
}
public string NavigateUrl(string path, bool refresh)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, refresh ? "refresh" : "");
return NavigateUrl(path, refresh ? "refresh" : "");
}
public string NavigateUrl(string path, string parameters)
public string NavigateUrl(int moduleid, string action)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);
return EditUrl(moduleid, action, "");
}
public string NavigateUrl(int moduleid, string action, string querystring)
{
return EditUrl(PageState.Page.Path, moduleid, action, querystring);
}
public string NavigateUrl(int moduleid, string action, Dictionary<string, string> querystring)
{
return EditUrl(PageState.Page.Path, moduleid, action, Utilities.CreateQueryString(querystring));
}
public string NavigateUrl(string path, int moduleId, string action)
{
return EditUrl(path, moduleId, action, "");
}
public string NavigateUrl(string path, int moduleid, string action, string querystring)
{
return EditUrl(path, moduleid, action, querystring);
}
public string NavigateUrl(string path, int moduleid, string action, Dictionary<string, string> querystring)
{
return EditUrl(path, moduleid, action, querystring);
}
// edit url
public string EditUrl(int moduleid, string action)
{
return EditUrl(moduleid, action, "");
}
public string EditUrl(int moduleid, string action, string parameters)
public string EditUrl(int moduleid, string action, string querystring)
{
return EditUrl(PageState.Page.Path, moduleid, action, parameters);
return EditUrl(PageState.Page.Path, moduleid, action, querystring);
}
public string EditUrl(string path, int moduleid, string action, string parameters)
public string EditUrl(int moduleid, string action, Dictionary<string, string> querystring)
{
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
return EditUrl(PageState.Page.Path, moduleid, action, querystring);
}
public string EditUrl(string path, int moduleid, string action, string querystring)
{
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, querystring);
}
public string EditUrl(string path, int moduleid, string action, Dictionary<string, string> querystring)
{
return EditUrl(path, moduleid, action, Utilities.CreateQueryString(querystring));
}
// file url
public string FileUrl(string folderpath, string filename)
{
return FileUrl(folderpath, filename, false);
@ -152,6 +200,7 @@ namespace Oqtane.Themes
return Utilities.FileUrl(PageState.Alias, fileid, download);
}
// image url
public string ImageUrl(int fileid, int width, int height)
{
return ImageUrl(fileid, width, height, "");

View File

@ -41,7 +41,7 @@
}
break;
case "HeadContent":
var content = RemoveScripts(SiteState.Properties.HeadContent) + "\n";
var content = FormatScripts(SiteState.Properties.HeadContent) + "\n";
if (content != _content)
{
_content = content;
@ -51,15 +51,29 @@
}
}
private string RemoveScripts(string headcontent)
private string FormatScripts(string headcontent)
{
if (!string.IsNullOrEmpty(headcontent) && RenderMode == RenderModes.Interactive)
if (!string.IsNullOrEmpty(headcontent))
{
var index = headcontent.IndexOf("<script");
while (index >= 0)
{
headcontent = headcontent.Remove(index, headcontent.IndexOf("</script>") + 9 - index);
index = headcontent.IndexOf("<script");
var script = headcontent.Substring(index, headcontent.IndexOf("</script>", index) - index + 9);
if (RenderMode == RenderModes.Interactive)
{
// remove scripts when interactive rendering
headcontent = headcontent.Remove(index, script.Length);
index = headcontent.IndexOf("<script");
}
else
{
if (!script.Contains("><") && !script.Contains("data-reload"))
{
// add data-reload attribute to inline script
headcontent = headcontent.Replace(script, script.Replace("<script", "<script data-reload=\"true\""));
}
index = headcontent.IndexOf("<script", index + 1);
}
}
}
return headcontent;

View File

@ -16,13 +16,18 @@ namespace Oqtane.UI
_jsRuntime = jsRuntime;
}
public Task SetCookie(string name, string value, int days)
public async Task SetCookie(string name, string value, int days)
{
await SetCookie(name, value, days, true, "Lax");
}
public Task SetCookie(string name, string value, int days, bool secure, string sameSite)
{
try
{
_jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.setCookie",
name, value, days);
name, value, days, secure, sameSite);
return Task.CompletedTask;
}
catch
@ -112,12 +117,17 @@ namespace Oqtane.UI
}
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location)
{
return IncludeScript(id, src, integrity, crossorigin, type, content, location, null);
}
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location, Dictionary<string, string> dataAttributes)
{
try
{
_jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeScript",
id, src, integrity, crossorigin, type, content, location);
id, src, integrity, crossorigin, type, content, location, dataAttributes);
return Task.CompletedTask;
}
catch

View File

@ -1,7 +1,5 @@
@namespace Oqtane.UI
@using Microsoft.AspNetCore.Http
@inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime
@inject SiteState SiteState
@if (_initialized)
@ -48,9 +46,6 @@
[Parameter]
public string Platform { get; set; } = "";
[CascadingParameter]
HttpContext HttpContext { get; set; }
private bool _initialized = false;
private bool _installed = false;
private string _display = "display: none;";
@ -61,9 +56,8 @@
{
SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken;
SiteState.RemoteIPAddress = (_pageState != null) ? _pageState.RemoteIPAddress : "";
SiteState.Platform = Platform;
SiteState.IsPrerendering = (HttpContext != null) ? true : false;
SiteState.IsPrerendering = !RendererInfo.IsInteractive;
if (Runtime == Runtimes.Hybrid)
{
@ -80,6 +74,7 @@
{
_pageState = PageState;
SiteState.Alias = PageState.Alias;
SiteState.RemoteIPAddress = (PageState != null) ? PageState.RemoteIPAddress : "";
_installed = true;
}
}

View File

@ -1,6 +1,5 @@
@using System.Diagnostics.CodeAnalysis
@using System.Net
@using Microsoft.AspNetCore.Http
@using System.Globalization
@using System.Security.Claims
@namespace Oqtane.UI
@ -157,7 +156,7 @@
// verify user is authenticated for current site
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey))
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == Constants.SiteKeyClaimType && item.Value == SiteState.Alias.SiteKey))
{
// get user
var userid = int.Parse(authState.User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
@ -287,10 +286,10 @@
}
// load additional metadata for current page
page = ProcessPage(page, site, user, SiteState.Alias);
page = ProcessPage(page, site, user, SiteState.Alias, action);
// load additional metadata for modules
(page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
(page, modules) = ProcessModules(site, page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
// populate page state (which acts as a client-side cache for subsequent requests)
_pagestate = new PageState
@ -366,7 +365,7 @@
}
}
private Page ProcessPage(Page page, Site site, User user, Alias alias)
private Page ProcessPage(Page page, Site site, User user, Alias alias, string action)
{
try
{
@ -403,6 +402,16 @@
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace);
}
}
// theme settings components are dynamically loaded within the framework Page Management module
if (page.Path == "admin/pages" && action.ToLower() == "edit" && theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
var settingsType = Type.GetType(theme.ThemeSettingsType);
if (settingsType != null)
{
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace);
}
}
if (!string.IsNullOrEmpty(panes))
{
@ -426,7 +435,7 @@
return page;
}
private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype, Alias alias)
private (Page Page, List<Module> Modules) ProcessModules(Site site, Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype, Alias alias)
{
var paneindex = new Dictionary<string, int>();
@ -494,15 +503,40 @@
module.Prerender = moduleobject.Prerender;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
// settings components are dynamically loaded within the framework Settings module
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{
// settings components are embedded within a framework settings module
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
// module settings component
var settingsType = "";
if (!string.IsNullOrEmpty(module.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
settingsType = module.ModuleDefinition.SettingsType;
}
else
{
// legacy support - module settings type determined by convention
settingsType = module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action);
}
moduletype = Type.GetType(settingsType, false, true);
if (moduletype != null)
{
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
}
// container settings component
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
moduletype = Type.GetType(theme.ContainerSettingsType);
if (moduletype != null)
{
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
}
}
}
// additional metadata needed for admin components

View File

@ -11,6 +11,8 @@
RenderFragment DynamicComponent { get; set; }
private string lastPagePath = "";
protected override void OnParametersSet()
{
// handle page redirection
@ -20,6 +22,13 @@
return;
}
// force authenticated user to provide email address (email may be missing if using external login)
if (PageState.User != null && PageState.User.IsAuthenticated && string.IsNullOrEmpty(PageState.User.Email) && PageState.Route.PagePath != "profile")
{
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, "profile", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
return;
}
// set page title
if (!string.IsNullOrEmpty(PageState.Page.Title))
{
@ -44,7 +53,6 @@
}
// head content
AddHeadContent(headcontent, PageState.Site.HeadContent);
if (!string.IsNullOrEmpty(PageState.Site.HeadContent))
{
headcontent = AddHeadContent(headcontent, PageState.Site.HeadContent);
@ -66,32 +74,34 @@
{
if (!string.IsNullOrEmpty(content))
{
if (PageState.RenderMode == RenderModes.Interactive)
var elements = content.Split('<', StringSplitOptions.RemoveEmptyEntries);
foreach (var element in elements)
{
// remove scripts
var index = content.IndexOf("<script");
while (index >= 0)
if (PageState.RenderMode == RenderModes.Static || (!element.ToLower().StartsWith("script") && !element.ToLower().StartsWith("/script")))
{
content = content.Remove(index, content.IndexOf("</script>") + 9 - index);
index = content.IndexOf("<script");
if (!headcontent.Contains("<" + element) || element.StartsWith("/"))
{
headcontent += "<" + element;
}
}
}
headcontent += content + "\n";
headcontent += "\n";
}
return headcontent;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// force authenticated user to provide email address (email may be missing if using external login)
if (PageState.User != null && PageState.User.IsAuthenticated && string.IsNullOrEmpty(PageState.User.Email) && PageState.Route.PagePath != "profile")
{
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, "profile", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
return;
}
if (!firstRender)
if (!firstRender && PageState.Page.Path != lastPagePath)
{
if (!string.IsNullOrEmpty(PageState.Site.HeadContent) && PageState.Site.HeadContent.Contains("<script"))
{
await InjectScripts(PageState.Site.HeadContent, ResourceLocation.Head);
}
if (!string.IsNullOrEmpty(PageState.Site.BodyContent) && PageState.Site.BodyContent.Contains("<script"))
{
await InjectScripts(PageState.Site.BodyContent, ResourceLocation.Body);
}
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
{
await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head);
@ -100,6 +110,7 @@
{
await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body);
}
lastPagePath = PageState.Page.Path;
}
// style sheets
@ -176,18 +187,20 @@
if (!string.IsNullOrEmpty(src))
{
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location.ToString().ToLower(), dataAttributes = dataAttributes });
scripts.Add(new { href = src, type = type, bundle = "", integrity = integrity, crossorigin = crossorigin, location = location.ToString().ToLower(), dataAttributes = dataAttributes });
}
else
{
// inline script must have an id attribute
if (id == "")
if (dataAttributes == null || !dataAttributes.ContainsKey("data-reload") || dataAttributes["data-reload"] != "false")
{
count += 1;
id = $"page{PageState.Page.PageId}-script{count}";
if (id == "")
{
count += 1;
id = $"page{PageState.Page.PageId}-script{count}";
}
var pos = script.IndexOf(">") + 1;
await interop.IncludeScript(id, "", "", "", type, script.Substring(pos, script.IndexOf("</script>") - pos), location.ToString().ToLower(), dataAttributes);
}
index = script.IndexOf(">") + 1;
await interop.IncludeScript(id, "", "", "", "", script.Substring(index, script.IndexOf("</script>") - index), location.ToString().ToLower());
}
index = content.IndexOf("<script", index + 1);
}

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.2.0</Version>
<TargetFramework>net9.0</TargetFramework>
<Version>6.0.1</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/v5.2.0</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,8 +33,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="MySql.Data" Version="9.0.0" />
<PackageReference Include="MySql.EntityFrameworkCore" Version="9.0.0-preview" />
<PackageReference Include="MySql.Data" Version="9.1.0" />
</ItemGroup>
<ItemGroup>
@ -42,7 +42,7 @@
</ItemGroup>
<ItemGroup>
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)MySql.EntityFrameworkCore.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net8.0\%(Filename)%(Extension)" />
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)MySql.EntityFrameworkCore.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
</ItemGroup>
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(MySQLFiles)" Outputs="@(MySQLFiles->'%(DestinationPath)')">

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.2.1</Version>
<TargetFramework>net9.0</TargetFramework>
<Version>6.0.1</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/v5.2.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -25,7 +25,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;EF1001;AD0001</NoWarn>
<NoWarn>1701;1702;EF1001;AD0001;NU1608</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@ -33,9 +33,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
</ItemGroup>
<ItemGroup>
@ -43,7 +43,7 @@
</ItemGroup>
<ItemGroup>
<PostgreSQLFiles Include="$(OutputPath)Oqtane.Database.PostgreSQL.dll;$(OutputPath)Oqtane.Database.PostgreSQL.pdb;$(OutputPath)EFCore.NamingConventions.dll;$(OutputPath)Npgsql.EntityFrameworkCore.PostgreSQL.dll;$(OutputPath)Npgsql.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net8.0\%(Filename)%(Extension)" />
<PostgreSQLFiles Include="$(OutputPath)Oqtane.Database.PostgreSQL.dll;$(OutputPath)Oqtane.Database.PostgreSQL.pdb;$(OutputPath)EFCore.NamingConventions.dll;$(OutputPath)Npgsql.EntityFrameworkCore.PostgreSQL.dll;$(OutputPath)Npgsql.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
</ItemGroup>
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(PostgreSQLFiles)" Outputs="@(PostgreSQLFiles->'%(DestinationPath)')">

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.2.1</Version>
<TargetFramework>net9.0</TargetFramework>
<Version>6.0.1</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/v5.2.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
@ -41,7 +41,7 @@
</ItemGroup>
<ItemGroup>
<SqlServerFiles Include="$(OutputPath)Oqtane.Database.SqlServer.dll;$(OutputPath)Oqtane.Database.SqlServer.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.SqlServer.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net8.0\%(Filename)%(Extension)" />
<SqlServerFiles Include="$(OutputPath)Oqtane.Database.SqlServer.dll;$(OutputPath)Oqtane.Database.SqlServer.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.SqlServer.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
</ItemGroup>
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(SqlServerFiles)" Outputs="@(SqlServerFiles->'%(DestinationPath)')">

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.2.1</Version>
<TargetFramework>net9.0</TargetFramework>
<Version>6.0.1</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/v5.2.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
@ -41,7 +41,7 @@
</ItemGroup>
<ItemGroup>
<SqliteFiles Include="$(OutputPath)Oqtane.Database.Sqlite.dll;$(OutputPath)Oqtane.Database.Sqlite.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.Sqlite.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net8.0\%(Filename)%(Extension)" />
<SqliteFiles Include="$(OutputPath)Oqtane.Database.Sqlite.dll;$(OutputPath)Oqtane.Database.Sqlite.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.Sqlite.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
</ItemGroup>
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(SqliteFiles)" Outputs="@(SqliteFiles->'%(DestinationPath)')">

View File

@ -5,7 +5,10 @@ public partial class App : Application
public App()
{
InitializeComponent();
MainPage = new MainPage();
}
protected override Window CreateWindow(IActivationState activationState)
{
return new Window(new MainPage());
}
}

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType>
<Version>5.2.1</Version>
<Version>6.0.1</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/v5.2.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace>
@ -28,19 +28,21 @@
<!-- App Identifier -->
<ApplicationId>com.oqtane.maui</ApplicationId>
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions -->
<ApplicationDisplayVersion>5.2.1</ApplicationDisplayVersion>
<ApplicationDisplayVersion>6.0.1</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
<WindowsPackageType>None</WindowsPackageType>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<!-- App Icon -->
@ -65,23 +67,22 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.80" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.80" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.80" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.0" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="Oqtane.Client">
<HintPath>..\Oqtane.Server\bin\Debug\net8.0\Oqtane.Client.dll</HintPath>
<HintPath>..\Oqtane.Server\bin\Debug\net9.0\Oqtane.Client.dll</HintPath>
</Reference>
<Reference Include="Oqtane.Shared">
<HintPath>..\Oqtane.Server\bin\Debug\net8.0\Oqtane.Shared.dll</HintPath>
<HintPath>..\Oqtane.Server\bin\Debug\net9.0\Oqtane.Shared.dll</HintPath>
</Reference>
</ItemGroup>

View File

@ -1,7 +1,7 @@
{
"profiles": {
"Windows Machine": {
"commandName": "MsixPackage",
"commandName": "Project",
"nativeDebugging": false
}
}

View File

@ -35,6 +35,9 @@ app {
}
/* Action Dialog */
.app-actiondialog{
position: absolute;
}
.app-actiondialog .modal {
position: fixed; /* Stay in place */
z-index: 9999; /* Sit on top */
@ -230,5 +233,41 @@ app {
}
.app-form-inline {
display: inline-block;
display: inline;
}
.app-search{
display: inline-block;
position: relative;
}
.app-search input + button{
background: none;
border: none;
position: absolute;
right: 0;
top: 0;
}
.app-search input + button .oi{
top: 0;
}
.app-search-noinput {
display: inline-block;
position: relative;
}
.app-search-noinput button {
background: none;
border: none;
color: var(--bs-heading-color);
}
.app-search-noinput button:hover {
color: var(--bs-heading-color);
}
/* Text Editor */
.text-area-editor > textarea {
width: 100%;
min-height: 250px;
}
.app-logo .navbar-brand {
padding: 5px 20px 5px 20px;
}

View File

@ -1,11 +1,18 @@
var Oqtane = Oqtane || {};
Oqtane.Interop = {
setCookie: function (name, value, days) {
setCookie: function (name, value, days, secure, sameSite) {
var d = new Date();
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
var cookieString = name + "=" + value + ";" + expires + ";path=/";
if (secure) {
cookieString += "; secure";
}
if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") {
cookieString += "; SameSite=" + sameSite;
}
document.cookie = cookieString;
},
getCookie: function (name) {
name = name + "=";
@ -198,7 +205,9 @@ Oqtane.Interop = {
}
promises.push(new Promise((resolve, reject) => {
if (loadjs.isDefined(bundles[b])) {
resolve(true);
loadjs.ready(bundles[b], () => {
resolve(true);
});
}
else {
loadjs(urls, bundles[b], {
@ -206,18 +215,25 @@ Oqtane.Interop = {
returnPromise: true,
before: function (path, element) {
for (let s = 0; s < scripts.length; s++) {
if (path === scripts[s].href && scripts[s].integrity !== '') {
element.integrity = scripts[s].integrity;
}
if (path === scripts[s].href && scripts[s].crossorigin !== '') {
element.crossOrigin = scripts[s].crossorigin;
}
if (path === scripts[s].href && scripts[s].es6module === true) {
element.type = "module";
}
if (path === scripts[s].href && scripts[s].location === 'body') {
document.body.appendChild(element);
return false; // return false to bypass default DOM insertion mechanism
if (path === scripts[s].href) {
if (scripts[s].integrity !== '') {
element.integrity = scripts[s].integrity;
}
if (scripts[s].crossorigin !== '') {
element.crossOrigin = scripts[s].crossorigin;
}
if (scripts[s].es6module === true) {
element.type = "module";
}
if (typeof scripts[s].dataAttributes !== "undefined" && scripts[s].dataAttributes !== null) {
for (var key in scripts[s].dataAttributes) {
element.setAttribute(key, scripts[s].dataAttributes[key]);
}
}
if (scripts[s].location === 'body') {
document.body.appendChild(element);
return false; // return false to bypass default DOM insertion mechanism
}
}
}
}
@ -286,41 +302,49 @@ Oqtane.Interop = {
},
uploadFiles: function (posturl, folder, id, antiforgerytoken, jwt) {
var fileinput = document.getElementById('FileInput_' + id);
var files = fileinput.files;
var progressinfo = document.getElementById('ProgressInfo_' + id);
var progressbar = document.getElementById('ProgressBar_' + id);
if (progressinfo !== null && progressbar !== null) {
progressinfo.setAttribute("style", "display: inline;");
progressinfo.innerHTML = '';
progressbar.setAttribute("style", "width: 100%; display: inline;");
progressbar.value = 0;
}
var files = fileinput.files;
var totalSize = 0;
for (var i = 0; i < files.length; i++) {
var FileChunk = [];
var file = files[i];
var MaxFileSizeMB = 1;
var BufferChunkSize = MaxFileSizeMB * (1024 * 1024);
var FileStreamPos = 0;
var EndPos = BufferChunkSize;
var Size = file.size;
totalSize = totalSize + files[i].size;
}
while (FileStreamPos < Size) {
FileChunk.push(file.slice(FileStreamPos, EndPos));
FileStreamPos = EndPos;
EndPos = FileStreamPos + BufferChunkSize;
var maxChunkSizeMB = 1;
var bufferChunkSize = maxChunkSizeMB * (1024 * 1024);
var uploadedSize = 0;
for (var i = 0; i < files.length; i++) {
var fileChunk = [];
var file = files[i];
var fileStreamPos = 0;
var endPos = bufferChunkSize;
while (fileStreamPos < file.size) {
fileChunk.push(file.slice(fileStreamPos, endPos));
fileStreamPos = endPos;
endPos = fileStreamPos + bufferChunkSize;
}
var TotalParts = FileChunk.length;
var PartCount = 0;
var totalParts = fileChunk.length;
var partCount = 0;
while (Chunk = FileChunk.shift()) {
PartCount++;
var FileName = file.name + ".part_" + PartCount.toString().padStart(3, '0') + "_" + TotalParts.toString().padStart(3, '0');
while (chunk = fileChunk.shift()) {
partCount++;
var fileName = file.name + ".part_" + partCount.toString().padStart(3, '0') + "_" + totalParts.toString().padStart(3, '0');
var data = new FormData();
data.append('__RequestVerificationToken', antiforgerytoken);
data.append('folder', folder);
data.append('formfile', Chunk, FileName);
data.append('formfile', chunk, fileName);
var request = new XMLHttpRequest();
request.open('POST', posturl, true);
if (jwt !== "") {
@ -328,28 +352,36 @@ Oqtane.Interop = {
request.withCredentials = true;
}
request.upload.onloadstart = function (e) {
if (progressinfo !== null && progressbar !== null) {
progressinfo.innerHTML = file.name + ' 0%';
progressbar.value = 0;
if (progressinfo !== null && progressbar !== null && progressinfo.innerHTML === '') {
if (files.length === 1) {
progressinfo.innerHTML = file.name;
}
else {
progressinfo.innerHTML = file.name + ", ...";
}
}
};
request.upload.onprogress = function (e) {
if (progressinfo !== null && progressbar !== null) {
var percent = Math.ceil((e.loaded / e.total) * 100);
progressinfo.innerHTML = file.name + '[' + PartCount + '] ' + percent + '%';
var percent = Math.ceil(((uploadedSize + e.loaded) / totalSize) * 100);
progressbar.value = (percent / 100);
}
};
request.upload.onloadend = function (e) {
if (progressinfo !== null && progressbar !== null) {
progressinfo.innerHTML = file.name + ' 100%';
progressbar.value = 1;
uploadedSize = uploadedSize + e.total;
var percent = Math.ceil((uploadedSize / totalSize) * 100);
progressbar.value = (percent / 100);
}
};
request.upload.onerror = function() {
if (progressinfo !== null && progressbar !== null) {
progressinfo.innerHTML = file.name + ' Error: ' + request.statusText;
progressbar.value = 0;
if (files.length === 1) {
progressinfo.innerHTML = file.name + ' Error: ' + request.statusText;
}
else {
progressinfo.innerHTML = ' Error: ' + request.statusText;
}
}
};
request.send(data);
@ -385,11 +417,20 @@ Oqtane.Interop = {
}
},
scrollTo: function (top, left, behavior) {
window.scrollTo({
top: top,
left: left,
behavior: behavior
});
const modal = document.querySelector('.modal');
if (modal) {
modal.scrollTo({
top: top,
left: left,
behavior: behavior
});
} else {
window.scrollTo({
top: top,
left: left,
behavior: behavior
});
}
},
scrollToId: function (id) {
var element = document.getElementById(id);

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<package>
<metadata>
<id>Oqtane.Client</id>
<version>5.2.1</version>
<version>6.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,13 +12,15 @@
<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/v5.2.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>
<files>
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.dll" target="lib\net8.0" />
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.pdb" target="lib\net8.0" />
<file src="..\Oqtane.Client\bin\Release\net9.0\Oqtane.Client.dll" target="lib\net9.0" />
<file src="..\Oqtane.Client\bin\Release\net9.0\Oqtane.Client.pdb" target="lib\net9.0" />
<file src="icon.png" target="" />
<file src="readme.md" target="" />
</files>
</package>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<package>
<metadata>
<id>Oqtane.Framework</id>
<version>5.2.1</version>
<version>6.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -11,12 +11,14 @@
<copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.2.1/Oqtane.Framework.5.2.1.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</releaseNotes>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.0.1/Oqtane.Framework.6.0.1.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane framework</tags>
</metadata>
<files>
<file src="icon.png" target="" />
<file src="readme.md" target="" />
</files>
</package>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<package>
<metadata>
<id>Oqtane.Server</id>
<version>5.2.1</version>
<version>6.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,13 +12,15 @@
<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/v5.2.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>
<files>
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.dll" target="lib\net8.0" />
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.pdb" target="lib\net8.0" />
<file src="..\Oqtane.Server\bin\Release\net9.0\Oqtane.Server.dll" target="lib\net9.0" />
<file src="..\Oqtane.Server\bin\Release\net9.0\Oqtane.Server.pdb" target="lib\net9.0" />
<file src="icon.png" target="" />
<file src="readme.md" target="" />
</files>
</package>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<package>
<metadata>
<id>Oqtane.Shared</id>
<version>5.2.1</version>
<version>6.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,13 +12,15 @@
<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/v5.2.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>
<files>
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.dll" target="lib\net8.0" />
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.pdb" target="lib\net8.0" />
<file src="..\Oqtane.Shared\bin\Release\net9.0\Oqtane.Shared.dll" target="lib\net9.0" />
<file src="..\Oqtane.Shared\bin\Release\net9.0\Oqtane.Shared.pdb" target="lib\net9.0" />
<file src="icon.png" target="" />
<file src="readme.md" target="" />
</files>
</package>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<package>
<metadata>
<id>Oqtane.Updater</id>
<version>5.2.1</version>
<version>6.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,12 +12,14 @@
<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/v5.2.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>
<files>
<file src="..\Oqtane.Updater\bin\Release\net8.0\publish\*.*" target="lib\net8.0" />
<file src="..\Oqtane.Updater\bin\Release\net9.0\publish\*.*" target="lib\net9.0" />
<file src="icon.png" target="" />
<file src="readme.md" target="" />
</files>
</package>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.1.Install.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Install.zip" -Force

Binary file not shown.

9
Oqtane.Package/readme.md Normal file
View File

@ -0,0 +1,9 @@
# Oqtane Framework
![Oqtane](https://github.com/oqtane/framework/blob/master/oqtane.png?raw=true "Oqtane")
Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a fully dynamic digital experience which can be hosted on Static Blazor, Blazor Server, Blazor WebAssembly, or Blazor Hybrid (via .NET MAUI).
Oqtane was created by [Shaun Walker](https://www.linkedin.com/in/shaunbrucewalker/) and was inspired by his earlier efforts with DotNetNuke... however Oqtane is a native Blazor application written from the ground up using modern .NET Core technology and a Single Page Application (SPA) architecture. It is a modular application framework offering a fully dynamic page compositing model, multi-site support, designer friendly themes, and extensibility via third party modules.
More information about Oqtane can be found at: [https://www.oqtane.org](https://www.oqtane.org)

View File

@ -6,14 +6,22 @@ nuget.exe pack Oqtane.Client.nuspec
nuget.exe pack Oqtane.Server.nuspec
nuget.exe pack Oqtane.Shared.nuspec
nuget.exe pack Oqtane.Framework.nuspec
del /F/Q/S "..\Oqtane.Server\bin\Release\net8.0\publish" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net8.0\publish"
del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish"
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
del /F/Q/S "..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Content" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Content"
del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content"
setlocal ENABLEDELAYEDEXPANSION
set retain=Placeholder.txt
for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\_content\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText
for /D %%i in ("..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Modules\*") do (
for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Modules\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
@ -21,18 +29,18 @@ if "%%~nxi" == "%%j" set /A found=1
if not !found! == 1 rmdir /Q/S "%%i"
)
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme
for /D %%i in ("..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Themes\*") do (
for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Themes\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
del "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.json"
ren "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.release.json" "appsettings.json"
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
del "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.json"
del "..\Oqtane.Server\bin\Release\net8.0\publish\web.config"
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
del "..\Oqtane.Server\bin\Release\net9.0\publish\web.config"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1"
dotnet clean -c Release ..\Oqtane.Updater.sln
dotnet build -c Release ..\Oqtane.Updater.sln

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.1.Upgrade.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Upgrade.zip" -Force

View File

@ -76,6 +76,10 @@
@((MarkupString)_scripts)
@((MarkupString)_bodyResources)
@if (_renderMode == RenderModes.Static)
{
<page-script src="./js/reload.js"></page-script>
}
}
else
{
@ -179,10 +183,6 @@
ManageScripts(resources, alias);
// generate scripts
if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server)
{
_scripts += CreateReconnectScript();
}
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
{
_scripts += CreatePWAScript(alias, site, route);
@ -192,32 +192,30 @@
_scripts += CreateScrollPositionScript();
}
_headResources += ParseScripts(site.HeadContent);
_bodyResources += ParseScripts(site.BodyContent);
// set culture if not specified
string culture = Context.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
if (culture == null)
string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName];
if (cultureCookie == null)
{
// get default language for site
if (site.Languages.Any())
{
// use default language if specified otherwise use first language in collection
culture = (site.Languages.Where(l => l.IsDefault).SingleOrDefault() ?? site.Languages.First()).Code;
cultureCookie = (site.Languages.Where(l => l.IsDefault).SingleOrDefault() ?? site.Languages.First()).Code;
}
else
{
culture = LocalizationManager.GetDefaultCulture();
// fallback language
cultureCookie = LocalizationManager.GetDefaultCulture();
}
SetLocalizationCookie(culture);
// convert language code to culture cookie format (ie. "c=en|uic=en")
cultureCookie = Shared.CookieRequestCultureProvider.MakeCookieValue(new Models.RequestCulture(cultureCookie));
SetLocalizationCookie(cultureCookie);
}
// set language for page
if (!string.IsNullOrEmpty(culture))
if (!string.IsNullOrEmpty(cultureCookie))
{
// localization cookie value in form of c=en|uic=en
_language = culture.Split('|')[0];
_language = _language.Replace("c=", "");
_language = Shared.CookieRequestCultureProvider.ParseCookieValue(cultureCookie).Culture.Name;
}
// create initial PageState
@ -494,25 +492,6 @@
"</script>" + Environment.NewLine;
}
private string CreateReconnectScript()
{
return Environment.NewLine +
"<script>" + Environment.NewLine +
" // Interactive Blazor Server Reconnect" + Environment.NewLine +
" new MutationObserver((mutations, observer) => {" + Environment.NewLine +
" if (document.querySelector('#components-reconnect-modal h5 a')) {" + Environment.NewLine +
" async function attemptReload() {" + Environment.NewLine +
" await fetch('');" + Environment.NewLine +
" location.reload();" + Environment.NewLine +
" }" + Environment.NewLine +
" observer.disconnect();" + Environment.NewLine +
" attemptReload();" + Environment.NewLine +
" setInterval(attemptReload, 5000);" + Environment.NewLine +
" }" + Environment.NewLine +
" }).observe(document.body, { childList: true, subtree: true });" + Environment.NewLine +
"</script>" + Environment.NewLine;
}
private string CreateScrollPositionScript()
{
return Environment.NewLine +
@ -522,7 +501,7 @@
" let currentUrl = window.location.pathname;" + Environment.NewLine +
" Blazor.addEventListener('enhancedload', () => {" + Environment.NewLine +
" let newUrl = window.location.pathname;" + Environment.NewLine +
" if (currentUrl != newUrl) {" + Environment.NewLine +
" if (currentUrl !== newUrl || window.location.hash === '#top') {" + Environment.NewLine +
" window.scrollTo({ top: 0, left: 0, behavior: 'instant' });" + Environment.NewLine +
" }" + Environment.NewLine +
" currentUrl = newUrl;" + Environment.NewLine +
@ -532,26 +511,10 @@
"</script>" + Environment.NewLine;
}
private string ParseScripts(string content)
{
// iterate scripts
var scripts = "";
if (!string.IsNullOrEmpty(content))
{
var index = content.IndexOf("<script");
while (index >= 0)
{
scripts += content.Substring(index, content.IndexOf("</script>", index) + 9 - index);
index = content.IndexOf("<script", index + 1);
}
}
return scripts;
}
private void AddScript(Resource resource, Alias alias)
{
var script = CreateScript(resource, alias);
if (resource.Location == Shared.ResourceLocation.Head)
if (resource.Location == Shared.ResourceLocation.Head && !resource.Reload)
{
if (!_headResources.Contains(script))
{
@ -569,52 +532,45 @@
private string CreateScript(Resource resource, Alias alias)
{
if (!string.IsNullOrEmpty(resource.Url))
if (!resource.Reload)
{
if (!resource.Reload)
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
var dataAttributes = "";
if (resource.DataAttributes != null && resource.DataAttributes.Count > 0)
{
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
return "<script" +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((resource.ES6Module) ? " type=\"module\"" : "") +
" src=\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
}
else
{
// use custom element which can execute script on every page transition
@if (string.IsNullOrEmpty(resource.Integrity) && string.IsNullOrEmpty(resource.CrossOrigin))
foreach (var attribute in resource.DataAttributes)
{
return "<page-script src=\"" + resource.Url + "\"></page-script>";
}
else
{
// use modulepreload for external resources
return "<link rel=\"modulepreload\" href=\"" + resource.Url + "\" integrity=\"" + resource.Integrity + "\" crossorigin=\"" + resource.CrossOrigin + "\" />\n" +
"<page-script src=\"" + resource.Url + "\"></page-script>";
dataAttributes += " " + attribute.Key + "=\"" + attribute.Value + "\"";
}
}
return "<script src=\"" + url + "\"" +
((!string.IsNullOrEmpty(resource.Type)) ? " type=\"" + resource.Type + "\"" : "") +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
"></script>";
}
else
{
// inline script
return "<script>" + resource.Content + "</script>";
return "<page-script src=\"" + resource.Url + "\"></page-script>";
}
}
private void SetLocalizationCookie(string culture)
private void SetLocalizationCookie(string cookieValue)
{
var cookieOptions = new Microsoft.AspNetCore.Http.CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute
Secure = true, // Ensure the cookie is only sent over HTTPS
HttpOnly = true // Optional: Helps mitigate XSS attacks
HttpOnly = false // cookie is updated using JS Interop in Interactive render mode
};
Context.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
Shared.CookieRequestCultureProvider.DefaultCookieName,
cookieValue,
cookieOptions
);
}
@ -644,6 +600,16 @@
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode);
}
}
// theme settings components are dynamically loaded within the framework Page Management module
if (page.Path == "admin/pages" && action.ToLower() == "edit" && theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
var settingsType = Type.GetType(theme.ThemeSettingsType);
if (settingsType != null)
{
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, site.RenderMode);
}
}
foreach (Module module in modules.Where(item => item.PageId == page.PageId || item.ModuleId == moduleid))
{
@ -686,25 +652,49 @@
// ensure component exists and implements IModuleControl
module.ModuleType = "";
Type moduletype = Type.GetType(typename, false, true); // case insensitive
var moduletype = Type.GetType(typename, false, true); // case insensitive
if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
{
module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name
}
if (moduletype != null && module.ModuleType != "")
{
var obj = Activator.CreateInstance(moduletype) as IModuleControl;
if (obj != null)
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
if (moduleobject != null)
{
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
// settings components are dynamically loaded within the framework Settings module
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{
// settings components are embedded within a framework settings module
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
// module settings component
var settingsType = "";
if (!string.IsNullOrEmpty(module.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
settingsType = module.ModuleDefinition.SettingsType;
}
else
{
// legacy support - module settings type determined by convention
settingsType = module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action);
}
moduletype = Type.GetType(settingsType, false, true);
if (moduletype != null)
{
obj = Activator.CreateInstance(moduletype) as IModuleControl;
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
}
// container settings component
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
moduletype = Type.GetType(theme.ContainerSettingsType);
if (moduletype != null)
{
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
}
}
}
}

View File

@ -17,11 +17,10 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Extensions;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Png;
using System.Net.Http;
using Microsoft.AspNetCore.Cors;
using System.IO.Compression;
using Oqtane.Services;
// ReSharper disable StringIndexOfIsCultureSpecific.1
@ -38,7 +37,9 @@ namespace Oqtane.Controllers
private readonly ILogManager _logger;
private readonly Alias _alias;
private readonly ISettingRepository _settingRepository;
public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISettingRepository settingRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
private readonly IImageService _imageService;
public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISettingRepository settingRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager, IImageService imageService)
{
_environment = environment;
_files = files;
@ -48,6 +49,7 @@ namespace Oqtane.Controllers
_logger = logger;
_alias = tenantManager.GetAlias();
_settingRepository = settingRepository;
_imageService = imageService;
}
// GET: api/<controller>?folder=x
@ -425,11 +427,11 @@ namespace Oqtane.Controllers
// POST api/<controller>/upload
[EnableCors(Constants.MauiCorsPolicy)]
[HttpPost("upload")]
public async Task UploadFile(string folder, IFormFile formfile)
public async Task<IActionResult> UploadFile(string folder, IFormFile formfile)
{
if (formfile == null || formfile.Length <= 0)
{
return;
return NoContent();
}
// ensure filename is valid
@ -437,7 +439,7 @@ namespace Oqtane.Controllers
if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token) || !HasValidFileExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token))))
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
return;
return NoContent();
}
string folderPath = "";
@ -492,6 +494,8 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
return NoContent();
}
private async Task<string> MergeFile(string folder, string filename)
@ -513,7 +517,7 @@ namespace Oqtane.Controllers
bool success = true;
using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create))
{
foreach (string filepart in fileparts)
foreach (string filepart in fileparts.Order())
{
try
{
@ -679,22 +683,18 @@ namespace Oqtane.Controllers
var filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath))
{
// 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 = "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";
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png");
string format = "png";
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format);
if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate))
{
// user has edit access to folder or folder supports the image size being created
if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) ||
(!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString()))))
{
imagepath = CreateImage(filepath, width, height, mode, position, background, rotate, imagepath);
imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, format, imagepath);
}
else
{
@ -741,70 +741,6 @@ namespace Oqtane.Controllers
return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null;
}
private string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath)
{
try
{
using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
{
stream.Position = 0;
using (var image = Image.Load(stream))
{
int.TryParse(rotate, out int angle);
Enum.TryParse(mode, true, out ResizeMode resizemode);
Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode);
PngEncoder encoder;
if (background != "transparent")
{
image.Mutate(x => x
.AutoOrient() // auto orient the image
.Rotate(angle)
.Resize(new ResizeOptions
{
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height),
PadColor = Color.ParseHex("#" + background)
}));
encoder = new PngEncoder();
}
else
{
image.Mutate(x => x
.AutoOrient() // auto orient the image
.Rotate(angle)
.Resize(new ResizeOptions
{
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height)
}));
encoder = new PngEncoder
{
ColorType = PngColorType.RgbWithAlpha,
TransparentColorMode = PngTransparentColorMode.Preserve,
BitDepth = PngBitDepth.Bit8,
CompressionLevel = PngCompressionLevel.BestSpeed
};
}
image.Save(imagepath, encoder);
}
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, ex, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Rotate} {Error}", filepath, width, height, mode, rotate, ex.Message);
imagepath = "";
}
return imagepath;
}
private string GetFolderPath(string folder)
{
return Utilities.PathCombine(_environment.ContentRootPath, folder);

View File

@ -55,6 +55,10 @@ namespace Oqtane.Controllers
else
{
languages = _languages.GetLanguages(SiteId).ToList();
foreach (Language language in languages)
{
language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName;
}
if (!string.IsNullOrEmpty(packagename))
{
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
@ -85,6 +89,7 @@ namespace Oqtane.Controllers
var language = _languages.GetLanguage(id);
if (language != null && language.SiteId == _alias.SiteId)
{
language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName;
return language;
}
else

View File

@ -351,9 +351,9 @@ namespace Oqtane.Controllers
return new Dictionary<string, object>()
{
{ "FrameworkVersion", Constants.Version },
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>" },
{ "ServerReference", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Server.dll</HintPath></Reference>" },
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>" },
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Client.dll</HintPath></Reference>" },
{ "ServerReference", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Server.dll</HintPath></Reference>" },
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Shared.dll</HintPath></Reference>" },
};
});
}

View File

@ -8,10 +8,6 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Security;
using System.Net;
using System.Reflection.Metadata;
using Microsoft.Extensions.Localization;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Linq;
namespace Oqtane.Controllers
{
@ -187,7 +183,7 @@ namespace Oqtane.Controllers
{
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && notification.NotificationId == id && _notifications.GetNotification(notification.NotificationId, false) != null && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId)))
{
if (!User.IsInRole(RoleNames.Admin))
if (!User.IsInRole(RoleNames.Admin) && notification.FromUserId != null)
{
// content must be HTML encoded for non-admins to prevent HTML injection
notification.Subject = WebUtility.HtmlEncode(notification.Subject);
@ -227,7 +223,7 @@ namespace Oqtane.Controllers
private bool IsAuthorized(int? userid)
{
bool authorized = true;
bool authorized = false;
if (userid != null)
{
authorized = (_userPermissions.GetUser(User).UserId == userid);

View File

@ -9,7 +9,7 @@ using System.Net;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.IO;
using System;
namespace Oqtane.Controllers
{

View File

@ -189,7 +189,7 @@ namespace Oqtane.Controllers
public void Delete(string entityName, int entityId, string settingName)
{
Setting setting = _settings.GetSetting(entityName, entityId, settingName);
if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
if (setting != null && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
{
_settings.DeleteSetting(setting.EntityName, setting.SettingId);
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Delete);
@ -199,7 +199,7 @@ namespace Oqtane.Controllers
{
if (entityName != EntityNames.Visitor)
{
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "User Not Authorized To Delete Setting {Setting}", setting);
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "Setting Does Not Exist Or User Not Authorized To Delete Setting For Entity {EntityName} Id {EntityId} Name {SettingName}", entityName, entityId, settingName);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
@ -269,11 +269,7 @@ namespace Oqtane.Controllers
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName);
break;
case EntityNames.User:
authorized = true;
if (permissionName == PermissionNames.Edit)
{
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
}
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
break;
case EntityNames.Visitor:
authorized = User.IsInRole(RoleNames.Admin);
@ -319,7 +315,7 @@ namespace Oqtane.Controllers
filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit);
break;
case EntityNames.User:
filter = !User.IsInRole(RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId;
filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId;
break;
case EntityNames.Visitor:
if (!User.IsInRole(RoleNames.Admin))

View File

@ -267,8 +267,8 @@ namespace Oqtane.Controllers
return new Dictionary<string, object>()
{
{ "FrameworkVersion", Constants.Version },
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>" },
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>" },
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Client.dll</HintPath></Reference>" },
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Shared.dll</HintPath></Reference>" },
};
});
}

View File

@ -6,7 +6,6 @@ using System.Threading.Tasks;
using System.Linq;
using System.Security.Claims;
using Oqtane.Shared;
using System;
using System.Net;
using Oqtane.Enums;
using Oqtane.Infrastructure;
@ -28,9 +27,11 @@ namespace Oqtane.Controllers
private readonly IUserPermissions _userPermissions;
private readonly IJwtManager _jwtManager;
private readonly IFileRepository _files;
private readonly ISettingRepository _settings;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ILogManager logger)
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ISettingRepository settings, ISyncManager syncManager, ILogManager logger)
{
_users = users;
_tenantManager = tenantManager;
@ -39,6 +40,8 @@ namespace Oqtane.Controllers
_userPermissions = userPermissions;
_jwtManager = jwtManager;
_files = files;
_settings = settings;
_syncManager = syncManager;
_logger = logger;
}
@ -110,31 +113,45 @@ namespace Oqtane.Controllers
private User Filter(User user)
{
// clone object to avoid mutating cache
User filtered = null;
if (user != null)
{
user.Password = "";
user.IsAuthenticated = false;
user.TwoFactorCode = "";
user.TwoFactorExpiry = null;
filtered = new User();
if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower())
// public properties
filtered.SiteId = user.SiteId;
filtered.UserId = user.UserId;
filtered.Username = user.Username;
filtered.DisplayName = user.DisplayName;
// restricted properties
filtered.Password = "";
filtered.TwoFactorCode = "";
filtered.SecurityStamp = "";
// include private properties if authenticated user is accessing their own user account os is an administrator
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId)
{
user.Email = "";
user.PhotoFileId = null;
user.LastLoginOn = DateTime.MinValue;
user.LastIPAddress = "";
user.Roles = "";
user.CreatedBy = "";
user.CreatedOn = DateTime.MinValue;
user.ModifiedBy = "";
user.ModifiedOn = DateTime.MinValue;
user.DeletedBy = "";
user.DeletedOn = DateTime.MinValue;
user.IsDeleted = false;
user.TwoFactorRequired = false;
filtered.Email = user.Email;
filtered.PhotoFileId = user.PhotoFileId;
filtered.LastLoginOn = user.LastLoginOn;
filtered.LastIPAddress = user.LastIPAddress;
filtered.TwoFactorRequired = user.TwoFactorRequired;
filtered.Roles = user.Roles;
filtered.CreatedBy = user.CreatedBy;
filtered.CreatedOn = user.CreatedOn;
filtered.ModifiedBy = user.ModifiedBy;
filtered.ModifiedOn = user.ModifiedOn;
filtered.DeletedBy = user.DeletedBy;
filtered.DeletedOn = user.DeletedOn;
filtered.IsDeleted = user.IsDeleted;
filtered.Settings = user.Settings; // include all settings
}
}
return user;
return filtered;
}
// POST api/<controller>
@ -147,11 +164,13 @@ namespace Oqtane.Controllers
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
{
user.EmailConfirmed = true;
user.IsAuthenticated = true;
allowregistration = true;
}
else
{
user.EmailConfirmed = false;
user.IsAuthenticated = false;
allowregistration = _sites.GetSite(user.SiteId).AllowRegistration;
}
@ -233,8 +252,26 @@ namespace Oqtane.Controllers
[Authorize]
public async Task Logout([FromBody] User user)
{
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : "");
if (_userPermissions.GetUser(User).UserId == user.UserId)
{
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, "Logout");
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : "");
}
}
// POST api/<controller>/logout
[HttpPost("logouteverywhere")]
[Authorize]
public async Task LogoutEverywhere([FromBody] User user)
{
if (_userPermissions.GetUser(User).UserId == user.UserId)
{
await _userManager.LogoutUserEverywhere(user);
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, "Logout");
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout Everywhere {Username}", (user != null) ? user.Username : "");
}
}
// POST api/<controller>/verify
@ -301,6 +338,13 @@ namespace Oqtane.Controllers
return user;
}
// GET api/<controller>/validate/x
[HttpGet("validateuser")]
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
{
return await _userManager.ValidateUser(username, email, password);
}
// GET api/<controller>/validate/x
[HttpGet("validate/{password}")]
public async Task<bool> Validate(string password)
@ -355,6 +399,7 @@ namespace Oqtane.Controllers
}
if (roles != "") roles = ";" + roles;
user.Roles = roles;
user.SecurityStamp = User.SecurityStamp();
}
return user;
}

View File

@ -10,7 +10,6 @@ using System.Linq;
using System.Net;
using Oqtane.Security;
using System;
using Oqtane.Modules.Admin.Roles;
namespace Oqtane.Controllers
{
@ -40,24 +39,34 @@ namespace Oqtane.Controllers
public IEnumerable<UserRole> Get(string siteid, string userid = null, string rolename = null)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null || rolename != null))
int UserId = -1;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null && int.TryParse(userid, out UserId) || rolename != null))
{
var userroles = _userRoles.GetUserRoles(SiteId).ToList();
if (userid != null)
if (IsAuthorized(UserId, rolename))
{
int UserId = int.TryParse(userid, out UserId) ? UserId : -1;
userroles = userroles.Where(item => item.UserId == UserId).ToList();
var userroles = _userRoles.GetUserRoles(SiteId).ToList();
if (UserId != -1)
{
userroles = userroles.Where(item => item.UserId == UserId).ToList();
}
if (rolename != null)
{
userroles = userroles.Where(item => item.Role.Name == rolename).ToList();
}
var user = _userPermissions.GetUser();
for (int i = 0; i < userroles.Count(); i++)
{
userroles[i] = Filter(userroles[i], user.UserId);
}
return userroles.OrderBy(u => u.User.DisplayName);
}
if (rolename != null)
else
{
userroles = userroles.Where(item => item.Role.Name == rolename).ToList();
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UserRole Get Attempt For Site {SiteId} User {UserId} Role {RoleName}", siteid, userid, rolename);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
var user = _userPermissions.GetUser();
for (int i = 0; i < userroles.Count(); i++)
{
userroles[i] = Filter(userroles[i], user.UserId);
}
return userroles.OrderBy(u => u.User.DisplayName);
}
else
{
@ -73,7 +82,7 @@ namespace Oqtane.Controllers
public UserRole Get(int id)
{
var userrole = _userRoles.GetUserRole(id);
if (userrole != null && SiteValid(userrole.Role.SiteId))
if (userrole != null && SiteValid(userrole.Role.SiteId) && IsAuthorized(userrole.UserId, userrole.Role.Name))
{
return Filter(userrole, _userPermissions.GetUser().UserId);
}
@ -92,33 +101,57 @@ namespace Oqtane.Controllers
}
}
private bool IsAuthorized(int userId, string roleName)
{
bool authorized = true;
if (userId != -1)
{
authorized = _userPermissions.GetUser(User).UserId == userId;
}
if (authorized && !string.IsNullOrEmpty(roleName))
{
authorized = User.IsInRole(roleName);
}
return authorized;
}
private UserRole Filter(UserRole userrole, int userid)
{
// clone object to avoid mutating cache
UserRole filtered = null;
if (userrole != null)
{
userrole.User.Password = "";
userrole.User.IsAuthenticated = false;
userrole.User.TwoFactorCode = "";
userrole.User.TwoFactorExpiry = null;
filtered = new UserRole();
if (!_userPermissions.IsAuthorized(User, userrole.User.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && userid != userrole.User.UserId)
// public properties
filtered.UserRoleId = userrole.UserRoleId;
filtered.UserId = userrole.UserId;
filtered.RoleId = userrole.RoleId;
filtered.User = new User();
filtered.User.SiteId = userrole.User.SiteId;
filtered.User.UserId = userrole.User.UserId;
filtered.User.Username = userrole.User.Username;
filtered.User.DisplayName = userrole.User.DisplayName;
filtered.Role = new Role();
filtered.Role.SiteId = userrole.Role.SiteId;
filtered.Role.RoleId = userrole.Role.RoleId;
filtered.Role.Name = userrole.Role.Name;
// include private properties if administrator
if (_userPermissions.IsAuthorized(User, filtered.User.SiteId, EntityNames.UserRole, -1, PermissionNames.Write, RoleNames.Admin))
{
userrole.User.Email = "";
userrole.User.PhotoFileId = null;
userrole.User.LastLoginOn = DateTime.MinValue;
userrole.User.LastIPAddress = "";
userrole.User.Roles = "";
userrole.User.CreatedBy = "";
userrole.User.CreatedOn = DateTime.MinValue;
userrole.User.ModifiedBy = "";
userrole.User.ModifiedOn = DateTime.MinValue;
userrole.User.DeletedBy = "";
userrole.User.DeletedOn = DateTime.MinValue;
userrole.User.IsDeleted = false;
userrole.User.TwoFactorRequired = false;
filtered.User.Email = userrole.User.Email;
filtered.User.PhotoFileId = userrole.User.PhotoFileId;
filtered.User.LastLoginOn = userrole.User.LastLoginOn;
filtered.User.LastIPAddress = userrole.User.LastIPAddress;
filtered.User.CreatedOn = userrole.User.CreatedOn;
}
}
return userrole;
return filtered;
}
// POST api/<controller>

View File

@ -1,6 +1,4 @@
using System.Collections.Generic;
using Oqtane.Databases.Interfaces;
using Oqtane.Interfaces;
namespace Oqtane.Repository.Databases.Interfaces
{

View File

@ -1,6 +1,5 @@
using System.Linq;
using System.Security.Claims;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Extensions
@ -41,9 +40,9 @@ namespace Oqtane.Extensions
public static string SiteKey(this ClaimsPrincipal claimsPrincipal)
{
if (claimsPrincipal.HasClaim(item => item.Type == "sitekey"))
if (claimsPrincipal.HasClaim(item => item.Type == Constants.SiteKeyClaimType))
{
return claimsPrincipal.Claims.FirstOrDefault(item => item.Type == "sitekey").Value;
return claimsPrincipal.Claims.FirstOrDefault(item => item.Type == Constants.SiteKeyClaimType).Value;
}
else
{
@ -71,6 +70,18 @@ namespace Oqtane.Extensions
return -1;
}
public static string SecurityStamp(this ClaimsPrincipal claimsPrincipal)
{
if (claimsPrincipal.HasClaim(item => item.Type == Constants.SecurityStampClaimType))
{
return claimsPrincipal.Claims.FirstOrDefault(item => item.Type == Constants.SecurityStampClaimType).Value;
}
else
{
return "";
}
}
public static bool IsOnlyInRole(this ClaimsPrincipal claimsPrincipal, string role)
{
var identity = claimsPrincipal.Identities.FirstOrDefault(item => item.AuthenticationType == Constants.AuthenticationScheme);

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