Compare commits

...

988 Commits

Author SHA1 Message Date
f56e102f20 Merge pull request #3045 from oqtane/master
Merge pull request #3044 from oqtane/dev
2023-07-18 12:25:59 -04:00
010c669ce2 Merge pull request #3044 from oqtane/dev
4.0.1 Release
2023-07-18 12:25:41 -04:00
9609e7b297 Merge pull request #3042 from leigh-pointer/ControlPanelPadding
Fixed up margins in Control Panel
2023-07-18 11:08:57 -04:00
92718372b8 Fixed up margins in Control Panel
Added top margin to the Module Management
2023-07-18 12:47:48 +02:00
354ad9d0d5 Merge pull request #3040 from sbwalker/dev
use html line breaks in error log notifications
2023-07-17 16:59:54 -04:00
100b92036b use html line breaks in error log notifications 2023-07-17 16:59:40 -04:00
e9ed1ecc8c Merge pull request #3039 from sbwalker/dev
prevent client looping if server is down
2023-07-17 11:49:16 -04:00
92595ccc35 prevent client looping if server is down 2023-07-17 11:49:06 -04:00
1ef28494b3 Merge pull request #3038 from sbwalker/dev
use discretion with LogLevel.Error messages
2023-07-17 09:29:00 -04:00
985d324593 use discretion with LogLevel.Error messages 2023-07-17 09:28:29 -04:00
96e7178cd2 Merge pull request #3036 from sbwalker/dev
Added logic to ensure assembly version being installed is equal to or greater than existing assembly
2023-07-17 08:16:16 -04:00
f41e2358a9 Added logic to ensure assembly version being installed is equal to or greater than existing assembly 2023-07-17 08:15:56 -04:00
0f146ff355 Merge pull request #3035 from sbwalker/dev
Added a ShowProgress parameter to FileManager (enabled by default). Disabling will display a simple spinner rather than detailed progress information during upload.
2023-07-17 07:43:18 -04:00
465f241e89 Added a ShowProgress parameter to FileManager (enabled by default). Disabling will display a simple spinner rather than detailed progress information during upload. 2023-07-17 07:42:48 -04:00
2bdb011240 Merge pull request #3026 from vnetonline/enhance-filemanager
[ENHANCE] - Moved `SetImage()` before the OnSelect fires so that the …
2023-07-17 07:11:41 -04:00
dd55725f9b Merge pull request #3034 from vnetonline/fix-publish-module
[FIX] - Page disappears if you unpublish and publish a module
2023-07-17 07:10:55 -04:00
411111fa55 [FIX] - Page disappears if you unpublish and publish a module on the page 2023-07-17 09:33:04 +10:00
5345a12aca [ENHANCE] - Moved SetImage() before the OnSelect fires so that the Parent can retrieve the Image from fileManager rather then calling FileService to retrieve again 2023-07-17 07:58:39 +10:00
39adcde16d Merge pull request #3030 from sbwalker/dev
display message if package service does not return package requested
2023-07-15 15:27:01 -04:00
3c37ee60af display message if package service does not return package requested 2023-07-15 15:26:48 -04:00
4effc4dd60 Merge pull request #3028 from sbwalker/dev
marketplace changes
2023-07-15 10:01:41 -04:00
fec9fa17e8 marketplace changes 2023-07-15 10:01:27 -04:00
fca306ffa6 Merge pull request #3025 from sbwalker/dev
SupportUrl integration
2023-07-14 15:19:04 -04:00
0e042925f2 SupportUrl integration 2023-07-14 15:18:52 -04:00
99421846c7 Merge pull request #3024 from sbwalker/dev
remove OnSelect call from OnParametersSet() which was causing infinite loop
2023-07-14 12:15:34 -04:00
592885ff6c remove OnSelect call from OnParametersSet() which was causing infinite loop 2023-07-14 12:15:16 -04:00
a95306c317 Merge pull request #3022 from sbwalker/dev
moved UserManager to Managers namespace
2023-07-13 17:05:21 -04:00
8bdbf7b994 moved UserManager to Managers namespace 2023-07-13 17:05:01 -04:00
aed65d2594 Merge pull request #3020 from sbwalker/dev
add handling for DisplayName
2023-07-13 11:17:04 -04:00
63b3e4d90d add handling for DisplayName 2023-07-13 11:16:49 -04:00
f9c459eab2 Merge pull request #3019 from sbwalker/dev
fix bash script line endings and preserve using gitattributes (credit @rcpacheco in #2958)
2023-07-13 10:19:14 -04:00
a6d83149a4 fix bash script line endings and preserve using gitattributes (credit @rcpacheco in #2958) 2023-07-13 10:18:52 -04:00
387b477034 Merge pull request #3018 from sbwalker/dev
allow page themes to be different from site theme - display warning message
2023-07-13 09:09:00 -04:00
68cdcd193c allow page themes to be different from site theme - display warning message 2023-07-13 08:58:43 -04:00
08438c7725 Merge pull request #2964 from OpenEugene/release/v4.0.0
Show all themes on page add/edit
2023-07-13 07:52:34 -04:00
1894b46d3a Merge pull request #3016 from leigh-pointer/UserSort
User management sort
2023-07-13 07:41:40 -04:00
367a825955 Merge pull request #3017 from sbwalker/dev
added logging to ServiceBase to capture HTTP errors
2023-07-13 07:40:55 -04:00
be799eb254 added logging to ServiceBase to capture HTTP errors 2023-07-13 07:39:24 -04:00
4820a27016 User management sort
Added Sorting to User management component.
2023-07-13 12:54:08 +02:00
4e82212956 Merge pull request #3015 from sbwalker/dev
fix issue for site folders
2023-07-12 19:49:56 -04:00
6b0f5ed12d fix issue for site folders 2023-07-12 19:49:38 -04:00
0a258caf48 Merge pull request #3013 from sbwalker/dev
remove unnecessary using statement
2023-07-12 17:59:34 -04:00
f546a68f7c remove unnecessary using statement 2023-07-12 17:59:21 -04:00
1e5bab09db Merge pull request #3012 from sbwalker/dev
migrate remaining methods to UserManager
2023-07-12 17:52:29 -04:00
35fa18a852 migrate remaining methods to UserManager 2023-07-12 17:52:16 -04:00
90c1eeb312 Merge pull request #3009 from ajahangard/dev
Using DynamicComponent Instead of RenderFragment in ContainerBuilder
2023-07-12 16:40:09 -04:00
404c32b49b Merge pull request #3011 from sbwalker/dev
add a UserManager to simplify user creation, improve response validation in ServiceBase, allow Section component to support parameter changes
2023-07-12 16:37:54 -04:00
c0f4cd2097 add a UserManager to simplify user creation, improve response validation in ServiceBase, allow Section component to support parameter changes 2023-07-12 16:37:18 -04:00
fa98908dea Merge branch 'oqtane:dev' into dev 2023-07-12 12:43:40 +03:30
23e8567e86 Using DynamicComponent Instead of RenderFragment in ContainerBuilder 2023-07-12 12:43:00 +03:30
a02e28f493 Merge pull request #3005 from leigh-pointer/DropIsPublic
The Down path is incorrect
2023-07-11 08:59:39 -04:00
d6a7e32ca9 Merge pull request #3006 from sbwalker/dev
support both 404 and 403 status codes in GET API response (404 should not log)
2023-07-11 08:14:52 -04:00
df0f562817 support both 404 andf 403 status codes in API response (404 should not log) 2023-07-11 08:14:00 -04:00
0dea2bc79e The Down path is incorrect
The Down Path drops the wrong column
2023-07-11 08:37:23 +02:00
dc1d1015ed Merge pull request #2999 from sbwalker/dev
prepare for 4.0.1 release
2023-07-10 16:51:20 -04:00
59fffbd3ee prepare for 4.0.1 release 2023-07-10 16:51:03 -04:00
655cbca574 Merge pull request #2998 from sbwalker/dev
add module name to the Module Settings UI
2023-07-10 16:36:57 -04:00
e22c28a3c9 add module name to the Module Settings UI 2023-07-10 16:36:37 -04:00
e823e371f6 Merge pull request #2997 from sbwalker/dev
fix #2976 - add logging methods to ThemeBase
2023-07-10 16:26:49 -04:00
feae7d6269 fix #2976 - add logging methods to ThemeBase 2023-07-10 16:26:30 -04:00
e1c0ea0720 Merge pull request #2996 from sbwalker/dev
fix #2977 - margin between edit and cog
2023-07-10 16:20:33 -04:00
4e1a389c8b fix #2977 - margin between edit and cog 2023-07-10 16:20:16 -04:00
0362a6a3dc Merge pull request #2995 from sbwalker/dev
fix #2978 - allow host users and admins to have personalized pages
2023-07-10 16:14:14 -04:00
3891dea009 fix #2978 - allow host users and admins to have personalized pages 2023-07-10 16:13:56 -04:00
1ccb7e2092 Merge pull request #2994 from sbwalker/dev
prevent logging of error for personalized pages
2023-07-10 14:51:58 -04:00
62ad99d0b6 prevent logging of error for personalized pages 2023-07-10 14:51:32 -04:00
66d07fbed3 Merge pull request #2980 from vnetonline/fix-ispersonalised
Fixing personalized page created to be UserName if DisplayName is null
2023-07-10 14:28:55 -04:00
ec117adb2d Merge pull request #2993 from sbwalker/dev
make GetHttpClient() public
2023-07-10 14:16:35 -04:00
3aff31b826 make GetHttpClient() public 2023-07-10 14:16:17 -04:00
1621944105 Merge pull request #2992 from sbwalker/dev
refactored upload so that it is not dependent on Folder Browse permission
2023-07-10 11:44:28 -04:00
16d0d11e0b refactored upload so that it is not dependent on Folder Browse permission 2023-07-10 11:44:05 -04:00
0b81f28e99 Merge pull request #2991 from sbwalker/dev
fix validation issue in FileManager related to Browse permissions
2023-07-10 10:10:16 -04:00
2ccb814223 fix validation issue in FileManager related to Browse permissions 2023-07-10 10:09:57 -04:00
f213abd70a Merge pull request #2990 from sbwalker/dev
fix migration error caused by new IsRead field in Notifications. Microsoft.Data.SqlClient.SqlException: 'ALTER TABLE only allows columns to be added that can contain nulls, or have a DEFAULT definition specified.'
2023-07-10 08:59:37 -04:00
b87eddeeda fix migration error caused by new IsRead field in Notifications. Microsoft.Data.SqlClient.SqlException: 'ALTER TABLE only allows columns to be added that can contain nulls, or have a DEFAULT definition specified.' 2023-07-10 08:59:09 -04:00
92fc56e70d Merge pull request #2988 from leigh-pointer/TemplateConfig
Remove Build for Oqtane Server in ConfigManger
2023-07-10 08:45:16 -04:00
e22f3238fb Merge pull request #2989 from sbwalker/dev
add API method to get File based on name, and fix permission validation for Folder
2023-07-10 08:44:41 -04:00
c597c4c234 add API method to get File based on name, and fix permission validation for Folder 2023-07-10 08:44:14 -04:00
5973c3d1a0 Remove Build for Oqtane Server in ConfigManger
Remove Build for Oqtane Server in ConfigManger
2023-07-10 14:40:00 +02:00
d50472cd7b Merge pull request #2984 from leigh-pointer/ConfigExtern
Fix for #2983 Oqtane. Server selected in for build in configmanager
2023-07-10 07:44:46 -04:00
11604bbcfd Merge pull request #2985 from vnetonline/fix-modulesettings-resource-type-name
[FIX] - Fixed the ResourceType in Module Settings with correct NameSpace
2023-07-10 07:41:34 -04:00
cf428598d5 [FIX] - Fixed the Resource Type in Module Settings with correct Namespace 2023-07-10 12:52:02 +10:00
a45fc0c4df Fix for #2983 Oqtane. Server selected in for build in configmanager
Removed Oqtane. Server build from configuration manager debug and release
2023-07-09 21:30:45 +02:00
5abf0df154 Merge pull request #2982 from sbwalker/dev
Package enhancements for Marketplace
2023-07-09 08:36:34 -04:00
9a3b458c45 Package enhancements for Marketplace 2023-07-09 08:36:14 -04:00
1b2de37c4e Merge pull request #2975 from vnetonline/enhance-notifications-isread
[ENHANCE] - Added IsRead property to Notifications
2023-07-08 10:07:44 -04:00
f7338bf00e Update GetNotifications (read) to retrieve descending oder by CreatedOn at repository level 2023-07-07 10:21:22 +10:00
bad7be39a6 Fixing personalized page created to be UserName if DisplayName is null 2023-07-07 09:21:49 +10:00
40459defa4 Cosmetic change to more the filter drop down to top of the notifications tab 2023-07-06 15:28:11 +10:00
b7de4b81a6 [ENHANCE] - Added IsRead property to Notifications
Fixed Version to Tenant.04.00.01.01 and reverted the Program.cs back to the way it was

This reverts commit 82fef82c4f.

[ENHANCE] - Added API to get Count of New Notifications based on IsRead

Fixed Typo in Notification Controller

[ENHANCE] - Added API to get Notifications by Count and IsRead
2023-07-06 01:02:05 +10:00
563695cdfa Merge pull request #2967 from leigh-pointer/RemoveRazorLangVersion 2023-07-01 16:03:48 -04:00
fd30112ee8 Removed RazorLangVersion tag from Client project files
as to discussion #2965
2023-07-01 14:41:03 +02:00
a36794b98b Merge pull request #1 from OpenEugene/markdav-is-patch-theme-dropdown
Show all themes when editing
2023-06-30 17:18:30 -07:00
e52653469d Merge pull request #2 from OpenEugene/markdav-is-patch-add-themes
show all themes when adding
2023-06-30 17:18:14 -07:00
b61035a3a3 show all themes when adding 2023-06-30 17:12:14 -07:00
b3039e71be Show all themes when editing 2023-06-30 17:07:46 -07:00
2c614f3bb6 Merge pull request #2961 from leigh-pointer/UnPubPage
Fixes #2960 - cant unpublish page.
2023-06-30 15:43:52 -04:00
07a27b25e0 Merge pull request #2959 from leigh-pointer/ModActRoleButt
ModuleActions not displaying the correct mouse pointer
2023-06-30 15:43:34 -04:00
4c080572ac Merge pull request #2940 from vnetonline/dev
Added ThemeSettings and ContainerSettings to Default Theme Template
2023-06-30 15:43:22 -04:00
95cd6ea48e Fixes #2960 - cant unpublish page.
Added the reverse code to change a published page to unpublished.
2023-06-30 08:53:09 +02:00
2e3c57226f ModuleActions not displaying the correct mouse pointer
Upated the <ul> tag with a role attribute button.
2023-06-30 08:20:57 +02:00
69f7367a18 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-30 08:17:33 +02:00
8ff7f0bf5e Merge branch 'dev' of https://github.com/vnetonline/oqtane.framework into dev 2023-06-30 08:04:26 +10:00
e2a9470f31 Adding Solution file back to Default Theme Template
Added ThemeSettings and ContainerSettings to Default Theme Template

Added ThemeSettings and ContainerSettings to Default Theme Template to Demonstrate how these features could be added to developers designing a theme as requested and discussed in Issue #2633

Includeda few setting options as per suggestion by @sbwalker  intended to provide a simple demonstration of how to use Theme and Container settings
2023-06-30 08:04:13 +10:00
a2a64e73bc Merge branch 'oqtane:dev' into dev 2023-06-30 07:41:49 +10:00
15305e3a0b Merge pull request #2956 from leigh-pointer/RoleDates
Closes #2955 User Roles Effective and Expiry date Rendering as Text
2023-06-29 14:42:17 -04:00
bcb484ba62 Closes #2955 User Roles Effective and Expiry date Rendering as Text
Updated the User Roles to use Date control and fixed the code to accept DateTime values
2023-06-29 19:17:38 +02:00
278fd8023b Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-29 18:37:15 +02:00
ba7efb45e7 Merge pull request #2954 from sbwalker/dev
handle paths during Folder add/update
2023-06-29 12:24:37 -04:00
8d3d218067 handle paths during Folder add/update 2023-06-29 12:24:20 -04:00
5185934f25 Merge pull request #2953 from sbwalker/dev
fix progress bar styling in FileManager
2023-06-29 11:54:20 -04:00
0a9cbfd0e5 fix progress bar styling in FileManager 2023-06-29 11:54:05 -04:00
35e237b5b3 Merge pull request #2952 from sbwalker/dev
allow FileManager to support changes in parameters
2023-06-29 11:21:02 -04:00
ba18cd505b allow FileManager to support changes in parameters 2023-06-29 11:20:45 -04:00
662df53a10 Fixed Indenting in ThemeSettings.cs 2023-06-29 13:22:51 +10:00
eadf2e89a8 Fix HasChildren property not considering if page is not in navigation (i.e. hidden) 2023-06-29 11:56:19 +10:00
a3b54b9891 Merge branch 'oqtane:dev' into dev 2023-06-29 11:05:12 +10:00
18cab2c759 Merge pull request #2950 from sbwalker/dev
allow admin to navigate to site settings for deleted site
2023-06-28 13:11:29 -04:00
8edc4fd67a allow admin to navigate to site settings for deleted site 2023-06-28 13:11:15 -04:00
b914163499 Merge pull request #2949 from sbwalker/dev
fix #2941 - HasChildren property not considering deleted pages
2023-06-28 13:05:46 -04:00
c55c8bff6e fix #2941 - HasChildren property not considering deleted pages 2023-06-28 13:05:34 -04:00
e669bb1f82 Merge pull request #2948 from sbwalker/dev
fix #2942 - reverse ordering of page name and site name for page title
2023-06-28 12:51:11 -04:00
452c3fd355 fix #2942 - reverse ordering of page name and site name for page title 2023-06-28 12:50:58 -04:00
60be0c92da Merge pull request #2947 from sbwalker/dev
improve helptext for Aliases
2023-06-28 12:44:16 -04:00
03bd88a2d5 improve helptext for Aliases 2023-06-28 12:44:00 -04:00
7f355698bc Merge pull request #2946 from sbwalker/dev
use case insensitive comparison for activetab name
2023-06-28 12:27:34 -04:00
22ec675cec use case insensitive comparison for activetab name 2023-06-28 12:27:21 -04:00
3954c482ce Merge pull request #2945 from sbwalker/dev
set Expanded to lowercase if specified
2023-06-28 12:25:13 -04:00
d06feec37b set Expanded to lowercase if specified 2023-06-28 12:24:59 -04:00
536af96ccc Merge pull request #2944 from sbwalker/dev
fix #2938 - path not updated correctly when parent page changed
2023-06-28 12:17:42 -04:00
e1a8d3db54 fix #2938 - path not updated correctly when parent page changed 2023-06-28 12:17:27 -04:00
83d51a85ce Adding Solution file back to Default Theme Template 2023-06-28 22:32:42 +10:00
4400744f35 Added ThemeSettings and ContainerSettings to Default Theme Template
Added ThemeSettings and ContainerSettings to Default Theme Template to Demonstrate how these features could be added to developers designing a theme as requested and discussed in Issue #2633
2023-06-28 22:25:07 +10:00
ea4b85ad54 Merge pull request #2939 from sbwalker/dev
filter deleted sites in notification job
2023-06-27 11:42:36 -04:00
60b6a9ce47 filter deleted sites in notification job 2023-06-27 11:42:15 -04:00
927e00bd66 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-27 09:31:07 +02:00
cd8d2dd8f3 Update README.md 2023-06-26 14:36:53 -04:00
6861965779 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-26 20:14:07 +02:00
8a9a1d7df7 Merge pull request #2937 from sbwalker/dev
cosmetic fix for input elements within a table intoduced with Bootstrap 5.3
2023-06-26 14:00:03 -04:00
e7e66699b1 cosmetic fix for input elements within a table intoduced with Bootstrap 5.3 2023-06-26 13:59:44 -04:00
681321feeb Merge pull request #2936 from oqtane/master
Merge pull request #2935 from oqtane/dev
2023-06-26 11:31:16 -04:00
a637b6d4d0 Merge pull request #2935 from oqtane/dev
4.0.0 Release
2023-06-26 11:30:59 -04:00
97026746f8 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-26 14:57:48 +02:00
5161ade786 Merge pull request #2932 from sbwalker/dev
added deprecation message for IHostResources
2023-06-26 08:12:01 -04:00
6dd62a164e added deprecation message for IHostResources 2023-06-26 08:11:46 -04:00
3bece7599c Merge pull request #2931 from sbwalker/dev
fix validation message behavior in installer
2023-06-25 09:10:07 -04:00
b2f3a53980 fix validation message behavior in installer 2023-06-25 09:09:53 -04:00
cea31a8573 Merge pull request #2930 from sbwalker/dev
remove A/B testing logic for stylesheets - discard root component approach in favor of legacy JS Interop approach to eliminate FOUC issues
2023-06-25 08:39:41 -04:00
ce6647a84a remove A/B testing logic for stylesheets - discard root component approach in favor of legacy JS Interop approach to eliminate FOUC issues 2023-06-25 08:39:12 -04:00
d54ba9a5c6 Merge pull request #2929 from sbwalker/dev
added logging to HostedServiceBase
2023-06-23 15:19:33 -04:00
1e18d913e0 added logging to HostedServiceBase 2023-06-23 15:19:18 -04:00
ef8c47a49f Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-23 14:32:00 +02:00
0041c7d3b3 Merge pull request #2928 from sbwalker/dev
allow for testing CSS page transitions
2023-06-23 08:16:56 -04:00
f6cca5cb35 allow for testing CSS page transitions 2023-06-23 08:16:39 -04:00
fa5abbf96f Merge pull request #2927 from sbwalker/dev
fix ordering of stylesheets
2023-06-23 08:07:22 -04:00
2ff365765d fix ordering of stylesheets 2023-06-23 08:07:06 -04:00
ee31e4a411 Merge pull request #2926 from sbwalker/dev
clarify documentation and parameter names
2023-06-23 07:44:31 -04:00
ee4068d671 clarify documentation and parameter names 2023-06-23 07:44:15 -04:00
5e86c95db6 Merge pull request #2925 from sbwalker/dev
add validation logic to ensure page theme martches site theme
2023-06-23 07:38:52 -04:00
6126624631 add validation logic to ensure page theme martches site theme 2023-06-23 07:38:33 -04:00
a320e4d48d Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-22 21:59:51 +02:00
a7c5841e76 Merge pull request #2924 from sbwalker/dev
external template changes to support non-Windows environments
2023-06-22 15:24:43 -04:00
0f242a94b4 external template changes to support non-Windows environments 2023-06-22 15:24:27 -04:00
8f8a0897e4 Add files via upload 2023-06-22 15:23:10 -04:00
7c6b64123c Add files via upload 2023-06-22 15:22:10 -04:00
55642093ed Merge pull request #2923 from sbwalker/dev
allow CSS testing using old and new method
2023-06-22 14:05:25 -04:00
5660f40512 allow CSS testing using old and new method 2023-06-22 14:05:08 -04:00
3259b7183b Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-22 17:43:30 +02:00
df7cd648ac Merge pull request #2921 from sbwalker/dev
restrict to a single theme per site
2023-06-22 11:32:21 -04:00
2a5713ed67 restrict to a single theme per site 2023-06-22 11:32:03 -04:00
b7c36c07c6 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-22 06:03:04 +02:00
de93281f3e Merge pull request #2920 from sbwalker/dev
cleanup and ensure site level scripts work properly
2023-06-21 15:49:13 -04:00
86fbdced1b cleanup and ensure site level scripts work properly 2023-06-21 15:49:00 -04:00
43bcfb9a4e Merge pull request #2910 from vnetonline/dev
[ENHANCE] - Added Module to NameSpace [Owner].Module.[Module]
2023-06-21 09:57:36 -04:00
0daf6af3b8 Merge pull request #2918 from sbwalker/dev
fix #2387 - improve container selection in Edit Page
2023-06-21 09:20:23 -04:00
c3bccbade8 fix #2387 - improve container selection in Edit Page 2023-06-21 09:20:05 -04:00
3261628da4 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-21 14:59:49 +02:00
1e39f48235 Merge pull request #2917 from sbwalker/dev
fix null handling in stylesheet logic
2023-06-21 08:47:39 -04:00
05b5d9da9b fix null handling in stylesheet logic 2023-06-21 08:47:25 -04:00
3491dde94a Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-21 14:46:44 +02:00
db2aa920e2 Merge pull request #2916 from sbwalker/dev
fix #2912 - move JavaScript handling from ThemeBuilder to component OnAfterRenderAsync
2023-06-21 08:32:11 -04:00
8067b2e634 fix #2912 - move JavaScript handling from ThemeBuilder to component OnAfterRenderAsync 2023-06-21 08:31:51 -04:00
273c6d7dee Merge branch 'oqtane:dev' into dev 2023-06-21 10:21:30 +10:00
4d0149a4a0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-20 14:53:57 +02:00
d9f3db5bf3 Merge pull request #2914 from sbwalker/dev
integrate old logic for managing stylesheets using JS Interop
2023-06-20 08:52:18 -04:00
c8a679ecce integrate old logic for managing stylesheets using JS Interop 2023-06-20 08:52:02 -04:00
3abe47ae9e [ENHANCE] - Added Module to NameSpace [Owner].Module.[Module]
[ENHANCE] - Added Module to NameSpace [Owner].Module.[Module]
2023-06-20 16:58:30 +10:00
725a8dc237 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-20 07:50:00 +02:00
fcb05a7834 Merge pull request #2908 from sbwalker/dev
fix parent page selection in add page and fix navigation on save/cancel
2023-06-19 16:22:57 -04:00
02e22df4d5 fix parent page selection in add page and fix navigation on save/cancel 2023-06-19 16:22:49 -04:00
694c0af146 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-19 21:52:12 +02:00
8bfca2f10a Merge pull request #2905 from sbwalker/dev
more optimizations of Head component
2023-06-19 15:11:07 -04:00
6bea9a087c more optimizations of Head component 2023-06-19 15:10:58 -04:00
3f4218f75f Merge pull request #2904 from sbwalker/dev
fix #2900 - theme deletion and fallback
2023-06-19 12:29:51 -04:00
3849f59126 fix #2900 - theme deletion and fallback 2023-06-19 12:29:43 -04:00
239b12cbb0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-19 17:03:37 +02:00
8ccee87378 Merge pull request #2898 from vnetonline/dev
[ENHANCE] - Added Theme to Name Space [Owner].Theme.[Theme]
2023-06-19 10:25:01 -04:00
9e518ffc4c Merge pull request #2903 from sbwalker/dev
add defensive logic
2023-06-19 10:11:32 -04:00
e3233fd19f add defensive logic 2023-06-19 10:11:24 -04:00
d3af9b0f14 Merge pull request #2902 from sbwalker/dev
optimize head component rendering
2023-06-19 09:01:53 -04:00
08daca848b optimize head component rendering 2023-06-19 09:01:40 -04:00
c5da8e3b10 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-19 10:32:47 +02:00
3b0ffde1fb [ENHANCE] - Added Theme to Name Space [Owner].Theme.[Theme]
changes all template files to conform to  [Owner].Theme.[Theme]
2023-06-16 14:52:13 +10:00
6ca97e8d5d Merge pull request #2897 from sbwalker/dev
allow selection of site template during installation
2023-06-15 19:10:52 -04:00
34a727b435 allow selection of site template during installation 2023-06-15 19:10:37 -04:00
6a9c865fec Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-14 15:52:56 +02:00
b1b9100600 Merge pull request #2896 from sbwalker/dev
create AppendHeadContent method to consolidate logic
2023-06-14 09:37:48 -04:00
bda0943d58 create AppendHeadContent method to consolidate logic 2023-06-14 09:37:34 -04:00
ad6db71f7e Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-14 15:11:40 +02:00
b21d202c7f Merge pull request #2895 from vnetonline/dev 2023-06-14 07:32:11 -04:00
3ea8ea1e3b Fixing the Library needed for declaring new way of theme resources 2023-06-14 19:13:04 +10:00
ee897a9973 Fixed targeting .NET 7.0 in ThemeController and ModuleDefinitionController missed in #2888 and #2890 2023-06-14 16:51:23 +10:00
bb9ea3f60f Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-14 06:35:28 +02:00
5f8cfb9977 Merge pull request #2893 from sbwalker/dev
update Installer to use new method for including CSS
2023-06-13 17:26:23 -04:00
d7928a70bf update Installer to use new method for including CSS 2023-06-13 17:26:07 -04:00
93568af5a9 Merge pull request #2892 from sbwalker/dev
upgrade external theme template to Bootstrap 5.3
2023-06-13 17:14:13 -04:00
9e86d97253 upgrade external theme template to Bootstrap 5.3 2023-06-13 17:13:59 -04:00
84bac0c462 Merge pull request #2891 from sbwalker/dev
upgrade to Bootstrap 5.3
2023-06-13 17:10:03 -04:00
3c18a258ff upgrade to Bootstrap 5.3 2023-06-13 17:09:49 -04:00
9759c68d58 Merge pull request #2890 from sbwalker/dev
upgrade external theme template to .NET 7
2023-06-13 14:16:58 -04:00
f57a190405 upgrade external theme template to .NET 7 2023-06-13 14:16:43 -04:00
d2eb203aa1 Merge pull request #2889 from sbwalker/dev
fix regression issue from #2852
2023-06-13 14:00:24 -04:00
dd2c2dbe61 fix regression issue from #2852 2023-06-13 14:00:09 -04:00
2529592b66 Merge pull request #2888 from sbwalker/dev
upgraded external module template to .NET 7
2023-06-13 13:10:41 -04:00
f248d2fe4e upgraded external module template to .NET 7 2023-06-13 13:10:20 -04:00
875bcd8ce2 Merge pull request #2885 from sbwalker/dev
handle type for favicon and improve helptext
2023-06-12 12:31:09 -04:00
726f9375e1 handle type for favicon and improve helptext 2023-06-12 12:30:56 -04:00
0c2e200245 Merge pull request #2884 from sbwalker/dev
allow PNG and GIF for favicon specification
2023-06-12 12:00:42 -04:00
a10db462eb allow PNG and GIF for favicon specification 2023-06-12 12:00:25 -04:00
6df28d8171 Merge pull request #2883 from vnetonline/dev
Made the default SQL Server and App Service provisioned Basic Tier
2023-06-12 09:56:34 -04:00
8367f15638 Made the default SQL Server and App Service provisioned Basic Tier 2023-06-12 23:51:36 +10:00
69456d3569 Merge pull request #2881 from sbwalker/dev
move logic for populating theme control names
2023-06-10 13:35:44 -04:00
3b644338bc move logic for populating theme control names 2023-06-10 13:35:22 -04:00
02e29aa22f Merge pull request #2880 from sbwalker/dev
fix for child pages in templates
2023-06-10 10:10:59 -04:00
88c489a585 fix for child pages in templates 2023-06-10 10:10:34 -04:00
706e73210d Merge pull request #2879 from sbwalker/dev
fix page template logic on install
2023-06-10 09:18:13 -04:00
d52809c914 fix page template logic on install 2023-06-10 09:17:52 -04:00
6058286577 Update README.md 2023-06-09 12:37:28 -04:00
a271f24157 Merge pull request #2878 from sbwalker/dev
change project tagline
2023-06-09 12:36:27 -04:00
2299375aaa change project tagline 2023-06-09 12:36:15 -04:00
c3290526a4 Merge pull request #2877 from sbwalker/dev
improvements to page template processing in cases where a page parent and name is specified without a path
2023-06-09 12:33:27 -04:00
a9d871e9af improvements to page template processing in cases where a page parent and name is specified without a path 2023-06-09 12:33:07 -04:00
5eb25796ca Merge pull request #2876 from sbwalker/dev
order theme controls and container comtrols in alphabetical order based on name
2023-06-09 08:50:03 -04:00
5d650bd276 order theme controls and container comtrols in alphabetical order based on name 2023-06-09 08:49:41 -04:00
b6be519537 Merge pull request #2875 from sbwalker/dev
add ScrollToPageTop method to ThemeBase
2023-06-08 08:39:33 -04:00
160477d612 add ScrollToPageTop method to ThemeBase 2023-06-08 08:39:20 -04:00
4a89403c37 Merge remote-tracking branch 'origin/dev' into dev 2023-06-08 08:32:54 +02:00
e54cfa629e Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-08 08:32:33 +02:00
b2bc09be16 Merge pull request #2873 from sbwalker/dev
fix to allow Theme Settings to be supported in Page Management
2023-06-07 10:50:39 -04:00
2f5d1cebb0 fix to allow Theme Settings to be supported in Page Management 2023-06-07 10:50:25 -04:00
e4d144651e Merge pull request #2872 from sbwalker/dev
fixed page setting cleanup on delete and centralized module delete logic within PageModuleRepository
2023-06-06 09:11:28 -04:00
ce56dfc239 fixed page setting cleanup on delete and centralized module delete logic within PageModuleRepository 2023-06-06 09:11:08 -04:00
ad4f8fd7a0 Merge pull request #2871 from leigh-pointer/ExtModDescription
Fix for External Module Description not updated
2023-06-06 08:20:48 -04:00
c4070cb603 External Module Description not updated
Fix for External Module Description not updated #2870
2023-06-06 12:26:39 +02:00
75c9322f75 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-06 07:28:06 +02:00
602fd7d658 Merge pull request #2869 from sbwalker/dev
fix JavaScript injection issue on first render for Resources defined in IModule and ITheme
2023-06-05 22:00:27 -04:00
818fd2fb43 fix JavaScript injection issue on first render for Resources defined in IModule and ITheme 2023-06-05 22:00:02 -04:00
e4912fcb09 Merge pull request #2868 from sbwalker/dev
Head root component integration in .NET MAUI
2023-06-05 14:51:06 -04:00
3692ec49c1 Head root component integration in .NET MAUI 2023-06-05 14:50:54 -04:00
9a1ea3f375 Merge pull request #2867 from sbwalker/dev
ability for non-administrators to edit page settings
2023-06-05 14:33:17 -04:00
10a754642a ability for non-administrators to edit page settings 2023-06-05 14:33:05 -04:00
bc9de5d094 Merge pull request #2865 from sbwalker/dev
use ReturnUrl for Page Management invoked by Control Panel
2023-06-05 09:42:54 -04:00
9f93476167 use ReturnUrl for Page Management invoked by Control Panel 2023-06-05 09:42:33 -04:00
f61492d353 Merge pull request #2864 from sbwalker/dev
fix personalization redirect
2023-06-05 09:30:29 -04:00
50cf67546b fix personalization redirect 2023-06-05 09:30:17 -04:00
41d83ec421 Merge pull request #2863 from sbwalker/dev
improvements for personalized pages
2023-06-05 08:22:47 -04:00
cc9377b37d improvements for personalized pages 2023-06-05 08:22:29 -04:00
7f986d5f60 Merge pull request #2862 from sbwalker/dev
fix page add/edit path
2023-06-02 15:24:31 -04:00
d272bf8a29 fix page add/edit path 2023-06-02 15:24:14 -04:00
ba5260f5b1 Merge pull request #2861 from sbwalker/dev
include methods for dynamic scenarios
2023-06-02 08:34:23 -04:00
40c788fc33 include methods for dynamic scenarios 2023-06-02 08:34:05 -04:00
7eca15d50f Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-02 10:42:16 +02:00
8f147e7de2 Merge pull request #2859 from sbwalker/dev
make page path and moduile title lookups case insensitive
2023-06-01 14:41:32 -04:00
85358c7dd4 make page path and moduile title lookups case insensitive 2023-06-01 14:40:59 -04:00
87c7eafb87 Merge pull request #2858 from sbwalker/dev
use constant rather than magic string
2023-06-01 12:41:14 -04:00
613f4848da use constant rather than magic string 2023-06-01 12:41:03 -04:00
3cb06e4819 Update README.md 2023-06-01 12:22:42 -04:00
811701ef7f Merge pull request #2856 from sbwalker/dev
fix log message spelling
2023-06-01 12:16:53 -04:00
c14dc67d8a fix log message spelling 2023-06-01 12:16:41 -04:00
867767f009 Merge pull request #2855 from sbwalker/dev
ensure PageModule Order is updated
2023-06-01 11:29:43 -04:00
640216d076 ensure PageModule Order is updated 2023-06-01 11:29:31 -04:00
3a1f378e2d Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-01 16:58:00 +02:00
6b7c61f228 Merge pull request #2853 from sbwalker/dev
make AliasName explicit
2023-06-01 10:37:53 -04:00
28ba2c00fc make AliasName explicit 2023-06-01 10:37:39 -04:00
b56daae321 Merge pull request #2852 from sbwalker/dev
optimize pane rendering, preserve querystring parameters in edit mode , relocate anchor tags to ensure they are always injected, add ability to determine if navigation is internal
2023-06-01 08:44:30 -04:00
b57450398c optimize pane rendering, preserve querystring parameters in edit mode, relocate anchor tags to ensure they are always injected, add ability to determine if navigation is internal 2023-06-01 08:44:07 -04:00
0c25cf0ec5 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-31 15:42:33 +02:00
c3d884e895 Merge pull request #2851 from sbwalker/dev
added IEventSubscriber amd EventDistributorHostedService to optimize event processing
2023-05-31 09:36:56 -04:00
7a21f96552 added IEventSubscriber amd EventDistributorHostedService to optimize event processing 2023-05-31 09:36:32 -04:00
55cfbb721b Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-31 08:25:26 +02:00
4ccb4c636f Merge pull request #2849 from sbwalker/dev
ability to specify PageTemplates for modules
2023-05-30 15:52:42 -04:00
0d5c3a3a0c ability to specify PageTemplates for modules 2023-05-30 15:52:27 -04:00
6bc1507114 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-26 16:32:21 +02:00
b093cbff15 Merge pull request #2847 from sbwalker/dev
ensure consistent admin dashboard styling
2023-05-26 08:54:58 -04:00
30cb8ec9c3 ensure consistent admin dashboard styling 2023-05-26 08:54:47 -04:00
d5e51d6c38 Merge pull request #2846 from sbwalker/dev
clarify scroll method name
2023-05-26 07:41:59 -04:00
6f0a6c7f69 clarify scroll method name 2023-05-26 07:41:49 -04:00
549d71073e Merge pull request #2845 from sbwalker/dev
add module base class method for ScrollToTop
2023-05-26 07:39:22 -04:00
4ad5522f9e add module base class method for ScrollToTop 2023-05-26 07:39:06 -04:00
2145c1a48c Merge pull request #2844 from sbwalker/dev
utilize new Resources capability in default module/theme
2023-05-25 17:14:14 -04:00
0f093b1238 utilize new Resources capability in default module/theme 2023-05-25 17:14:00 -04:00
1aa3fd7056 Merge pull request #2843 from sbwalker/dev
added validation support for user profile fields
2023-05-25 16:16:28 -04:00
cc4c47c3ee added validation support for user profile fields 2023-05-25 16:16:16 -04:00
6e3f3fac9d Merge pull request #2842 from sbwalker/dev
added ability to disable SMTP and set IsBodyHtml by default
2023-05-25 15:19:20 -04:00
261adefbc7 added ability to disable SMTP and set IsBodyHtml by default 2023-05-25 15:19:05 -04:00
7eafcfd7ad Merge pull request #2841 from sbwalker/dev
add support for Job and Theme settings in API
2023-05-25 15:00:44 -04:00
6183d6a22e add support for Job and Theme settings in API 2023-05-25 15:00:30 -04:00
51d42692ba Merge pull request #2840 from sbwalker/dev
fix site provisioning issue for host module definitions
2023-05-25 14:56:04 -04:00
18a9c059f4 fix site provisioning issue for host module definitions 2023-05-25 14:55:46 -04:00
50a13ffd6d Update README.md 2023-05-25 13:02:03 -04:00
b360944742 Merge pull request #2839 from sbwalker/dev
add ability to modify Theme Name
2023-05-25 12:57:02 -04:00
59d1a47846 add ability to modify Theme Name 2023-05-25 12:56:49 -04:00
22969d8daa Merge pull request #2838 from sbwalker/dev
optimize client assembly download service, add support for site level scripts
2023-05-25 12:32:39 -04:00
95ba87945b optimize client assembly download service, add support for site level scripts 2023-05-25 12:32:21 -04:00
63aeee8b22 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-25 13:14:52 +02:00
da11a86f99 Merge pull request #2837 from sbwalker/dev
ability to specify if a theme is enabled for a site
2023-05-24 13:09:28 -04:00
98c2f012ee ability to specify if a theme is enabled for a site 2023-05-24 13:09:10 -04:00
386c6144f8 Merge pull request #2835 from sbwalker/dev
use SiteKey as a cache key for multi-tenancy
2023-05-24 10:29:57 -04:00
666f9c2db9 use SiteKey as a cache key for multi-tenancy 2023-05-24 10:29:45 -04:00
f7c49588fb Merge pull request #2834 from sbwalker/dev
ability to specify if a module definition is enabled for a site
2023-05-24 09:40:20 -04:00
c0e6f06a5c ability to specify if a module definition is enabled for a site 2023-05-24 09:40:05 -04:00
6668c8ff66 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-23 17:19:25 +02:00
31907f2d16 Merge pull request #2833 from sbwalker/dev
add null check
2023-05-23 11:16:21 -04:00
452d0af8c9 add null check 2023-05-23 11:16:07 -04:00
c5a28f3601 Merge remote-tracking branch 'origin/dev' into dev 2023-05-23 15:55:00 +02:00
d13f26dc7e Merge pull request #2832 from sbwalker/dev
format head content, remove scripts, and filter duplicate elements
2023-05-23 09:12:21 -04:00
03374483e4 format head content, remove scripts, and filter duplicate elements 2023-05-23 09:12:03 -04:00
b1c0a865a0 Merge pull request #2831 from sbwalker/dev
utilize ResourceLocation consistently
2023-05-23 08:08:31 -04:00
7b0799a6f6 utilize ResourceLocation consistently 2023-05-23 08:08:16 -04:00
0de2976933 Merge pull request #2830 from sbwalker/dev
add support for body content
2023-05-22 15:02:49 -04:00
20c7bf3c48 add support for body content 2023-05-22 15:02:36 -04:00
55e090ba2a Merge pull request #2829 from sbwalker/dev
changes to support page level scripts, ability to detect prerendering
2023-05-22 13:57:06 -04:00
ded326c822 changes to support page level scripts, ability to detect prerendering 2023-05-22 13:56:48 -04:00
1ee9c2cf0e Merge pull request #2828 from sbwalker/dev
ability to specify Resources in IModule and ITheme interfaces,, fixed module settings error for personalized pages
2023-05-19 18:08:40 -04:00
e41d9008b3 ability to specify Resources in IModule and ITheme interfaces,, fixed module settings for personalized pages 2023-05-19 18:08:15 -04:00
1ec24251a2 Merge pull request #2827 from sbwalker/dev
remove unused local reference to ThemeType
2023-05-19 11:59:06 -04:00
2be48c3847 remove unused local reference to ThemeType 2023-05-19 11:58:52 -04:00
2137b79f6d Merge pull request #2824 from sbwalker/dev
optimize JavaScript handling
2023-05-18 14:36:19 -04:00
0b8086bd36 optimize JavaScript handling 2023-05-18 14:36:06 -04:00
d16659890c Merge pull request #2823 from sbwalker/dev
add support for type attribute in JSInterop IncludeScript
2023-05-18 09:36:23 -04:00
076d150f72 add support for type attribute in JSInterop IncludeScript 2023-05-18 09:36:09 -04:00
62deffd017 Merge pull request #2822 from sbwalker/dev
move PWA elements back to _Host
2023-05-18 08:37:36 -04:00
f1ec70ff14 move PWA elements back to _Host 2023-05-18 08:37:21 -04:00
af27c763e0 Merge pull request #2821 from sbwalker/dev
handle id attribute automatically for headcontent inline scripts
2023-05-18 08:27:07 -04:00
7336417634 handle id attribute automatically for headcontent inline scripts 2023-05-18 08:26:51 -04:00
cde46c0889 Merge pull request #2820 from sbwalker/dev
allow HeadContent to support script tags
2023-05-17 17:13:25 -04:00
5da4dadc31 allow HeadContent to support script tags 2023-05-17 17:13:08 -04:00
e32ca089ec Merge pull request #2817 from sbwalker/dev
added HeadContent property to Site and replaced Meta property on Page with HeadContent property.
2023-05-16 16:23:34 -04:00
8d2f644177 added HeadContent property to Site and replaced Meta property on Page with HeadContent property. 2023-05-16 16:23:07 -04:00
a003010be5 Merge pull request #2815 from sbwalker/dev
migrate CSS references and remove JS Interop methods
2023-05-16 09:09:32 -04:00
89ada83012 migrate CSS references and remove JS Interop methods 2023-05-16 09:09:18 -04:00
65fbf6926c Merge pull request #2814 from sbwalker/dev
migrate PWA script injection
2023-05-16 08:01:00 -04:00
5e652364c9 migrate PWA script injection 2023-05-16 08:00:48 -04:00
8b7c3d6cfa Merge pull request #2813 from sbwalker/dev
relocate favicon rendering
2023-05-16 07:42:06 -04:00
3b214a0105 relocate favicon rendering 2023-05-16 07:41:50 -04:00
c0c4073bc4 Merge pull request #2811 from sbwalker/dev
ability to add arbitrary content to head and body during client and server rendering
2023-05-15 16:43:40 -04:00
dbe7324c7f ability to add arbitrary content to head and body during client and server rendering 2023-05-15 16:43:22 -04:00
9316255e87 Merge pull request #2809 from sbwalker/dev
added AboutAssets.txt to Maui project and changed assembly references to Oqtane 4.0.0
2023-05-15 13:21:59 -04:00
7f7dff7019 added AboutAssets.txt to Maui project and changed assembly references to Oqtane 4.0.0 2023-05-15 13:21:30 -04:00
961e004a49 Merge pull request #2808 from sbwalker/dev
remove unneccesary package reference to Microsoft.AspNetCore.Mvc.ViewFeatures (not used and deprecated)
2023-05-15 12:58:43 -04:00
02c1e4fb65 remove unneccesary package reference to Microsoft.AspNetCore.Mvc.ViewFeatures (not used and deprecated) 2023-05-15 12:58:14 -04:00
bcbc2a6e95 Merge pull request #2807 from sbwalker/dev
initial changes to upgrade to .NET 7
2023-05-15 12:01:53 -04:00
0c749a126c initial changes to upgrade to .NET 7 2023-05-15 12:01:29 -04:00
3698ebad7c Merge pull request #2804 from sbwalker/dev
Modify the FilterModuleDefinition() method to return null if the object passed to the method is null instead of returning an initialized object
2023-05-11 17:11:02 -04:00
f59a5c90a5 Modify the FilterModuleDefinition() method to return null if the object passed to the method is null instead of returning an initialized object 2023-05-11 17:10:39 -04:00
36e4a69891 Merge pull request #2802 from chlupac/Backslash_fix
FileService backslash fix
2023-05-11 16:58:17 -04:00
19b560f7c1 Update README.md 2023-05-11 11:58:15 -04:00
0bec92e87e FileService backslash fix 2023-05-10 17:08:56 +02:00
a0d02f16cd Update README.md 2023-05-10 08:44:08 -04:00
4a0f655e1c Update README.md 2023-05-09 08:56:42 -04:00
91bf65292d Update README.md 2023-05-09 08:55:07 -04:00
a69d52d688 Merge pull request #2799 from sbwalker/dev
fix #2795 - remove protocol from alias names during add/update
2023-05-08 11:44:18 -04:00
79fe224cea fix #2795 - remove protocol from alias names during add/update 2023-05-08 11:43:59 -04:00
757d8e59a2 Merge pull request #2797 from thabaum/patch-25
Add missing settings excluded from Fix #2727
2023-05-08 07:29:38 -04:00
53ec1416b4 Add missing settings excluded from Fix #2727 2023-05-05 08:44:29 -07:00
e5add21612 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-04 09:15:58 +02:00
d1b52534de Update README.md 2023-05-03 15:46:38 -04:00
35edc053b5 Update README.md 2023-05-03 15:31:34 -04:00
2aa8d3be0f Update README.md 2023-05-03 15:20:55 -04:00
ec0a5377b3 Merge pull request #2784 from oqtane/master
Merge pull request #2783 from oqtane/dev
2023-05-03 14:59:27 -04:00
b876da069d Merge pull request #2783 from oqtane/dev
3.4.3 release
2023-05-03 14:59:09 -04:00
f06063d7eb Merge pull request #2782 from sbwalker/dev
fix #2763 - prevent module definitions from having duplicate names
2023-05-03 12:46:44 -04:00
c6ba4f4bee fix #2763 - prevent module definitions from having duplicate names 2023-05-03 12:46:29 -04:00
89da4ab2a1 Merge pull request #2781 from sbwalker/dev
add defensive logic in case list of pages is empty
2023-05-03 12:32:44 -04:00
99ac0a3cab add defensive logic in case list of pages is empty 2023-05-03 12:32:28 -04:00
a964c30705 Merge pull request #2776 from thabaum/patch-24
Updated Dashboard Index
2023-05-03 12:27:32 -04:00
2334d0297d Merge pull request #2780 from sbwalker/dev
fix #2777 - module rendering order within pane - moved default module ordering logic to server API for consistency and better performance
2023-05-03 12:26:17 -04:00
e444c6bcf0 fix #2777 - module rendering order within pane - moved default module ordering logic to server API for consistency and better performance 2023-05-03 12:25:52 -04:00
601582fc98 Update Index.razor 2023-05-03 08:19:24 -07:00
e939dbe24e Updated SecurityAccessLevel.Admin 2023-05-02 17:14:46 -07:00
143ad85fd5 Update README.md 2023-05-02 16:00:23 -04:00
20377e9789 Merge pull request #2775 from sbwalker/dev
prepare for 3.4.3 release
2023-05-02 15:54:49 -04:00
e4a24df7b4 prepare for 3.4.3 release 2023-05-02 15:54:36 -04:00
e88ca00658 Merge pull request #2770 from thabaum/patch-23
Remove Admin Page/Module Registered User View
2023-05-02 15:41:27 -04:00
2312c612d7 Merge pull request #2774 from sbwalker/dev
remove message items as these are handled by MessageType enum and UX styles
2023-05-02 15:14:34 -04:00
3aee52482d remove message items as these are handled by MessageType enum and UX styles 2023-05-02 15:14:17 -04:00
32248e0be6 Merge pull request #2745 from thabaum/patch-18
Add support for new language translation values for #2732.
2023-05-02 15:05:36 -04:00
6c18c320bd Merge pull request #2773 from sbwalker/dev
fixed compilation error and improved UTF8 support
2023-05-02 15:03:43 -04:00
e31f32e5aa fixed compilation error and improved UTF8 support 2023-05-02 15:03:26 -04:00
a856390566 Merge pull request #2772 from sbwalker/dev
improve module/theme/translation upload user experience to be consistent with download
2023-05-02 14:22:56 -04:00
64b8291487 improve module/theme/translation upload user experience to be consistent with download 2023-05-02 14:22:34 -04:00
2ebd1310c9 Merge pull request #2747 from thabaum/patch-19
Add content-type to sitemap: Fixes issues 2749 2764
2023-05-02 14:20:39 -04:00
09118fcb42 Updates lastmod date 2023-05-02 07:15:22 -07:00
5d5167abbc Merge pull request #2771 from vnetonline/dev
Invalid template when I try and use the Deploy to Azure button
2023-05-02 09:01:41 -04:00
bf43dea060 Merge branch 'oqtane:dev' into dev 2023-05-02 15:53:56 +10:00
6ebab830c5 Invalid template when I try and use the Deploy to Azure button
1. removed depends on connection strings
2. Moved sku outside properties for type Microsoft.Web/serverfarms and added various app plans
3. Removed worker size and replaced with instance capacity
4. Replaced database and sqlserver to use various editions
5. Updated ApiVersions for various types
2023-05-02 15:47:31 +10:00
9a9a78e0bd Remove Admin Page/Module Registered User View 2023-05-01 20:01:49 -07:00
418eff7d21 Merge pull request #2754 from pepsinio/dev
Add ability to use environment variables in order to set them as app settings in Azure
2023-05-01 16:14:47 -04:00
d39869ca8e Merge pull request #2759 from leigh-pointer/MenuHorizontal#2757
Fix for MenuHorizontal #2757
2023-05-01 16:14:16 -04:00
7829168ea7 Merge pull request #2768 from sbwalker/dev
fix #2761 - updating Module Definition name, description, category not invalidating cache
2023-05-01 15:38:46 -04:00
dd83e3ee67 fix #2761 - updating Module Definition name, description, category not invalidating cache 2023-05-01 15:38:18 -04:00
c3ac0e365d Removed PR comments 2023-04-27 16:32:48 -07:00
2986625605 Update content type to XML and include UTF-8 2023-04-27 16:22:35 -07:00
8beaeabf09 Include utf-8 encoding. 2023-04-27 16:21:14 -07:00
fa9b4b6112 Fix for MenuHorizontal #2757
Add the css class to MenuHorizontal to handle scrolling when hamburger Menu is in use.
2023-04-26 09:43:11 +02:00
a20c2aa996 Merge remote-tracking branch 'oqtane/dev' into dev 2023-04-26 09:29:31 +02:00
d81fbe4585 Fixed missing logic from PR 2023-04-19 10:18:27 -07:00
536c044139 Add environment settings needed for Azure deployment 2023-04-19 19:18:19 +02:00
376531195e Add environment settings needed for Azure deployment 2023-04-19 19:17:33 +02:00
abf4ff71d7 re-add missing settings 2023-04-19 10:13:31 -07:00
d25debcea3 cleanup using 2023-04-19 10:11:16 -07:00
948c186cb5 fixed formatting 2023-04-19 10:09:00 -07:00
ba27e70fe3 Removed unnecessary cache comments. 2023-04-19 10:05:43 -07:00
c93d2576af Updates content-type to "application/xml"
removes sitemap cache from previous commits.
2023-04-19 10:00:33 -07:00
e0b0156640 allow module and theme dependencies setting to include .dll file extension, added testmode config setting for validating list of assemblies sent to client 2023-04-19 08:48:52 -07:00
a3ca6a3071 Merge pull request #2753 from sbwalker/dev
allow module and theme dependencies setting to include .dll file extension, added testmode config setting for validating list of assemblies sent to client
2023-04-19 08:46:12 -07:00
b20157450b Add caching and content-type 2023-04-14 18:08:39 -07:00
7e4f0923d7 Add support for new language translation values. 2023-04-13 07:42:04 -07:00
f8c1fde358 Merge remote-tracking branch 'oqtane/dev' into dev 2023-04-13 13:27:33 +02:00
e0c2b2982f improvements to #2736 to support scenarios where module is not explicitly assigned to a page 2023-04-11 13:01:34 -04:00
9a231c28af Merge pull request #2742 from sbwalker/dev
improvements to #2736 to support scenarios where module is not explicitly assigned to a page
2023-04-11 12:59:33 -04:00
94a02b7bf9 add filter to exclude orphaned permissions 2023-04-11 10:35:23 -04:00
22c8fb411a Merge pull request #2741 from sbwalker/dev
add filter to exclude orphaned permissions
2023-04-11 10:33:09 -04:00
cf46210ff8 Merge pull request #2725 from thabaum/patch-17
Fixes null reference permissions issue: Fixes #2724
2023-04-11 10:22:08 -04:00
f32d988297 Merge pull request #2740 from sbwalker/dev
Routes with Module ID and no Action can be displayed on any page regardless of whether a PageModule record exists (ie. Admin Dashboard)
2023-04-11 10:19:28 -04:00
7fe4577158 Routes with Module ID and no Action can be displayed on any page regardless of whether a PageModule record exists (ie. Admin Dashboard) 2023-04-11 10:21:37 -04:00
8985dcb4c0 fix #2736 - UI not loading correct module instance in scenarios where a module exists on multiple pages 2023-04-10 08:37:35 -04:00
d346444c51 Merge pull request #2739 from sbwalker/dev
fix #2736 - UI not loading correct module instance in scenarios where a module exists on multiple pages
2023-04-10 08:35:42 -04:00
113658f3f7 Merge remote-tracking branch 'oqtane/dev' into dev 2023-04-05 18:32:26 +02:00
ef27a0d6b0 Merge pull request #2727 from leigh-pointer/ModulePassSettings
Fix for #2718 SiteMap functionality missing Settings
2023-04-05 10:53:09 -04:00
627158cb5d Merge pull request #2729 from leigh-pointer/PwdControlCancelFocus
Fix for #2728 Toggle Password button focus
2023-04-05 10:51:27 -04:00
648edcabba Merge pull request #2731 from sbwalker/dev
fix #2720 - module definition permissions not being created properly for new sites
2023-04-05 10:27:44 -04:00
0f34c6efc5 fix #2720 - module definition permissions not being created properly for new sites 2023-04-05 10:29:51 -04:00
cc3cc55269 consolidated package installation so that it always occurs during startup and added logging in case of errors 2023-04-05 10:26:21 -04:00
a503a9d5bc Merge pull request #2730 from sbwalker/dev
consolidated package installation so that it always occurs during startup and added logging in case of errors
2023-04-05 10:24:15 -04:00
789baf99ff Fix for #2728 Toggle Password button focus
Added  tabindex="-1" to the Button control so that focus is not received and passed the the following control.
2023-04-05 07:51:32 +02:00
036279a54c Fix for #2718 SiteMap functionality missing Settings
Added the Module Settings to the Module parameter passed in the GetUrls interface call,
2023-04-05 07:31:57 +02:00
481f18cf1c Fixes null reference permissions issue 2023-04-04 11:19:25 -07:00
c54a48865b Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-31 12:42:51 +02:00
2f1e386554 Update README.md 2023-03-29 14:13:27 -04:00
1e0c7cf43d Merge pull request #2706 from oqtane/master
Merge pull request #2705 from oqtane/dev
2023-03-29 14:06:22 -04:00
7978c89731 Merge pull request #2705 from oqtane/dev
3.4.2 release
2023-03-29 14:05:57 -04:00
82221f54c5 add defensive logic to package installer 2023-03-29 08:47:54 -04:00
2e23e6e4d5 Merge pull request #2704 from sbwalker/dev
add defensive logic to package installer
2023-03-29 08:45:46 -04:00
4c4e94dba0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-29 08:38:41 +02:00
3a79fa074a fix #2700 - translation installation 2023-03-28 15:52:07 -04:00
696c63c6d2 Merge pull request #2703 from sbwalker/dev
fix #2700 - translation installation
2023-03-28 15:49:54 -04:00
8f6dc52430 prepare for 3.4.2 release 2023-03-28 14:29:57 -04:00
c8a9ad9807 Merge pull request #2702 from sbwalker/dev
prepare for 3.4.2 release
2023-03-28 14:27:45 -04:00
a0933d07d8 made word casing consistent with other module messages and reduced length of message 2023-03-28 08:53:26 -04:00
6bf61e2008 Merge pull request #2701 from sbwalker/dev
made word casing consistent with other module messages and reduced length of message
2023-03-28 08:51:24 -04:00
36ecc55578 Merge pull request #2697 from leigh-pointer/PwdConstra
Modified Registration to display the Password requirments
2023-03-28 08:17:00 -04:00
47065299ca Merge pull request #2691 from Behnam-Emamian/dev
Code Cleanups
2023-03-28 08:14:09 -04:00
0f707a7607 Moved message to the notification.
To be honest, the message about the password should be visible at all times.
2023-03-25 11:47:15 +01:00
7590c5550f Merge pull request #2698 from sbwalker/dev
Fix #2696 - PermissionNames not appearing in PermissionGrid
2023-03-24 12:52:35 -04:00
3d23a5c79a Fix #2696 - PermissionNames not appearing in PermissionGrid 2023-03-24 12:54:46 -04:00
9aa0374dc2 Removed unused Using 2023-03-24 12:41:27 +01:00
058a191673 Modified Registration to display the Password requirments 2023-03-24 12:38:27 +01:00
ec142cbbe1 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-24 10:42:07 +01:00
5fbb9160f1 Code Cleanups 2023-03-21 00:45:17 +11:00
2c3dad0592 Merge pull request #2689 from sbwalker/dev
Fix #2687 - add Setters to Permissions property to provide improved backward compatibility
2023-03-16 13:24:27 -04:00
00f039d31e Fix #2687 - add Setters to Permissions property to provide improved backward compatibility 2023-03-16 13:26:18 -04:00
497ef1750b add defensive logic to Oqtane.Client for loading modules on which have not declared all dependencies on WebAssembly 2023-03-14 20:40:49 -04:00
7d4cd04ce9 Merge pull request #2684 from sbwalker/dev
add defensive logic to Oqtane.Client for loading modules on which have not declared all dependencies on WebAssembly
2023-03-14 20:39:23 -04:00
04c0b9d37d add defensive logic to Oqtane.Maui for loading modules which have not declared all dependencies 2023-03-14 20:37:42 -04:00
b9c16c0727 Merge pull request #2683 from sbwalker/dev
add defensive logic to Oqtane.Maui for loading modules which have not declared all dependencies
2023-03-14 20:35:55 -04:00
702ff964fe Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-14 16:52:08 +01:00
0a30f2b7e8 fix #2679 - fixed issue where ModuleDefinition cache properties were being overwritten (same issue as #2674 however implemented in ModuleController) 2023-03-14 11:49:38 -04:00
dbb1d53202 Merge pull request #2682 from sbwalker/dev
fix #2679 - fixed issue where ModuleDefinition cache properties were being overwritten (same issue as #2674 however implemented in ModuleController)
2023-03-14 11:48:01 -04:00
2c88f36e3d fix #2680 - issue when adding new site to existing installation 2023-03-14 10:26:51 -04:00
d91dcad774 Merge pull request #2681 from sbwalker/dev
fix #2680 - issue when adding new site to existing installation
2023-03-14 10:24:53 -04:00
919439977c Merge remote-tracking branch 'origin/dev' into dev 2023-03-14 09:44:04 +01:00
6eb4ea2a2d Update README.md 2023-03-13 22:41:30 -04:00
ff6187c336 Merge pull request #2678 from oqtane/master
Merge pull request #2677 from oqtane/dev
2023-03-13 22:35:52 -04:00
1253dfe0c8 Merge pull request #2677 from oqtane/dev
3.4.1 release
2023-03-13 22:35:34 -04:00
c1f2f9a970 prepare for 3.4.1 2023-03-13 22:21:04 -04:00
8d5e7ed69f Merge pull request #2676 from sbwalker/dev
prepare for 3.4.1
2023-03-13 22:19:07 -04:00
3d3540f090 fixed issue where ModuleDefinition cache properties were being overwritten 2023-03-13 10:04:37 -04:00
61f1fcb99c Merge pull request #2674 from sbwalker/dev
fixed issue where ModuleDefinition cache properties were being overwritten
2023-03-13 10:02:53 -04:00
a6478c5146 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-13 08:11:31 +01:00
c7b576d6d3 Update README.md 2023-03-12 10:44:17 -04:00
ab3b1d5e46 Update README.md 2023-03-12 10:42:23 -04:00
ddff3faba9 Update README.md 2023-03-12 10:37:42 -04:00
f7bb8444da Merge pull request #2670 from oqtane/master
Merge pull request #2669 from oqtane/dev
2023-03-12 10:12:26 -04:00
92128974bb Merge pull request #2669 from oqtane/dev
3.4.0 release
2023-03-12 10:11:44 -04:00
524c4ab5a1 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-12 11:04:23 +01:00
4eb15d4806 include Site Map field in Site Settings 2023-03-11 17:42:37 -05:00
39cb3780c8 Merge pull request #2668 from sbwalker/dev
include Site Map field in Site Settings
2023-03-11 17:40:44 -05:00
3c795eec7c Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-11 22:41:21 +01:00
81030f468b improve user profile error logging for external login 2023-03-11 14:03:24 -05:00
2032cb1ace Merge pull request #2667 from sbwalker/dev
improve user profile error logging for external login
2023-03-11 14:01:38 -05:00
5e4c91440e modify editmode parameter value in url to be more intuitive 2023-03-11 11:56:43 -05:00
13bbad863f Merge pull request #2666 from sbwalker/dev
modify editmode parameter value in url to be more intuitive
2023-03-11 11:54:48 -05:00
3065ed5094 fix issue with capturing user profile information during external login 2023-03-11 11:50:02 -05:00
9eb75cfff0 Merge pull request #2665 from sbwalker/dev
fix issue with capturing user profile information during external login
2023-03-11 11:48:07 -05:00
9305c99577 exclude hidden pages by default 2023-03-10 17:22:18 -05:00
9078da6937 Merge pull request #2663 from sbwalker/dev
exclude hidden pages by default
2023-03-10 17:20:25 -05:00
4c579639b9 avoid null reference error if list is null 2023-03-10 13:57:13 -05:00
b86472ab52 Merge pull request #2662 from sbwalker/dev
avoid null reference error if list is null
2023-03-10 13:55:28 -05:00
b9e352cbcb Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-10 16:42:44 +01:00
5e1ac485a0 #2655 - add support for capturing user profile infrmation from claims during external login 2023-03-10 10:14:57 -05:00
527c1a12f4 Merge pull request #2661 from sbwalker/dev
#2655 - add support for capturing user profile infrmation from claims during external login
2023-03-10 10:13:09 -05:00
ef4e99b3a7 further optimization of permissions - removed reference to Role to reduce API payload and minimize information disclosure 2023-03-10 08:28:37 -05:00
12a9635309 Merge pull request #2660 from sbwalker/dev
further optimization of permissions - removed reference to Role to reduce API payload and minimize information disclosure
2023-03-10 08:26:53 -05:00
78adb24a75 fix new installation issue 2023-03-09 16:54:44 -05:00
49955cf642 Merge pull request #2658 from sbwalker/dev
fix new installation issue
2023-03-09 16:52:51 -05:00
af3b289331 exclude legacy Permissions properties from serialization/API payload 2023-03-09 15:51:16 -05:00
d11591e5aa Merge pull request #2657 from sbwalker/dev
exclude legacy Permissions properties from serialization/API payload
2023-03-09 15:49:29 -05:00
e23744a0d0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-09 19:04:26 +01:00
9c6174e3f2 rolling back CSS changes so that it remains consistent with the Oqtane theme 2023-03-09 09:46:58 -05:00
09c2f74d52 Merge pull request #2654 from sbwalker/dev
rolling back CSS changes so that it remains consistent with the Oqtane theme
2023-03-09 09:45:15 -05:00
7d7e0254cb Merge pull request #2642 from leigh-pointer/RecycleBinDelete
Fix Clearing modules from the Recycle Bin
2023-03-09 08:08:58 -05:00
fe767afe9c Merge branch 'dev' into RecycleBinDelete 2023-03-09 11:49:22 +01:00
3378f0e4ee Procedures now checks on PageModuleId 2023-03-09 11:47:19 +01:00
626528fa5e Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-08 19:08:55 +01:00
c0341798ea Merge pull request #2653 from sbwalker/dev
include documentation to explain logic
2023-03-08 12:09:25 -05:00
fc114dc5db include documentation to explain logic 2023-03-08 12:11:11 -05:00
7107d844e1 fix #2640 - system should remain in edit mode when editing a page 2023-03-08 11:50:30 -05:00
59af0a817e Merge pull request #2652 from sbwalker/dev
fix #2640 - system should remain in edit mode when editing a page
2023-03-08 11:48:34 -05:00
9615eded85 Merge pull request #2644 from leigh-pointer/HideDeleteAllButtons
Hide the ActionDialog Button for  "Clear Notifications"
2023-03-08 10:50:13 -05:00
c51fa23fcb Merge pull request #2649 from leigh-pointer/PageChangeRecBin
Add OnPageChangePage to the Page and the Module Pagers in Recycle Bin
2023-03-08 10:50:04 -05:00
8737fd6f1e Merge pull request #2651 from sbwalker/dev
fx #2647 - error when creating new site in existing installation
2023-03-08 10:49:43 -05:00
0f109ab93a fx #2647 - error when creating new site in existing installation 2023-03-08 10:51:38 -05:00
63df2742db initialize SiteId in Permission constructor 2023-03-08 08:43:45 -05:00
7b7811f8ad Merge pull request #2650 from sbwalker/dev
initialize SiteId in Permission constructor
2023-03-08 08:42:13 -05:00
80f74b9939 Add OnPageChangePage to the Page and the Module Pagers
Now when deleting Pages or Modules the current page is shown and not reset to the first page.
2023-03-08 13:55:27 +01:00
274cde1c21 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-06 18:55:42 +01:00
1f29f77f66 fix #2624 - permission grid behavior issues 2023-03-06 12:20:20 -05:00
dd7da5f354 Merge pull request #2645 from sbwalker/dev
fix #2624 - permission grid behavior issues
2023-03-06 12:19:01 -05:00
49b30da697 Hide the ActionDialog Button for "Clear Notifications"
If there are no Notifications then the buttons is not displayed.
2023-03-06 09:13:25 +01:00
90ed767d75 Fix Clearing modules from the Recycle Bin
Modules and all related records now correctly deleted from the database.
2023-03-06 06:26:01 +01:00
4c8cb31a6f Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-06 05:54:29 +01:00
7871f0f3ce Merge pull request #2628 from thabaum/dev
Fixes #2627 #2631 #2630 #2629 #2632  #2635 - Dev Branch Module and Theme Template Issues
2023-03-05 17:53:20 -05:00
a60cf40a3c inverts color of text within "main" <div> 2023-03-05 09:50:56 -08:00
f4eb2f6726 Merge pull request #2637 from sbwalker/dev
fix localization issue in Scheduled Jobs
2023-03-05 08:55:06 -05:00
cfe87a802e fix localization issue in Scheduled Jobs 2023-03-05 08:56:35 -05:00
6c90ec812f Fix - unable to see module actions dropdown toggle 2023-03-04 15:24:43 -08:00
196d611c1c Fix issue with navbar 2023-03-04 13:13:49 -08:00
ff41cb2735 Missing using directive Oqtane.Repository 2023-03-04 10:45:32 -08:00
fb11674301 Missing - Using Oqtane.Repository 2023-03-04 10:44:19 -08:00
b9e7f4530c Update Theme Project to use [RootFolder] 2023-03-04 10:35:45 -08:00
27049687bf Use [RootFolder] for Oqtane.Server Project 2023-03-04 10:34:12 -08:00
13503edc63 Removed extra { } 2023-03-04 09:50:49 -08:00
bea89be67b Merge remote-tracking branch 'origin/dev' into dev 2023-03-04 08:07:48 +01:00
d33f82d969 prepare for 3.4.0 release 2023-03-03 15:45:56 -05:00
177632eee0 Merge pull request #2622 from sbwalker/dev
prepare for 3.4.0 release
2023-03-03 15:44:08 -05:00
ca0de5258e Merge pull request #2620 from leigh-pointer/DeletePermissionModDef
Fix Correct Permission Delete when ModuleDef is deleted #2619
2023-03-02 15:33:25 -05:00
1de788bc26 Merge pull request #2621 from sbwalker/dev
#2618 - add backward compatibility for permissions optimizations
2023-03-02 15:33:11 -05:00
2b41909d47 #2618 - add backward compatibility for permissions optimizations 2023-03-02 15:34:42 -05:00
e23a9f22dd Fix Correct Permission Delete when ModuleDef is deleted #2619
Added PermissionsRepository to delete the Module permissions when the Module Definition is deleted.
2023-03-02 06:58:19 +01:00
4cb17de82a Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-02 06:40:17 +01:00
465b7850b7 Fix #2614 - ability to add module to page 2023-03-01 10:05:14 -05:00
a0f2eedd7f Merge pull request #2615 from sbwalker/dev
Fix #2614 - ability to add module to page
2023-03-01 10:03:30 -05:00
da3a4d5855 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-01 10:34:47 +01:00
8605e3ca5a Major refactoring replacing permission strings with permission collections. These changes will require extensive regression testing. These changes may include breaking changes which will need to be identified and resolved to provide backward compatibility. 2023-02-28 17:59:21 -05:00
dd893e6d48 Merge pull request #2612 from sbwalker/dev
Major refactoring replacing permission strings with permission collections. These changes will require extensive regression testing. These changes may include breaking changes which will need to be identified and resolved to provide backward compatibility.
2023-02-28 17:57:54 -05:00
c4cd1a5a54 Merge pull request #2610 from leigh-pointer/DeleteModDefPageMod
Fix for deleting a ModuleDefinition and related records  #2602
2023-02-24 14:21:22 -05:00
94152651fc Merge pull request #2609 from Behnam-Emamian/dev
extends watching *.dll files
2023-02-24 14:20:44 -05:00
563ea76192 Merge pull request #2611 from sbwalker/dev
explicity specify optional and reload parameters
2023-02-24 14:19:27 -05:00
4913fab0b3 explicity specify optional and reload parameters 2023-02-24 14:21:03 -05:00
b49d011edf Fix for deleting a ModuleDefinition and related records #2602
We then find all Module items that have a ModuleDefinitionName property that matches the ModuleDefinitionName of the item to be removed, and remove them one by one. For each Module item to be removed, we find the PageModule items associated with it, remove them from the pageModules list, and then remove the Module item itself from the modules list.
2023-02-24 11:44:12 +01:00
b0c5d1e991 Merge remote-tracking branch 'oqtane/dev' into dev 2023-02-24 10:33:52 +01:00
6e04281b03 extends watching dll files
extends watching group to include *.dll files and exclude the ones cause an infinite loop.
2023-02-24 11:20:05 +11:00
f2df8e96db fix #2567 - migrate tenant connection string details from database to appsettings.json 2023-02-23 16:29:15 -05:00
c6dd7605b2 Merge pull request #2608 from sbwalker/dev
fix #2567 - migrate tenant connection string details from database to appsettings.json
2023-02-23 16:28:16 -05:00
71dd00da0f Merge pull request #2605 from Behnam-Emamian/dev
add AddByteColumn to add tinyint to the database table.
2023-02-21 07:54:20 -05:00
231fb80c2c Merge remote-tracking branch 'oqtane/dev' into dev 2023-02-20 16:14:42 +01:00
da48ca884d Merge pull request #2606 from sbwalker/dev
add sitemap generator which outputs all public pages and also includes an ISitemap interface for modules
2023-02-20 08:34:15 -05:00
8c6c66fb11 add sitemap generator which outputs all public pages and also includes an ISitemap interface for modules 2023-02-20 08:35:46 -05:00
f333b57310 add AddByteColumn to add tinyint to the database table 2023-02-20 00:20:19 +11:00
d1d00e6c98 Merge pull request #2601 from leigh-pointer/ModDefSettingsDelete
Fix for Missing Delete ModuleDefinition settings #1966
2023-02-17 09:15:44 -05:00
52300e680a Fix for Missing Delete ModuleDefinition settings #1966
Added ISettingRepository _settings  to the public ModuleDefinitionRepository method and updated the DeleteModuleDefinition with _settings.DeleteSettings(EntityNames.ModuleDefinition, moduleDefinitionId);
2023-02-16 13:38:42 +01:00
c49af06e57 Merge remote-tracking branch 'oqtane/dev' into dev 2023-02-16 12:05:02 +01:00
b3f7353582 Merge pull request #2599 from sbwalker/dev
add defensive logic to querystring parser to handle duplicate parameters
2023-02-15 15:20:12 -05:00
7db6b82a1a add defensive logic to querystring parser to handle duplicate parameters 2023-02-15 15:21:50 -05:00
a50a13374f improve initialization logic in FileManager which could sometimes result in Upload button not being displayed when the component was initially loaded 2023-02-15 15:06:50 -05:00
3952fe5a72 Merge pull request #2598 from sbwalker/dev
improve initialization logic in FileManager which could sometimes result in Upload button not being displayed when the component was initially loaded
2023-02-15 15:05:17 -05:00
2e61a43e4f fix #2596 - fix EF Core tracking error when updating a file in a folder which has a Capacity specified 2023-02-15 12:43:18 -05:00
ebe03e9310 Merge pull request #2597 from sbwalker/dev
fix #2596 - fix EF Core tracking error when updating a file in a folder which has a Capacity specified
2023-02-15 12:42:00 -05:00
11dd3ce110 adding Oqtane.Server project back to module and theme external template solutions 2023-02-09 17:45:45 -05:00
1919c24959 Merge pull request #2593 from sbwalker/dev
adding Oqtane.Server project back to module and theme external template solutions
2023-02-09 17:44:27 -05:00
aa80f31e52 fix #2570 - do not allow the term "oqtane" to be used as an organization or module/theme name (to avoid namespace issues). 2023-02-09 16:26:20 -05:00
6d8400e72f Merge pull request #2592 from sbwalker/dev
fix #2570 - do not allow the term "oqtane" to be used as an organization or module/theme name (to avoid namespace issues).
2023-02-09 16:25:08 -05:00
fa8d0c91fc added new methods for managing visitor settings (for personalization) 2023-02-08 17:43:55 -05:00
e91ff95712 Merge pull request #2591 from sbwalker/dev
added new methods for managing visitor settings (for personalization)
2023-02-08 17:42:35 -05:00
0883a8dbff optimize Split() statements for consistency 2023-02-08 16:51:45 -05:00
0db297d1cd Merge pull request #2590 from sbwalker/dev
optimize Split() statements for consistency
2023-02-08 16:50:23 -05:00
db73052ee5 allow system log to be cleared 2023-02-08 14:45:20 -05:00
8b95069610 Merge pull request #2589 from sbwalker/dev
allow system log to be cleared
2023-02-08 14:43:58 -05:00
2a12744cd5 added toggle to show/hide connection string in Site Settings 2023-02-08 08:29:50 -05:00
1df4059284 Merge pull request #2588 from sbwalker/dev
added toggle to show/hide connection string in Site Settings
2023-02-08 08:28:34 -05:00
475894b680 fix #2584 - added IsDeleted columns back to Folder and File tables to preserve compatibility for SQLite 2023-02-08 08:05:25 -05:00
1663bf8e52 Merge pull request #2587 from sbwalker/dev
fix #2584 - added IsDeleted columns back to Folder and File tables to preserve compatibility for SQLite
2023-02-08 08:04:58 -05:00
ffca1d2486 refactor visitor cookie name into a shared constant 2023-02-07 16:26:23 -05:00
eb876845ff Merge pull request #2585 from sbwalker/dev
refactor visitor cookie name into a shared constant
2023-02-07 16:25:05 -05:00
02c134bf4b Merge pull request #2580 from markdav-is/patch-3
Make ActiveDatabase setter public
2023-02-06 16:50:55 -05:00
af55c11aa0 Merge pull request #2582 from sbwalker/dev
fix #2574 - check for null ModuleDefinition reference  when loding permissions in PageModuleRepository (credit @beolafsen)
2023-02-06 16:48:40 -05:00
33bc6adcb5 fix #2574 - check for null ModuleDefinition reference when loding permissions in PageModuleRepository (credit @beolafsen) 2023-02-06 16:49:45 -05:00
56e4dcc11e fix #2578 - error notification sent via email includes direct link to specific log item, however redirect was causing an infinite loop. This resolves the problem and also preserves url querystring parameters during login/logout. 2023-02-06 16:44:25 -05:00
467cf7620e Merge pull request #2581 from sbwalker/dev
fix #2578 - error notification sent via email includes direct link to specific log item, however redirect was causing an infinite loop. This resolves the problem and also preserves url querystring parameters during login/logout.
2023-02-06 16:43:23 -05:00
85ac8dd701 Make ActiveDatabase setter public
We have two cases where we need to override the active database:  Unit Testing and added GraphQL.  In both of these cases, we have a database context that is in a different scope than the automatically assigned active database during normal Oqtane startup.  Our work-around has been to make this setter public.  Unless there is a better solution to our cases, I feel this change would be useful for others as well.
2023-02-04 09:04:54 -08:00
1f2ad4e884 Suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies 2023-02-03 16:12:13 -05:00
cf2d9af664 Merge pull request #2579 from sbwalker/dev
Suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies
2023-02-03 16:10:57 -05:00
7a105047e9 Fixed issue where TenantMiddleware was not rewriting the Url path for the new File Server when running on an Alias Path which resulted in a 404 when serving files 2023-01-23 15:16:08 -05:00
bc8bdef37d Merge pull request #2571 from sbwalker/dev
Fixed issue where TenantMiddleware was not rewriting the Url path for the new File Server when running on an Alias Path which resulted in a 404 when serving files
2023-01-23 15:15:05 -05:00
fd0519b955 Update README.md 2023-01-14 15:17:38 -05:00
d5ffb56fa8 Update README.md 2023-01-14 15:15:33 -05:00
d6cce9e2d8 Update README.md 2023-01-14 15:08:20 -05:00
08ec46637f Update README.md 2023-01-14 15:04:31 -05:00
f596795792 Update README.md 2023-01-14 15:03:46 -05:00
2c56bfd4aa Merge pull request #2565 from oqtane/master
Merge pull request #2564 from oqtane/dev
2023-01-14 14:54:37 -05:00
755615da30 Merge pull request #2564 from oqtane/dev
3.3.1 release
2023-01-14 14:54:18 -05:00
4cc0060c67 prepare for 3.3.1 patch 2023-01-14 14:45:30 -05:00
52af015c2f Merge pull request #2563 from sbwalker/dev
prepare for 3.3.1 patch
2023-01-14 14:44:32 -05:00
eae6d13284 Fix #2561 - set Permission EntityName explicitly to Module when adding module to page from Control Panel 2023-01-14 12:39:34 -05:00
2a20a7cf21 Merge pull request #2562 from sbwalker/dev
Fix #2561 - set Permission EntityName explicitly to Module when adding module to page from Control Panel
2023-01-14 12:38:55 -05:00
27251005ec incorrect url in framework nusepc 2023-01-13 07:50:58 -05:00
90a9b2e5a1 Merge pull request #2560 from sbwalker/dev
incorrect url in framework nusepc
2023-01-13 07:50:04 -05:00
cd1f12e9b4 Merge pull request #2557 from oqtane/master
Merge pull request #2556 from oqtane/dev
2023-01-12 12:50:09 -05:00
734b9b6458 Merge pull request #2556 from oqtane/dev
3.3.0 Release
2023-01-12 12:49:49 -05:00
a120449c8d Update README.md 2023-01-12 12:48:54 -05:00
b005a1da56 Merge pull request #2555 from sbwalker/dev
remove extra info from Body as the From display name is now always set to the user's name
2023-01-12 11:47:32 -05:00
afc75a09d9 remove extra info from Body as the From display name is now always set to the user's name 2023-01-12 11:48:25 -05:00
c6e6c98875 improve notification job 2023-01-12 09:13:13 -05:00
fa1a5ab913 Merge pull request #2554 from sbwalker/dev
improve notification job
2023-01-12 09:12:11 -05:00
b671b590ad change Sql Manager logging level 2023-01-12 08:18:36 -05:00
a46c98eed3 Merge pull request #2553 from sbwalker/dev
change Sql Manager logging level
2023-01-12 08:17:42 -05:00
7ab5ed5bcb copyright year update 2023-01-11 14:40:16 -05:00
5e609edc31 Merge pull request #2551 from sbwalker/dev
copyright year update
2023-01-11 14:39:15 -05:00
ac466429f8 improve release batch file 2023-01-11 10:24:50 -05:00
2804c50c8a Merge pull request #2550 from sbwalker/dev
improve release batch file
2023-01-11 10:24:18 -05:00
c4315c25bc prepare for 3.3.0 release 2023-01-10 14:02:23 -05:00
e2d5fa48cf Merge pull request #2548 from sbwalker/dev
prepare for 3.3.0 release
2023-01-10 14:01:48 -05:00
c2375c897d permission updates 2023-01-10 08:20:32 -05:00
49f181583b Merge pull request #2546 from sbwalker/dev
permission updates
2023-01-10 08:20:27 -05:00
ea463a6548 fix #2534 - added Relay Configured site setting to enable sending from users email address 2023-01-09 16:37:06 -05:00
1a567dbdc1 Merge pull request #2545 from sbwalker/dev
fix #2534 - added Relay Configured site setting to enable sending from users email address
2023-01-09 16:36:30 -05:00
e4ec10ef49 format PermissionNames to be more readable 2023-01-09 15:36:41 -05:00
3db4fc687a Merge pull request #2544 from sbwalker/dev
format PermissionNames to be more readable
2023-01-09 15:35:59 -05:00
e136972cd7 add support for API permissions at the UI layer - including ability to delegate user, role, profile management 2023-01-09 11:38:25 -05:00
7b1b32e16f Merge pull request #2543 from sbwalker/dev
add support for API permissions at the UI layer - including ability to delegate user, role, profile management
2023-01-09 11:37:54 -05:00
1616f94b86 add ability to view error.log in System Info 2023-01-05 10:18:55 -05:00
7043d1f3bf Merge pull request #2542 from sbwalker/dev
add ability to view error.log in System Info
2023-01-05 10:18:10 -05:00
7bebfe1919 fix typo and help text 2023-01-05 09:55:06 -05:00
d33ded0426 Merge pull request #2541 from sbwalker/dev
fix typo and help text
2023-01-05 09:54:23 -05:00
66aa67581f improve dynamic policy registration to handle possible race conditions 2023-01-05 09:43:59 -05:00
3a95c05db9 Merge pull request #2540 from sbwalker/dev
improve dynamic policy registration to handle possible race conditions
2023-01-05 09:43:15 -05:00
79fc03bb0f Merge remote-tracking branch 'oqtane/dev' into dev 2023-01-04 21:27:31 +01:00
67046e9d36 Merge pull request #2535 from thabaum/Readme-Glow-Logo
Update framework root to use the new dark/light theme glow logo
2023-01-04 14:51:39 -05:00
6f2965a5b0 Merge pull request #2537 from leigh-pointer/Pager-Pointer
A change to the Pager bar to set the mouse pointer to pointer
2023-01-04 14:51:29 -05:00
197db449a1 Merge pull request #2539 from sbwalker/dev
include owner in migration tag name in external module template
2023-01-04 14:51:11 -05:00
f4800bb7f0 include owner in migration tag name in external module template 2023-01-04 14:51:55 -05:00
6a213561f6 added an autocomplete component and implemented in permission grid 2023-01-04 14:50:05 -05:00
8f8d31f0c9 Merge pull request #2538 from sbwalker/dev
added an autocomplete component and implemented in permission grid
2023-01-04 14:49:23 -05:00
f8cfdacc26 A change to the Pager bar to set the mouse pointer to pointer
Currently the mouse pointer shows the Selector icon when hoovered over the page number buttons. This is an update changing the icon to the Pointer icon.

Updated the CSS class name to 'app-pager-pointer' in app.css and the Component.
2023-01-03 14:24:17 +01:00
07223e27a4 Add Oqtane Glow Logo for Dark/Light Themes 2022-12-27 10:30:00 -08:00
4c6f46ad17 Remove Dark Logo from Framework Root 2022-12-27 10:28:37 -08:00
39dc288f9c Merge pull request #2533 from sbwalker/dev
fix #2526 - support multiple TabStrip components on a page
2022-12-19 15:57:30 -05:00
467e88ef55 fix #2526 - support multiple TabStrip components on a page 2022-12-19 15:58:04 -05:00
0965db5d57 fix skip pages logic in pager where screen was not being refreshed 2022-12-19 15:11:04 -05:00
c30339d9b8 Merge pull request #2532 from sbwalker/dev
fix skip pages logic in pager where screen was not being refreshed
2022-12-19 15:10:27 -05:00
369adcb173 allow profile management to be delegated 2022-12-19 15:07:38 -05:00
d837b981d4 Merge pull request #2531 from sbwalker/dev
allow profile management to be delegated
2022-12-19 15:07:03 -05:00
f455461c1e Update README.md 2022-12-14 14:54:23 -05:00
0087b188a1 improve null handling 2022-12-07 10:33:25 -05:00
98602f49d8 Merge pull request #2522 from sbwalker/dev
improve null handling
2022-12-07 10:33:21 -05:00
70466e5626 Merge pull request #2521 from sbwalker/dev
fix #2513 - add new methods for deleting a setting and retrieving a list of settings
2022-12-07 08:59:07 -05:00
cc7f98a6fe fix #2513 - add new methods for deleting a setting and retrieving a list of settings 2022-12-07 08:59:03 -05:00
fd13ad1fca initialize API permissions based on default roles 2022-12-06 17:16:51 -05:00
5077f6fbca Merge pull request #2520 from sbwalker/dev
initialize API permissions based on default roles
2022-12-06 17:16:47 -05:00
9b15ce6d29 Merge pull request #2519 from sbwalker/dev
make casing consistent in route template definition and method parameter declation or else Swagger will not be able to resolve
2022-12-06 10:48:57 -05:00
5a8ca24566 make casing consistent in route template definition and method parameter declation or else Swagger will not be able to resolve 2022-12-06 10:48:56 -05:00
d3f982cae1 Merge pull request #2518 from sbwalker/dev
add ModuleControllerBase helper method for validating EntityId
2022-12-05 14:21:15 -05:00
28b58b9048 add ModuleControllerBase helper method for validating EntityId 2022-12-05 14:21:12 -05:00
cb10dde97d added API Management for managing site level entity permissions 2022-12-02 16:42:43 -05:00
2c179867e8 Merge pull request #2517 from sbwalker/dev
added API Management for managing site level entity permissions
2022-12-02 16:42:41 -05:00
44fc6de82b hide connection string by default in SQL Management and provide toggle for display 2022-12-02 08:16:18 -05:00
f671af43cd Merge pull request #2516 from sbwalker/dev
hide connection string by default in SQL Management and provide toggle for display
2022-12-02 08:16:18 -05:00
8c0dc6422e Merge pull request #2515 from sbwalker/dev
fix #2512 - provide guidance about password complexity policy during install, and ensure modified passwords meet complexity policy
2022-12-02 07:42:55 -05:00
c91e285475 fix #2512 - provide guidance about password complexity policy during install, and ensure modified passwords meet complexity policy 2022-12-02 07:42:49 -05:00
642e41530e Merge pull request #2514 from sbwalker/dev
enhance dynamic authorization policies to support default role specification
2022-12-02 07:34:08 -05:00
b09a3ccdae enhance dynamic authorization policies to support default role specification 2022-12-02 07:34:06 -05:00
a1aab62cea Merge pull request #2504 from leigh-pointer/PagerPointer
A change to the Pager bar to set the mouse pointer to pointer
2022-11-23 11:26:57 -05:00
e8b1aca45c Merge pull request #2507 from sbwalker/dev
fix #2502 - invalid logic checking querystring parameter
2022-11-23 11:26:21 -05:00
3de98873d6 fix #2502 - invalid logic checking querystring parameter 2022-11-23 11:26:23 -05:00
c030ede12c Merge pull request #2506 from sbwalker/dev
fix #2503 - generate password using CultureInfo.InvariantCulture to ensure it satisfies password complexity criteria
2022-11-23 11:11:05 -05:00
67f740c264 fix #2503 - generate password using CultureInfo.InvariantCulture to ensure it satisfies password complexity criteria 2022-11-23 11:10:59 -05:00
9b68337047 Merge pull request #2505 from sbwalker/dev
fix #2501 - set default Visibility to Same As Page when adding module to a page
2022-11-23 10:51:50 -05:00
2bae971b92 fix #2501 - set default Visibility to Same As Page when adding modules to a page 2022-11-23 10:51:37 -05:00
c5c5fd859f A change to the Pager bar to set the mouse pointer to pointer
Currently the mouse pointer shows the Selector icon when hoovered over the page number buttons.  This is an update changing the icon to the Pointer icon.
2022-11-21 09:44:30 +01:00
15c46fd157 Merge pull request #2496 from sbwalker/dev
Fix #2488 - add ability to include inline script resource definitions in modules and themes
2022-11-12 10:59:25 -05:00
424950bd3e Fix #2488 - add ability to include inline script resource definitions in modules and themes 2022-11-12 10:58:58 -05:00
39a9968971 Merge pull request #2493 from sbwalker/dev
fix JS Interop methods for includeScript and includeMeta
2022-11-10 14:19:52 -05:00
075a09f0df fix JS Interop methods for includeScript and includeMeta 2022-11-10 14:19:31 -05:00
26e628e189 Merge pull request #2492 from sbwalker/dev
move UI logic from FileService to FileManager, add progressive retry logic, update file attributes if uploading a new version of a file, clean up temporary artifacts on failure, improve upload efficiency
2022-11-09 21:11:42 -05:00
7489d9d186 move UI logic from FileService to FileManager, add progressive retry logic, update file attributes if uploading a new version of a file, clean up temporary artifacts on failure, improve upload efficiency 2022-11-09 21:11:02 -05:00
7db5b6c7f3 Update README.md 2022-11-08 08:59:49 -05:00
9b7fa8cac2 Update README.md 2022-11-08 08:53:33 -05:00
5028051a34 Update README.md 2022-11-08 08:52:51 -05:00
83b3767df6 Merge pull request #2490 from sbwalker/dev
Scope permissions by SiteId to support entity level authorization as well as improve caching and performance. Optimize GetTenant to use existing cache.
2022-11-07 18:17:04 -05:00
6182b96d16 Scope permissions by SiteId to support entity level authorization as well as improve caching and performance. Optimize GetTenant to use existing cache. 2022-11-07 18:16:32 -05:00
a719382563 Merge pull request #2482 from sbwalker/dev
add support for dynamic authorization policies
2022-11-04 08:08:33 -04:00
2aa6eb90e2 add support for dynamic authorization policies 2022-11-04 08:08:10 -04:00
d2495455cd Merge pull request #2477 from sbwalker/dev
added ETag / 304 Not Modified logic to File server for performance optimization
2022-10-29 10:23:58 -04:00
23d1dd23d1 added ETag / 304 Not Modified logic to File server for performance optimization 2022-10-29 10:23:04 -04:00
573acd6a5d Merge pull request #2476 from sbwalker/dev
fix File Update API to update the file size and image dimensions
2022-10-27 09:39:05 -04:00
40ddbbfbb7 fix File Update API to update the file size and image dimensions 2022-10-27 09:38:26 -04:00
1b488b298b Merge pull request #2475 from sbwalker/dev
remove IDeletable fields from Folder and File entities as they are never set and not used
2022-10-26 17:43:09 -04:00
54b45943db remove IDeletable fields from Folder and File entities as they are never set and not used 2022-10-26 17:42:26 -04:00
89b3ae4c74 Merge pull request #2474 from sbwalker/dev
fix IDeletable code documentation
2022-10-26 17:33:52 -04:00
fe97a76d00 fix IDeletable code documentation 2022-10-26 17:33:17 -04:00
78094f69d7 Merge pull request #2473 from sbwalker/dev
update models to use new ModelBase
2022-10-26 17:27:22 -04:00
4499d55464 update models to use new ModelBase 2022-10-26 17:26:46 -04:00
4c12ca0607 Merge pull request #2472 from sbwalker/dev
introduced a ModelBase to move the IAuditable properties to a base class
2022-10-26 17:12:38 -04:00
1daa9575db introduced a ModelBase to move the IAuditable properties to a base class 2022-10-26 17:12:03 -04:00
f7c9961f8c Merge pull request #2471 from sbwalker/dev
add loading icon to Maui project
2022-10-26 10:06:57 -04:00
e27a625069 add loading icon to Maui project 2022-10-26 09:04:04 -04:00
42b1669cce Merge pull request #2469 from sbwalker/dev
remove Oqtane.Server from external templates as they cause random compilation issues
2022-10-20 14:05:45 -04:00
74571afc9e remove Oqtane.Server from external templates as they cause random compilation issues 2022-10-20 14:04:53 -04:00
f678f11c59 Merge pull request #2468 from sbwalker/dev
add validation message for missing package name
2022-10-20 14:00:56 -04:00
e685252b1d add validation message for missing package name 2022-10-20 14:00:17 -04:00
bb75fcb5a3 Merge pull request #2467 from sbwalker/dev
fix language delete refresh
2022-10-20 13:43:36 -04:00
7653f36f31 fix language delete refresh 2022-10-20 13:42:54 -04:00
0670148064 Merge pull request #2466 from sbwalker/dev
fix #2464 - translation install/upgrade experience
2022-10-20 13:17:06 -04:00
368b900a6e fix #2464 - translation install/upgrade experience 2022-10-20 13:16:18 -04:00
6378a78cbd Update README.md 2022-10-18 08:10:48 -04:00
d3bcdec0f2 Update README.md 2022-10-18 08:10:02 -04:00
82c074ce2e Merge pull request #2463 from sbwalker/dev
replace assembly references with package references
2022-10-18 07:52:33 -04:00
a313e2d386 replace assembly references with package references 2022-10-18 07:51:50 -04:00
578cb2f7e3 Update README.md 2022-10-18 07:43:18 -04:00
c8f56e1659 Merge pull request #2461 from oqtane/master
Merge pull request #2460 from oqtane/dev
2022-10-17 16:46:03 -04:00
56631a3e6b Merge pull request #2460 from oqtane/dev
3.2.1 release
2022-10-17 16:45:47 -04:00
84ee9de18c Merge pull request #2459 from sbwalker/dev
Changed default service url in MAUI so users can immediately run client app
2022-10-17 08:12:53 -04:00
0aeb4e9173 Changed default service url in MAUI so users can immediately run client app 2022-10-17 08:12:04 -04:00
bb65e5c373 Merge pull request #2455 from sbwalker/dev
prepare for 3.2.1 release
2022-10-13 13:35:36 -04:00
45e2027c56 prepare for 3.2.1 release 2022-10-13 13:34:43 -04:00
6dc5ef44b7 Merge pull request #2454 from sbwalker/dev
Resolve deserialization issue with System.Text.Json when accessing remote services
2022-10-12 12:38:08 -04:00
e88d3cca07 Resolve deserialization issue with System.Text.Json when accessing remote services 2022-10-12 12:37:03 -04:00
a4b7381141 Merge pull request #2451 from sbwalker/dev
fix #2435 - remove NewtonSoft.Json dependency
2022-10-11 08:35:44 -04:00
2ea054dc72 fix #2435 - remove NewtonSoft.Json dependency 2022-10-11 08:34:33 -04:00
13ec726ab2 Merge pull request #2450 from sbwalker/dev
add file download event
2022-10-05 08:02:22 -04:00
2e32b65421 add file download event 2022-10-05 08:00:45 -04:00
f48ca53cdb Merge pull request #2449 from sbwalker/dev
Enhance SyncManager to raise events which can be handled on the server within hosted services. Raise create, update, delete events for all major entities. Include support for refresh and reload events to synchronize client state. Move client state cache invalidation to a hosted service to separate concerns and demonstrate events.
2022-10-04 19:21:35 -04:00
c5b632cb24 Enhance SyncManager to raise events which can be handled on the server within hosted services. Raise create, update, delete events for all major entities. Include support for refresh and reload events to synchronize client state. Move client state cache invalidation to a hosted service to separate concerns and demonstrate events. 2022-10-04 19:20:02 -04:00
422e9ae99e Update README.md 2022-09-30 15:24:15 -04:00
68ada8fbe4 Merge pull request #2431 from chlupac/InstallFix
Unattended installation fix
2022-09-30 11:49:06 -04:00
e40bf08691 Merge pull request #2446 from sbwalker/dev
add upgrade logic for sites using remapped identifier and email claim…
2022-09-30 09:54:59 -04:00
a04c7222b2 add upgrade logic for sites using remapped identifier and email claim types 2022-09-30 09:53:37 -04:00
172faec5a0 Merge pull request #2445 from sbwalker/dev
log any user creation errors from .NET Identity
2022-09-29 17:18:48 -04:00
e01c3e7e4a log any user creation errors from .NET Identity 2022-09-29 17:16:29 -04:00
7a3d5d0429 Merge pull request #2444 from sbwalker/dev
fix #2432 - add support for roles as part of external login via OIDC
2022-09-29 16:34:18 -04:00
ddf1caaaaa fix #2432 - add support for roles as part of external login via OIDC 2022-09-29 16:32:50 -04:00
021293cbb0 Merge pull request #2443 from sbwalker/dev
fix #2427 - issue with upgrade available in Language Management
2022-09-28 16:18:08 -04:00
1438e61f1b fix #2427 - issue with upgrade available in Language Management 2022-09-28 16:16:46 -04:00
182f4dbae7 Merge pull request #2442 from sbwalker/dev
fix #2426 - error in recycle bin
2022-09-28 13:56:46 -04:00
26ec3fc7cf fix #2426 - error in recycle bin 2022-09-28 13:55:12 -04:00
225c758795 Merge pull request #2440 from leigh-pointer/PagerFooter
Add footer to the Pager control
2022-09-28 09:45:48 -04:00
5e653250f3 Merge pull request #2441 from sbwalker/dev
Fix #2439 - ensure resource urls are constructed consistently on client and server
2022-09-28 09:44:32 -04:00
b7a3713946 Fix #2439 - ensure resource urls are constructed consistently on client and server 2022-09-28 09:43:02 -04:00
44242fdb4a Add footer to the Pager control
Mirrored the Head functionality for <tfoot> tag
2022-09-28 14:00:53 +02:00
45515b2c06 Unattented instalation fix 2022-09-24 15:44:20 +02:00
2d95fe294c Merge pull request #2430 from sbwalker/dev
Add Blazor Server reconnect script, fix event log direct link from notification email, add more validation to Pager, improve browser refresh script to wait for server availability
2022-09-24 08:38:54 -04:00
72cc44641b Add Blazor Server reconnect script, fix event log direct link from notification email, add more validation to Pager, improve browser refresh script to wait for server availability 2022-09-24 08:37:18 -04:00
9ac3fa5269 Merge pull request #2425 from sbwalker/dev
fix new id convention in file server
2022-09-21 15:39:20 -04:00
d1ea141165 fix new id convention in file server 2022-09-21 15:37:52 -04:00
dca21fbb8c Merge pull request #2424 from sbwalker/dev
improve BaseUrl handling for MAUI, replace ContentUrl with FileUrl and improve file server
2022-09-21 13:39:51 -04:00
06812d5df8 improve BaseUrl handling for MAUI, replace ContentUrl with FileUrl and improve file server 2022-09-21 13:38:21 -04:00
564410d3cd Update README.md 2022-09-19 17:10:11 -04:00
4b1cec979a Merge pull request #2422 from sbwalker/dev
rename list name to match intent
2022-09-14 22:30:05 -04:00
a5f1bc3895 rename list name to match intent 2022-09-14 22:28:31 -04:00
b097d031b5 Merge pull request #2421 from sbwalker/dev
clean up pdb files on client, hash assembly file names
2022-09-14 10:11:22 -04:00
45df729711 clean up pdb files on client, hash assembly file names 2022-09-14 10:09:50 -04:00
802ee8a1ff Merge pull request #2419 from oqtane/master
Merge pull request #2418 from oqtane/dev
2022-09-13 09:18:35 -04:00
e312970212 Merge pull request #2418 from oqtane/dev
3.2.0 release
2022-09-13 09:18:16 -04:00
c0f4069a9b Merge pull request #2417 from sbwalker/dev
refactor IndexedDB interop functions
2022-09-13 07:44:10 -04:00
654352827e refactor IndexedDB interop functions 2022-09-13 07:42:27 -04:00
7dd210976d Merge pull request #2416 from sbwalker/dev
optimize assembly list retrieval
2022-09-12 16:21:11 -04:00
5302be8bc1 optimize assembly list retrieval 2022-09-12 16:19:32 -04:00
9ed7181e28 Merge pull request #2415 from sbwalker/dev
remove unnecessary using statements
2022-09-12 14:56:35 -04:00
23ae4b01cb remove unnecessary using statements 2022-09-12 14:54:31 -04:00
59764d3378 Merge pull request #2414 from sbwalker/dev
cache assemblies in IndexedDB on WebAssembly
2022-09-12 14:48:17 -04:00
b8e2c729c1 cache assemblies in IndexedDB on WebAssembly 2022-09-12 14:46:46 -04:00
530d80a011 Merge pull request #2412 from sbwalker/dev
optimize assembly loading for MAUI to use client storage
2022-09-11 10:50:20 -04:00
2d306e8fda optimize assembly loading for MAUI to use client storage 2022-09-11 10:48:40 -04:00
b3d9a70fd1 Merge pull request #2410 from sbwalker/dev
remove Oqtane.Server from Oqtane.Maui solution
2022-09-09 11:40:06 -04:00
b880207f61 remove Oqtane.Server from Oqtane.Maui solution 2022-09-09 11:37:33 -04:00
ee76d02999 Merge pull request #2409 from sbwalker/dev
improvements to run in Android Emulator
2022-09-09 10:27:45 -04:00
804c33a375 improvements to run in Android Emulator 2022-09-09 10:26:13 -04:00
de784714d9 Merge pull request #2408 from sbwalker/dev
fix issue in upgrade logic for making folder paths cross platform
2022-09-08 15:44:34 -04:00
2404e26b61 fix issue in upgrade logic for making folder paths cross platform 2022-09-08 15:43:03 -04:00
b191fdda2c Merge pull request #2407 from sbwalker/dev
prepare for 3.2.0
2022-09-08 15:30:05 -04:00
e8adfd45d2 prepare for 3.2.0 2022-09-08 15:28:25 -04:00
7158595801 Merge pull request #2406 from orionlaw/dev
Make sure Job date times are stored in the database as UTC.
2022-09-08 09:14:31 -04:00
ba97f63338 Make sure Job date times are stored in the database as UTC. This is required if using Postgres or you will get an exception with a message of “Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone', only UTC is supported.”. 2022-09-07 12:46:24 -06:00
62eca2aedc Merge pull request #2401 from chlupac/BackslashFix
Backslash fix.
2022-09-06 10:53:48 -04:00
b15f6b1fa7 Merge pull request #2402 from chlupac/GitignoreUpdate
Gitignore update
2022-09-06 10:53:05 -04:00
5b22de589c Merge pull request #2403 from sbwalker/dev
Fix #2399 - page paths not being validated for deleted pages
2022-09-06 10:52:34 -04:00
d1f50f12af Fix #2399 - page paths not being validated for deleted pages 2022-09-06 10:50:53 -04:00
d40c1d9b31 Backslash fix. 2022-09-06 09:14:58 +02:00
b69041d4af Gitignore update 2022-09-06 09:14:42 +02:00
64c5d9a09f Merge pull request #2400 from sbwalker/dev
more changes to support Default pane
2022-09-05 15:51:22 -04:00
dd170bb41a more changes to support Default pane 2022-09-05 15:49:38 -04:00
1e6e4033f8 Merge pull request #2398 from sbwalker/dev
changed UrlParameterTemplate name for consistency
2022-09-04 09:48:48 -04:00
01fabc8d9e changed UrlParameterTemplate name for consistency 2022-09-04 09:47:03 -04:00
2ca2539b53 Merge pull request #2397 from sbwalker/dev
fix #2366 - populate new UrlParameters property
2022-09-04 09:37:09 -04:00
51e2e2966f fix #2366 - populate new UrlParameters property 2022-09-04 09:35:18 -04:00
55d02d2db5 Merge pull request #2396 from sbwalker/dev
Fix #2382 - Admin pane improvements
2022-09-02 18:11:51 -04:00
282a0b0c44 Fix #2382 - Admin pane improvements 2022-09-02 18:10:13 -04:00
8432779b23 Merge pull request #2395 from sbwalker/dev
added public Refresh method to FileManager
2022-09-02 09:12:41 -04:00
13b9982461 added public Refresh method to FileManager 2022-09-02 09:11:00 -04:00
d76b8cebdc Merge pull request #2389 from sbwalker/dev
Changes for .NET MAUI on Android
2022-08-31 16:35:53 -04:00
80315ae6d4 Changes for .NET MAUI on Android 2022-08-31 16:34:14 -04:00
95e7344286 Merge pull request #2385 from sbwalker/dev
moved hierarchical ordering logic to server for pages and folders
2022-08-30 07:33:46 -04:00
28f73727b5 moved hierarchical ordering logic to server for pages and folders 2022-08-30 07:31:56 -04:00
68f5bf5759 Merge pull request #2384 from sbwalker/dev
made folder paths cross platform, introduced file handler for abstracting the serving of files, enabled url mapping for broken file links, resolved public folder deletion issue
2022-08-30 07:23:50 -04:00
075748d697 made folder paths cross platform, introduced file handler for abstracting the serving of files, enabled url mapping for broken file links, resolved public folder deletion issue 2022-08-30 07:21:52 -04:00
e8d86f94f2 Merge pull request #2376 from sbwalker/dev
fix rootnamespace
2022-08-19 16:12:32 -04:00
d6bb802892 fix rootnamespace 2022-08-19 16:10:27 -04:00
52680e9002 Merge pull request #2375 from sbwalker/dev
prepare for 3.2.0
2022-08-19 15:59:35 -04:00
d6385d82ae prepare for 3.2.0 2022-08-19 15:57:31 -04:00
d058de067c Merge pull request #2374 from sbwalker/dev
Prepare for 3.2.0 release
2022-08-19 15:56:39 -04:00
32d6d143dd Prepare for 3.2.0 release 2022-08-19 15:54:33 -04:00
b49432802b Merge pull request #2373 from sbwalker/dev
Improvements to richtexteditor to allow file management in raw html editor. Also allow disabling of raw html editor which can be utilized via new setting in Html/Text module.
2022-08-19 15:34:43 -04:00
99d4d75d8e Improvements to richtexteditor to allow file management in raw html editor. Also allow disabling of raw html editor which can be utilized via new setting in Html/Text module. 2022-08-19 15:32:30 -04:00
1f584d57ac Merge pull request #2372 from sbwalker/dev
optimize Url Parameters and implement in Event Log
2022-08-18 16:06:34 -04:00
2c1543aa82 optimize Url Parameters and implement in Event Log 2022-08-18 16:04:30 -04:00
4390cbbfae Update README.md 2022-08-18 08:35:49 -04:00
bbf9e5717e Merge pull request #2370 from sbwalker/dev
improve support for module content editors
2022-08-16 17:27:54 -04:00
c7edc28bd9 improve support for module content editors 2022-08-16 17:25:46 -04:00
6e0de6f7bf Merge pull request #2369 from sbwalker/dev
check for existence of appsettings.json on Maui
2022-08-16 09:42:14 -04:00
3659422165 check for existence of appsettings.json on Maui 2022-08-16 09:40:03 -04:00
1af30da44e Merge pull request #2368 from sbwalker/dev
trim list of pages allowed to be Home Page
2022-08-16 08:44:53 -04:00
56c082cb26 trim list of pages allowed to be Home Page 2022-08-16 08:42:47 -04:00
e8eca582de Merge pull request #2363 from sbwalker/dev
added ability to specify a site home page, updated default template content to include .NET MAUI
2022-08-15 17:03:34 -04:00
4084b352de added ability to specify a site home page, updated default template content to include .NET MAUI 2022-08-15 17:01:20 -04:00
633e4acf0e Merge pull request #2362 from sbwalker/dev
add Site option for specifying a Hosting Model of Blazor Hybrid
2022-08-15 09:32:42 -04:00
468df15d80 add Site option for specifying a Hosting Model of Blazor Hybrid 2022-08-15 09:30:36 -04:00
f4537b4fcb Merge pull request #2361 from sbwalker/dev
optimize site router
2022-08-14 11:24:43 -04:00
8bca345b45 optimize site router 2022-08-14 11:22:39 -04:00
ee80712c77 Merge pull request #2360 from sbwalker/dev
resolve issue with deleted pages and modules caused by refactoring
2022-08-12 18:04:51 -04:00
3cf7153f44 resolve issue with deleted pages and modules caused by refactoring 2022-08-12 18:02:45 -04:00
8e2fc75e48 Update README.md 2022-08-12 17:09:15 -04:00
4aa51c8583 Merge pull request #2359 from sbwalker/dev
performance improvements to reduce http and database interactions
2022-08-12 16:49:59 -04:00
3c6ebd7742 performance improvements to reduce http and database interactions 2022-08-12 16:47:51 -04:00
b85539dc17 Merge pull request #2358 from sbwalker/dev
add ability to dynamically set module title and visible from components
2022-08-12 13:08:14 -04:00
4cae3f02ed add ability to dynamically set module title and visible from components 2022-08-12 13:05:48 -04:00
469b436f10 Merge pull request #2356 from dkoeder/dev
Some methods failing in BaseEntityBuilder if Schema is not null.
2022-08-12 10:45:14 -04:00
66e3e6729b Merge pull request #2357 from sbwalker/dev
add support for preserving state when loading admin components
2022-08-12 10:45:05 -04:00
fc6a794714 add support for preserving state when loading admin components 2022-08-12 10:43:00 -04:00
d75ed3d5ac Update BaseEntityBuilder.cs
Some methods failing in BaseEntityBuilder if Schema is not null.
2022-08-11 16:15:09 -06:00
bd0a218214 Merge pull request #2355 from sbwalker/dev
Blazor Hybrid / .NET MAUI support
2022-08-11 17:11:45 -04:00
f96129fa37 Blazor Hybrid / .NET MAUI support 2022-08-11 17:09:32 -04:00
920418618a Update README.md 2022-08-10 08:30:38 -04:00
29247481a6 Merge pull request #2351 from ajahangard/patch-1
#Bug in passing Lifetime property to GenerateToken
2022-08-09 10:47:28 -04:00
773710aeef #Bug in passing Lifetime property to GenerateToken
Audience is passed to GenerateToken instead of Lifetime.
2022-08-09 15:32:52 +04:30
d0c8ee57e6 Merge pull request #2348 from sbwalker/dev
Fix satellite assembly loading issue when running on WebAssembly
2022-08-08 10:49:42 -04:00
cf2adc7f6a Fix satellite assembly loading issue when running on WebAssembly 2022-08-08 10:47:33 -04:00
b621f24540 Merge pull request #2342 from sbwalker/dev
Fix #2336 - error.png path incorrect
2022-08-06 16:27:45 -04:00
99be638525 Fix #2336 - error.png path incorrect 2022-08-06 16:27:24 -04:00
d35c204e07 Merge pull request #2341 from sbwalker/dev
Fix #2339 - refactor module upgrade logic to  remove requirement on ServerManagerType for modules which have no backend
2022-08-06 16:13:53 -04:00
d8b4267668 Fix #2339 - refactor module upgrade logic to remove requirement on ServerManagerType for modules which have no backend 2022-08-06 16:13:28 -04:00
3c2f3be451 Merge pull request #2338 from chlupac/TruncateAgent
Truncate UserAgent for save to Visitors table #2337
2022-08-05 09:16:54 -04:00
e846cf8672 Truncate UserAgent for save to Visitors table 2022-08-04 16:14:39 +02:00
8804bce6c0 Merge pull request #2331 from sbwalker/dev
add proper translation keys for ActionLink and ActionDialog into RESX for Module Creator template
2022-08-03 08:50:12 -04:00
83acda6d05 add proper translation keys for ActionLink and ActionDialog into RESX for Module Creator template 2022-08-03 08:49:47 -04:00
063719532f Merge pull request #2328 from sbwalker/dev
include ResourceType attribute in Settings component for external module template
2022-08-02 07:55:38 -04:00
7b1b061355 include ResourceType attribute in Settings component for external module template 2022-08-02 07:55:11 -04:00
ed4540887e Merge pull request #2326 from leigh-pointer/Bootstrap5.2
Formating issues with Bootstrap 5.2
2022-08-02 07:51:33 -04:00
6968476ed0 Merge pull request #2327 from leigh-pointer/ScreenProgess
Added Progress Indicator
2022-08-02 07:50:59 -04:00
5d2c7c3058 Added Progress Indicator
When deleting large blocks of Pages, Modules or Notifications there was currently no visual feedback so added the ModuleInstance.ShowProgressIndicator() and ModuleInstance.HideProgressIndicator() calls to these processes.
2022-08-02 10:37:04 +02:00
e6cb90e545 Formating issues with Bootstrap 5.2 2022-08-02 08:55:42 +02:00
ec73f4dbea Merge pull request #2322 from leigh-pointer/Bootstrap5.2
Updated Bootstrap to 5.2
2022-08-01 17:36:10 -04:00
4f41a52ee7 Merge pull request #2325 from sbwalker/dev
fix upgrade issue for framework translations, improvements for managing module translations
2022-08-01 17:05:57 -04:00
c097956fcb fix upgrade issue for framework translations, improvements for managing module translations 2022-08-01 17:05:18 -04:00
8cbc17ed98 Theme Creator updated to Bootstrap 5.2.0 2022-07-28 20:59:52 +02:00
50d89d0f13 Updated Bootstrap to 5.2
Replaced Bootstrap cloudflare versions and Integrity keys to match 5.2.0
2022-07-28 20:52:11 +02:00
7b4d13b73e Merge pull request #2318 from oqtane/master
Merge pull request #2317 from oqtane/dev
2022-07-27 16:16:01 -04:00
2909aa1656 Merge pull request #2317 from oqtane/dev
3.1.4 release
2022-07-27 16:15:43 -04:00
64131b6764 Update README.md 2022-07-27 08:54:48 -04:00
0b78d75a21 Merge pull request #2314 from sbwalker/dev
prepare for 3.1.4 release
2022-07-26 17:22:27 -04:00
b35c342960 prepare for 3.1.4 release 2022-07-26 17:22:06 -04:00
24c858d379 Merge pull request #2313 from sbwalker/dev
support for module translation download/install
2022-07-26 14:44:25 -04:00
b8a31a8be9 support for module translation download/install 2022-07-26 14:44:06 -04:00
02c30d6454 Merge pull request #2312 from sbwalker/dev
add ability to supply connection string in Add Site
2022-07-26 10:13:14 -04:00
985f003e6d add ability to supply connection string in Add Site 2022-07-26 10:12:54 -04:00
98045e1e2e Merge pull request #2311 from sbwalker/dev
introduce ITransientService interface for auto registration of transient services (for DBContexts and Repositories)
2022-07-26 09:42:12 -04:00
5762ce58a4 introduce ITransientService interface for auto registration of transient services (for DBContexts and Repositories) 2022-07-26 09:41:42 -04:00
2787ee71fc Merge pull request #2310 from sbwalker/dev
Allow for entry of raw connection string during installation
2022-07-26 07:48:29 -04:00
e61a6df4d7 Allow for entry of raw connection string during installation 2022-07-26 07:48:04 -04:00
0a5c2ecbf5 Merge pull request #2304 from sbwalker/dev
optimize satellite assembly loading based on the new model where all cultures are available
2022-07-21 16:02:48 -04:00
6bfab696ad optimize satellite assembly loading based on the new model where all cultures are available 2022-07-21 16:02:23 -04:00
928f2dd496 Merge pull request #2302 from chlupac/FileServiceFix
FileService fix
2022-07-20 08:23:41 -04:00
bcf75892f7 FileService fix 2022-07-20 10:39:13 +02:00
594761385f Merge pull request #2301 from sbwalker/dev
add Environment to System Info
2022-07-19 14:34:10 -04:00
d05fba06ec add Environment to System Info 2022-07-19 14:33:51 -04:00
25155b1b38 Merge pull request #2295 from chlupac/AppSettingsFix
Fixed loading of alternative appsettings "appsettings.{env.EnvironmentName}.json"
2022-07-19 14:20:08 -04:00
ded6c9c199 Merge pull request #2299 from chlupac/InstallManFix
Fix - InstallationManager crash when package folders are missing
2022-07-19 13:12:43 -04:00
c62e6c0045 Merge pull request #2300 from sbwalker/dev
performance optimization for permissions
2022-07-19 10:49:52 -04:00
b3feda9fd1 performance optimization for permissions 2022-07-19 10:49:33 -04:00
7ef8e2c8b8 Fix - InstallationManager crash when package folders are missing 2022-07-19 09:42:12 +02:00
d5ff211871 Fixed loading of alternative appsettings "appsettings.{env.EnvironmentName}.json" 2022-07-18 21:34:59 +02:00
51c23e3842 Merge pull request #2294 from sbwalker/dev
use package name as a convention for identifying satellite assemblies
2022-07-18 13:14:53 -04:00
557b30815e use package name as a convention for identifying satellite assemblies 2022-07-18 13:14:34 -04:00
145459bfc3 Merge pull request #2292 from sbwalker/dev
Added version to Language Management, improved framework performance by loading languages into PageState, include all supported cultures and allow Administrator to add any language to a site regardless of translation availability, fix translation upgrade issue
2022-07-16 10:00:20 -04:00
f97a6a2bee Added version to Language Management, improved framework performance by loading languages into PageState, include all supported cultures and allow Administrator to add any language to a site regardless of translation availability, fix translation upgrade issue 2022-07-16 09:59:47 -04:00
1134422891 Merge pull request #2291 from sbwalker/dev
Fix #2282 - dynamically determine framework path when scaffolding project references
2022-07-15 16:00:23 -04:00
6012275c7b Fix #2282 - dynamically determine framework path when scaffolding project references 2022-07-15 15:59:55 -04:00
2f07063375 Merge pull request #2288 from sbwalker/dev
Fix #2285 - handle scenario where the module definition associated to a module instance does not exist
2022-07-14 16:58:43 -04:00
310d1ed485 Fix #2285 - handle scenario where the module definition associated to a module instance does not exist 2022-07-14 16:58:16 -04:00
a48edbb16e Merge pull request #2287 from sbwalker/dev
fixed issue in default site template where MIT License module was being created in invalid pane
2022-07-14 09:11:16 -04:00
d6258409fc fixed issue in default site template where MIT License module was being created in invalid pane 2022-07-14 09:10:51 -04:00
1b94b1247b Merge pull request #2284 from sbwalker/dev
Fix #2280 - add 404 page on upgrade, Fix #2279 add message indicating a restart is required to activate scheduled jobs after installation, add Package Name to Module and Theme management
2022-07-13 15:19:07 -04:00
9ef63ae60e Fix #2280 - add 404 page on upgrade, Fix #2279 add message indicating a restart is required to activate scheduled jobs after installation, add Package Name to Module and Theme management 2022-07-13 15:18:41 -04:00
f99de4be48 Merge pull request #2272 from sbwalker/dev
Support for module editors by exposing Edit Mode in the Control Panel
2022-07-06 17:25:43 -04:00
80fd1820c2 Support for module editors by exposing Edit Mode in the Control Panel 2022-07-06 17:25:08 -04:00
0a4a983d20 Merge pull request #2269 from leigh-pointer/Resx
Added Missing Resource from Oqtane Theme Settings
2022-07-06 08:37:25 -04:00
c2cc830691 Added Missing Resource from Oqtane Theme Settings
Missing Resource string added
2022-07-04 21:37:42 +02:00
4d0490d1c6 Merge pull request #2260 from sbwalker/dev
FIx issue with redirect after site delete and remove tenant if it is empty
2022-06-28 08:17:39 -04:00
02d1838547 FIx issue with redirect after site delete and remove tenant if it is empty 2022-06-28 08:17:06 -04:00
5c6edff778 Update README.md 2022-06-27 16:35:50 -04:00
7cbca32ddd Update README.md 2022-06-27 16:16:58 -04:00
8950e315f8 Merge pull request #2258 from oqtane/master
Merge pull request #2257 from oqtane/dev
2022-06-27 16:12:27 -04:00
a703df40c0 Merge pull request #2257 from oqtane/dev
3.1.3 release
2022-06-27 16:12:05 -04:00
225fce8810 Merge pull request #2256 from sbwalker/dev
3.1.3 data provider packages
2022-06-27 15:43:42 -04:00
bdc0f0fcdd 3.1.3 data provider packages 2022-06-27 15:43:24 -04:00
5bbb8c4858 Merge pull request #2255 from sbwalker/dev
Add schema support to BaseEntityBuilder
2022-06-27 13:55:08 -04:00
35b9551bfb Add schema support to BaseEntityBuilder 2022-06-27 13:54:52 -04:00
c8c5a05b39 Merge pull request #2254 from sbwalker/dev
Fix #2249 Fix #2250 - issues with site deletion
2022-06-27 13:46:02 -04:00
2771f0301a Fix #2249 Fix #2250 - issues with site deletion 2022-06-27 13:45:42 -04:00
c4f04edc59 Merge pull request #2253 from sbwalker/dev
Fix #2252 - unable to insert images into rich text editor
2022-06-27 12:24:16 -04:00
5530422846 Fix #2252 - unable to insert images into rich text editor 2022-06-27 12:23:55 -04:00
ce77c81fb5 Merge pull request #2248 from sbwalker/dev
prepare for 3.1.3
2022-06-21 09:25:33 -04:00
bc488d4ac2 prepare for 3.1.3 2022-06-21 09:25:14 -04:00
fe72a10346 Merge pull request #2247 from sbwalker/dev
fix #2239 - email notification encoding to support all cultures
2022-06-20 19:42:50 -04:00
0da88398b4 fix #2239 - email notification encoding to support all cultures 2022-06-20 19:42:32 -04:00
c42de3da20 Merge pull request #2246 from sbwalker/dev
fix #2245 - default database type not set correctly when adding new site for any DB other than LocalDB, added Source: info to all extension installation scenarios now that the Registry supports both Nuget and GitHub locations
2022-06-20 17:45:10 -04:00
4bf9f36baa fix #2245 - default database type not set correctly when adding new site for any DB other than LocalDB, added Source: info to all extension installation scenarios now that the Registry supports both Nuget and GitHub locations 2022-06-20 17:44:49 -04:00
380cb192c7 Merge pull request #2244 from sbwalker/dev
allow multiple aliases to be defined as default
2022-06-18 09:18:47 -04:00
8882e19ec5 allow multiple aliases to be defined as default 2022-06-18 09:18:23 -04:00
7f52059b98 Merge pull request #2243 from sbwalker/dev
added extension method for creating a LocalizerFactory using a type name, refactored Pager and LocalizableComponent to use LocalizerFactory
2022-06-15 16:19:51 -04:00
1ce3cc4d7c added extension method for creating a LocalizerFactory using a type name, refactored Pager and LocalizableComponent to use LocalizerFactory 2022-06-15 16:19:22 -04:00
657c71e94d Merge pull request #2242 from leigh-pointer/Issue#2234
Fix for Recycle bin not showing Deleted Date (Issue #2234)
2022-06-14 10:26:50 -04:00
c8cfb3c7b7 Fix for Recycle bin not showing Deleted Date (Issue #2234)
Added the Deleted data to Module from the PageModule
2022-06-14 09:22:12 +02:00
6e7e90acf4 Merge pull request #2241 from sbwalker/dev
additional changes for #2228
2022-06-13 09:10:20 -04:00
6d3a556d34 additional changes for #2228 2022-06-13 09:10:01 -04:00
7d9188b659 Merge pull request #2238 from chlupac/LogExceptions
Exception is not saved to log
2022-06-09 15:13:10 -04:00
4f0a805c79 Exception is not saved to log 2022-06-09 10:24:13 +02:00
53f3320492 Merge pull request #2228 from chlupac/Log_notification_improvement
Log notification improvement
2022-06-08 15:48:17 -04:00
f9ce51b4a5 Merge pull request #2222 from hishamco/pager
Pager should inherits from LocalizableComponent
2022-06-08 15:47:25 -04:00
f8bf432c0d Merge pull request #2237 from sbwalker/dev
Improvements for #2229 - relax userrole restrictions
2022-06-08 15:47:12 -04:00
a822482172 Improvements for #2229 - relax userrole restrictions 2022-06-08 15:46:36 -04:00
b22f8a0b02 Remove generic type suffix properly 2022-06-08 00:12:10 +03:00
227331bf24 Merge branch 'dev' into pager 2022-06-07 23:37:08 +03:00
744688cbe1 Merge pull request #2235 from sbwalker/dev
Fix #2230 - add support for an Unauthenticated User global role
2022-06-07 15:26:05 -04:00
79c8126c4a Fix #2230 - add support for an Unauthenticated User global role 2022-06-07 15:25:44 -04:00
0b7c8e4ef7 Merge pull request #2232 from ijaz-saeed/dev
missing translation keys for module names
2022-06-07 15:23:08 -04:00
35e00f61d8 Update README.md 2022-06-07 09:38:42 -04:00
aba3d58df8 missing translation keys for module names 2022-06-06 10:12:00 +05:00
45984a8166 Merge pull request #2231 from sbwalker/dev
Improvements for #2221 - validate file extensions client-side before initiating upload, validate file extension server-side before writing part to disk, optimize part cleanup logic, add error handling to JavaScript XMLHttpRequest, ensure FileInput gets initialized after upload
2022-06-04 15:41:38 -04:00
ea5655ae42 Improvements for #2221 - validate file extensions client-side before initiating upload, valid file extension server-side before writing part to disk, optimize cleanup logic, add error handling to JavaScript XMLHttpRequest, ensure FileInput gets initialized after upload 2022-06-04 15:40:26 -04:00
f06cb0dfbb Log notification improvement 2022-05-31 11:33:42 +02:00
1abae55976 Remove magic string 2022-05-31 11:59:17 +03:00
a83ed40ec4 Avoid breaking changes 2022-05-31 10:40:42 +03:00
75ecae4672 Merge pull request #2223 from hishamco/toggle-password
Avoid toggle password & confirm password as same time when one button is clicked
2022-05-28 21:59:40 -04:00
16a6f942c5 Avoid toggle password & confirm password as same time when one button is clicked 2022-05-27 16:20:35 +03:00
aa98508e57 Remove the entry from SharedResources 2022-05-27 15:37:13 +03:00
583383aee1 Pager should inherits from LocalizableComponent 2022-05-27 15:33:48 +03:00
13f69f81d7 Merge pull request #2219 from sbwalker/dev
fix #2213 - disabling show on all pages
2022-05-26 01:19:35 -04:00
43c34fcd64 fix #2213 - disabling show on all pages 2022-05-26 01:19:14 -04:00
c272238539 Merge pull request #2217 from leigh-pointer/MissingRes2215
Fix for missing Resx entries #2215
2022-05-25 07:23:01 -04:00
6e0d2706a8 Fix for missing Resx entries #2215
Updated the Resx File with the missing entries.  

However the UserProfile\Add cant be Localized at present as the IStringLocalizer is not loaded when that property assignment is executed.
2022-05-25 11:13:38 +02:00
25a7289ce8 Merge pull request #2210 from leigh-pointer/PageOf
Fix for #2209 Localization Pager component (Page @_page of @_pages)
2022-05-24 21:47:12 -04:00
1ba7d045e4 Merge pull request #2211 from elgransan/dev
* Collapse menu after click on a page (mobile version)
2022-05-24 21:46:11 -04:00
a4d75befe7 * Collapse menu after click on a page (mobile version) 2022-05-22 20:05:01 -03:00
88377529bc Fix for #2209 Localization Pager component (Page @_page of @_pages)
Added fixe for issue.  Added the resource "PageOfPages" to SharedResources as trying to inject IStringLocalizer<Pager<TableItem>> and adding Pager.resx the resource failed to load.
2022-05-22 20:23:17 +02:00
91b9a0280f Merge pull request #2207 from sbwalker/dev
Add filtering by Category to Module Management - default to Common
2022-05-21 10:11:24 -04:00
25173ae85c Ddd filtering by Category to Module Management - default to Common 2022-05-21 10:10:57 -04:00
ad3350705e Update README.md 2022-05-19 11:44:38 -04:00
473 changed files with 17166 additions and 8242 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
Oqtane.Server/wwwroot/Modules/Templates/External/Package/*.sh eol=lf
Oqtane.Server/wwwroot/Themes/Templates/External/Package/*.sh eol=lf

2
.gitignore vendored
View File

@ -10,6 +10,8 @@ msbuild.binlog
*.zip
*.idea
_ReSharper.Caches
.DS_Store
Oqtane.Server/appsettings.json
Oqtane.Server/Data

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018-2022 .NET Foundation
Copyright (c) 2018-2023 .NET Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,8 @@
@using Microsoft.AspNetCore.Http
@inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime
@inject SiteState SiteState
@inject IServiceProvider ServiceProvider
@if (_initialized)
{
@ -30,35 +32,47 @@
}
@code {
[Parameter]
public string AntiForgeryToken { get; set; }
[Parameter]
public string AntiForgeryToken { get; set; }
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public int VisitorId { get; set; }
[Parameter]
public int VisitorId { get; set; }
[Parameter]
public string RemoteIPAddress { get; set; }
[Parameter]
public string RemoteIPAddress { get; set; }
[Parameter]
public string AuthorizationToken { get; set; }
[Parameter]
public string AuthorizationToken { get; set; }
private bool _initialized = false;
private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" };
private bool _initialized = false;
private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" };
private PageState PageState { get; set; }
private PageState PageState { get; set; }
protected override async Task OnParametersSetAsync()
{
SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken;
private IHttpContextAccessor accessor;
protected override async Task OnParametersSetAsync()
{
SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken;
accessor = (IHttpContextAccessor)ServiceProvider.GetService(typeof(IHttpContextAccessor));
if (accessor != null)
{
SiteState.IsPrerendering = !accessor.HttpContext.Response.HasStarted;
}
else
{
SiteState.IsPrerendering = true;
}
_installation = await InstallationService.IsInstalled();
if (_installation.Alias != null)
@ -72,6 +86,7 @@
{
if (firstRender)
{
// prevents flash on initial page load
_display = "";
StateHasChanged();
}

View File

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

View File

@ -16,7 +16,7 @@ namespace Microsoft.Extensions.DependencyInjection
return services;
}
internal static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
public static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
{
services.AddScoped<SiteState>();
services.AddScoped<IInstallationService, InstallationService>();

64
Oqtane.Client/Head.razor Normal file
View File

@ -0,0 +1,64 @@
@using System.ComponentModel
@using Oqtane.Shared
@inject SiteState SiteState
@if (!string.IsNullOrEmpty(_title))
{
@((MarkupString)_title)
}
@if (!string.IsNullOrEmpty(_content))
{
@((MarkupString)_content)
}
@code {
private string _title = "";
private string _content = "";
protected override void OnInitialized()
{
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged += PropertyChanged;
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "PageTitle":
var title = "\n<title>" + SiteState.Properties.PageTitle + "</title>";
if (title != _title)
{
_title = title;
StateHasChanged();
}
break;
case "HeadContent":
var content = RemoveScripts(SiteState.Properties.HeadContent) + "\n";
if (content != _content)
{
_content = content;
StateHasChanged();
}
break;
}
}
private string RemoveScripts(string headcontent)
{
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");
}
}
return headcontent;
}
public void Dispose()
{
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged -= PropertyChanged;
}
}

View File

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

View File

@ -5,15 +5,17 @@
@inject ISiteService SiteService
@inject IUserService UserService
@inject IDatabaseService DatabaseService
@inject ISiteTemplateService SiteTemplateService
@inject IJSRuntime JSRuntime
@inject IStringLocalizer<Installer> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject SiteState SiteState
<div class="container">
<div class="row">
<div class="mx-auto text-center">
<img src="oqtane-black.png" />
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version</div>
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 7)</div>
</div>
</div>
<hr class="app-rule" />
@ -26,28 +28,41 @@
<div class="col-sm-9">
@if (_databases != null)
{
<select id="databasetype" class="form-select custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
@foreach (var database in _databases)
{
if (database.IsDefault)
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
}
</select>
<div class="input-group">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
@if (!_showConnectionString)
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
}
</div>
}
</div>
</div>
@{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent;
}
}
@if (!_showConnectionString)
{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent
}
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
</div>
</div>
}
</div>
</div>
<div class="col text-center">
@ -63,8 +78,8 @@
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div>
</div>
@ -72,8 +87,8 @@
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
</div>
</div>
</div>
@ -83,6 +98,20 @@
<input type="text" class="form-control" @bind="@_hostEmail" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="template" HelpText="Select a site template" ResourceKey="Template">Template:</Label>
<div class="col-sm-9">
@if (_templates != null)
{
<select id="template" class="form-select" @bind="@_template" required>
@foreach (var template in _templates)
{
<option value="@template.TypeName">@template.Name</option>
}
</select>
}
</div>
</div>
</div>
</div>
</div>
@ -90,7 +119,13 @@
<div class="row">
<div class="mx-auto text-center">
<button type="button" class="btn btn-success" @onclick="Install">@Localizer["InstallNow"]</button><br /><br />
<ModuleMessage Message="@_message" Type="MessageType.Error"></ModuleMessage>
@if (!string.IsNullOrEmpty(_message))
{
<div class="alert alert-danger alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message)
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
</div>
}
</div>
<div class="app-progress-indicator" style="@_loadingDisplay"></div>
</div>
@ -102,25 +137,37 @@
</div>
@code {
private List<Database> _databases;
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private List<Database> _databases;
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty;
private bool _register = true;
private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty;
private string _passwordType = "password";
private string _confirmPasswordType = "password";
private string _togglePassword = string.Empty;
private string _toggleConfirmPassword = string.Empty;
private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty;
private List<SiteTemplate> _templates;
private string _template = Constants.DefaultSiteTemplate;
private bool _register = true;
private string _message = string.Empty;
private string _loadingDisplay = "display: none;";
protected override async Task OnInitializedAsync()
{
_togglepassword = SharedLocalizer["ShowPassword"];
// 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\"/>";
SiteState.AppendHeadContent(content);
_togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
{
@ -131,14 +178,16 @@
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
}
_templates = await SiteTemplateService.GetSiteTemplatesAsync();
}
private void DatabaseChanged(ChangeEventArgs eventArgs)
{
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
@ -166,18 +215,25 @@
{
if (firstRender)
{
// include JavaScript
var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", "text/css", "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", "anonymous", "");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", "anonymous", "", "head");
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");
}
}
private async Task Install()
{
var connectionString = String.Empty;
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
if (_showConnectionString)
{
connectionString = databaseConfigControl.GetConnectionString();
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
}
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
@ -203,7 +259,8 @@
TenantName = TenantNames.Master,
IsNewTenant = true,
SiteName = Constants.DefaultSite,
Register = _register
Register = _register,
SiteTemplate = _template
};
var installation = await InstallationService.Install(config);
@ -221,24 +278,53 @@
{
_message = Localizer["Message.Password.Invalid"];
}
}
else
{
_message = Localizer["Message.Require.DbInfo"];
}
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
_message = Localizer["Message.Require.DbInfo"];
}
}
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
private void ToggleConfirmPassword()
{
if (_confirmPasswordType == "password")
{
_confirmPasswordType = "text";
_toggleConfirmPassword = SharedLocalizer["HidePassword"];
}
else
{
_confirmPasswordType = "password";
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
}
}
private void ToggleConnectionString()
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
_connectionString = databaseConfigControl.GetConnectionString();
}
_showConnectionString = !_showConnectionString;
}
private void DismissModal()
{
_message = "";
StateHasChanged();
}
}

View File

@ -4,29 +4,35 @@
@inject IUserService UserService
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row">
@foreach (var p in _pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
@if (_pages != null)
{
<div class="row">
@foreach (var p in _pages)
{
string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center">
<NavLink class="nav-link" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
</NavLink>
</div>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center mb-3">
<NavLink class="nav-link text-primary" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
</NavLink>
</div>
}
}
}
</div>
</div>
}
@code {
private List<Page> _pages;
private List<Page> _pages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override void OnInitialized()
{
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
_pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList();
protected override void OnInitialized()
{
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null)
{
_pages = PageState.Pages.Where(item => item.ParentId == admin.PageId).ToList();
}
}
}

View File

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

View File

@ -62,8 +62,7 @@
<div class="row mb-1 align-items-center">
<div class="col-sm-12">
<Label Class="col-sm-3" For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" Permissions="@_permissions" @ref="_permissionGrid" />
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" PermissionList="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</div>
@ -99,7 +98,7 @@
private string _imagesizes = string.Empty;
private string _capacity = "0";
private bool _isSystem;
private string _permissions = string.Empty;
private List<Permission> _permissions = null;
private string _createdBy;
private DateTime _createdOn;
private string _modifiedBy;
@ -131,7 +130,7 @@
_imagesizes = folder.ImageSizes;
_capacity = folder.Capacity.ToString();
_isSystem = folder.IsSystem;
_permissions = folder.Permissions;
_permissions = folder.PermissionList;
_createdBy = folder.CreatedBy;
_createdOn = folder.CreatedOn;
_modifiedBy = folder.ModifiedBy;
@ -196,7 +195,7 @@
folder.ImageSizes = _imagesizes;
folder.Capacity = int.Parse(_capacity);
folder.IsSystem = _isSystem;
folder.Permissions = _permissionGrid.GetPermissions();
folder.PermissionList = _permissionGrid.GetPermissionList();
if (_folderId != -1)
{

View File

@ -132,22 +132,10 @@
_isEnabled = job.IsEnabled.ToString();
_interval = job.Interval.ToString();
_frequency = job.Frequency;
_startDate = job.StartDate;
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{
_startTime = job.StartDate.Value.ToString("HH:mm");
}
_endDate = job.EndDate;
if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0)
{
_endTime = job.EndDate.Value.ToString("HH:mm");
}
(_startDate, _startTime) = Utilities.UtcAsLocalDateAndTime(job.StartDate);
(_endDate, _endTime) = Utilities.UtcAsLocalDateAndTime(job.EndDate);
_retentionHistory = job.RetentionHistory.ToString();
_nextDate = job.NextExecution;
if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0)
{
_nextTime = job.NextExecution.Value.ToString("HH:mm");
}
(_nextDate, _nextTime) = Utilities.UtcAsLocalDateAndTime(job.NextExecution);
createdby = job.CreatedBy;
createdon = job.CreatedOn;
modifiedby = job.ModifiedBy;
@ -180,50 +168,27 @@
{
job.Interval = int.Parse(_interval);
}
job.StartDate = _startDate;
if (job.StartDate != null)
{
job.StartDate = job.StartDate.Value.Date;
if (!string.IsNullOrEmpty(_startTime))
{
job.StartDate = DateTime.Parse(job.StartDate.Value.ToShortDateString() + " " + _startTime);
}
}
job.EndDate = _endDate;
if (job.EndDate != null)
{
job.EndDate = job.EndDate.Value.Date;
if (!string.IsNullOrEmpty(_endTime))
{
job.EndDate = DateTime.Parse(job.EndDate.Value.ToShortDateString() + " " + _endTime);
}
}
job.RetentionHistory = int.Parse(_retentionHistory);
job.NextExecution = _nextDate;
if (job.NextExecution != null)
{
job.NextExecution = job.NextExecution.Value.Date;
if (!string.IsNullOrEmpty(_nextTime))
{
job.NextExecution = DateTime.Parse(job.NextExecution.Value.ToShortDateString() + " " + _nextTime);
}
}
job.StartDate = Utilities.LocalDateAndTimeAsUtc(_startDate, _startTime);
job.EndDate = Utilities.LocalDateAndTimeAsUtc(_endDate, _endTime);
job.RetentionHistory = int.Parse(_retentionHistory);
job.NextExecution = Utilities.LocalDateAndTimeAsUtc(_nextDate, _nextTime);
try
{
job = await JobService.UpdateJobAsync(job);
await logger.LogInformation("Job Updated {Job}", job);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
}
}
try
{
job = await JobService.UpdateJobAsync(job);
await logger.LogInformation("Job Updated {Job}", job);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
}
}
}

View File

@ -33,7 +33,7 @@ else
<td>@context.Name</td>
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
<td>@context.NextExecution</td>
<td>@context.NextExecution?.ToLocalTime()</td>
<td>
@if (context.IsStarted)
{
@ -56,6 +56,10 @@ else
protected override async Task OnParametersSetAsync()
{
_jobs = await JobService.GetJobsAsync();
if (_jobs.Count == 0)
{
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
}
}
private string DisplayStatus(bool isEnabled, bool isExecuting)

View File

@ -9,7 +9,7 @@
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_supportedCultures == null)
@if (_cultures == null)
{
<p><em>@SharedLocalizer["Loading"]</em></p>
}
@ -17,330 +17,112 @@ else
{
<TabStrip>
<TabPanel Name="Manage" ResourceKey="Manage">
@if (_availableCultures.Count() == 0)
{
<ModuleMessage Type="MessageType.Info" Message="@_message"></ModuleMessage>
}
else
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
<div class="col-sm-9">
<select id="_code" class="form-select" @bind="@_code" required>
@foreach (var culture in _availableCultures)
{
<option value="@culture.Name">@culture.DisplayName</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
<div class="col-sm-9">
<select id="default" class="form-select" @bind="@_isDefault" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
<div class="col-sm-9">
<select id="_code" class="form-select" @bind="@_code" required>
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var culture in _cultures)
{
<option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option>
}
</select>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
</form>
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
<TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
<div class="col-sm-9">
<select id="default" class="form-select" @bind="@_default" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</div>
@if (_packages != null)
{
@if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
}
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Language: </Label>
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install." ResourceKey="LanguageUpload">Translation: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
</TabStrip>
}
@if (_productname != "")
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
{
@((MarkupString)_license)
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
@code {
private ElementReference form;
private bool validated = false;
private ElementReference form;
private bool validated = false;
private string _code = string.Empty;
private string _isDefault = "False";
private string _message;
private IEnumerable<Culture> _supportedCultures;
private IEnumerable<Culture> _availableCultures;
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
private string _code = "-";
private string _default = "False";
private List<string> _languages;
private IEnumerable<Culture> _cultures;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var languagesCodes = languages.Select(l => l.Code).ToList();
protected override async Task OnParametersSetAsync()
{
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
_languages = languages.Select(l => l.Code).ToList();
_supportedCultures = await LocalizationService.GetCulturesAsync();
_availableCultures = _supportedCultures
.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
await LoadTranslations();
await LoadCultures();
}
if (_supportedCultures.Count() == 1)
{
_message = Localizer["OnlyEnglish"];
}
else if (_availableCultures.Count() == 0)
{
_message = Localizer["AllLanguages"];
}
}
private async Task LoadCultures()
{
_cultures = await LocalizationService.GetCulturesAsync(false);
_cultures = _cultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !_languages.Contains(c.Name));
_code = "-";
}
private async Task LoadTranslations()
{
_packages = await PackageService.GetPackagesAsync("translation", _search, _price, "");
}
private async Task SaveLanguage()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form) && _code != "-")
{
var language = new Language
{
SiteId = PageState.Page.SiteId,
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
Code = _code,
IsDefault = (_default == null ? false : Boolean.Parse(_default))
};
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadTranslations();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
try
{
language = await LanguageService.AddLanguageAsync(language);
private async Task Search()
{
try
{
await LoadTranslations();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
if (language.IsDefault)
{
await SetCultureAsync(language.Code);
}
private async Task Reset()
{
try
{
_search = "";
await LoadTranslations();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
}
await logger.LogInformation("Language Added {Language}", language);
private async Task SaveLanguage()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
var language = new Language
{
SiteId = PageState.Page.SiteId,
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
Code = _code,
IsDefault = (_isDefault == null ? false : Boolean.Parse(_isDefault))
};
try
{
language = await LanguageService.AddLanguageAsync(language);
if (language.IsDefault)
{
await SetCultureAsync(language.Code);
}
await logger.LogInformation("Language Added {Language}", language);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void HideModal()
{
_productname = "";
_license = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Language.Download"], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Translation {Name} {Version}", _packageid, _version);
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
}
}
private async Task InstallLanguages()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task SetCultureAsync(string culture)
{
@ -353,4 +135,9 @@ else
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -20,52 +20,113 @@ else
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th>
<th>@Localizer["Default"]</th>
<th style="width: 1px;">&nbsp;</th>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<th style="width: 1px;">@Localizer["Translation"]</th>
<th style="width: 1px;">&nbsp;</th>
}
</Header>
<Row>
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
<td>@context.Name</td>
<td>@context.Code</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td>
@if (UpgradeAvailable(context.Code))
{
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
}
</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td>
@{
var translation = TranslationAvailable(context.Code, context.Version);
}
@if (translation != null)
{
if (string.IsNullOrEmpty(context.Version))
{
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code, translation.Version))>@SharedLocalizer["Download"]</button>
}
else
{
if (Version.Parse(translation.Version).CompareTo(Version.Parse(context.Version)) > 0)
{
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code, translation.Version))>@SharedLocalizer["Upgrade"]</button>
}
}
}
</td>
}
</Row>
</Pager>
}
@if (_package != null)
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h4 style="display: inline;"><a href="@_package.ProductUrl" target="_new">@_package.Name</a></h4><br />
@SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@_package.OwnerUrl" target="new">@_package.Owner</a></strong><br />
@(_package.Description.Length > 400 ? (_package.Description.Substring(0, 400) + "...") : _package.Description)<br />
<strong>@(String.Format("{0:n0}", _package.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@_package.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@_package.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(_package.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(_package.PackageUrl).Host + "</strong>" : ""))
<br /><br />
@if (!string.IsNullOrEmpty(_package.License))
{
@((MarkupString)_package.License.Replace("\n", "<br />"))
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
@code {
private List<Language> _languages;
private List<Package> _packages;
private Package _package;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var cultures = await LocalizationService.GetCulturesAsync();
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
// Adds English as default language
_languages.Insert(0, new Language { Name = culture.DisplayName, Code = culture.Name, IsDefault = !_languages.Any(l => l.IsDefault) });
await GetLanguages();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
_packages = await PackageService.GetPackagesAsync("translation");
}
}
private async Task GetLanguages()
{
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
}
private async Task DeleteLanguage(Language language)
{
try
{
await LanguageService.DeleteLanguageAsync(language.LanguageId);
await logger.LogInformation("Language Deleted {Language}", language);
await GetLanguages();
StateHasChanged();
}
catch (Exception ex)
@ -76,37 +137,53 @@ else
}
}
private bool UpgradeAvailable(string code)
private Package TranslationAvailable(string code, string version)
{
var upgradeavailable = false;
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + ".Client." + code)).FirstOrDefault();
if (package != null)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0);
}
}
return upgradeavailable;
return _packages?.FirstOrDefault(item => item.PackageId == (Constants.PackageId + "." + code));
}
private async Task DownloadLanguage(string code)
private async Task GetPackage(string code, string version)
{
try
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
_package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, version);
if (_package != null)
{
await PackageService.DownloadPackageAsync(Constants.PackageId + ".Client." + code, Constants.Version, Constants.PackagesFolder);
await logger.LogInformation("Translation Downloaded {Code} {Version}", code, Constants.Version);
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Translation {Code} {Version} {Error}", code, Constants.Version, ex.Message);
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
_package = null;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Translation {Name} {Version}", _package.PackageId, _package.Version);
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
}
}
private void HideModal()
{
_package = null;
StateHasChanged();
}
}

View File

@ -1,9 +1,9 @@
@using System.Net
@namespace Oqtane.Modules.Admin.Login
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IServiceProvider ServiceProvider
@inject SiteState SiteState
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -34,7 +34,7 @@
<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">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
<div class="form-group mt-2">
@ -184,11 +184,12 @@
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (!twofactor)
{
user = await UserService.LoginUserAsync(user);
user = await UserService.LoginUserAsync(user, hybrid, _remember);
}
else
{
@ -199,10 +200,21 @@
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
// post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields);
if (hybrid)
{
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider
.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
}
else
{
// post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields);
}
}
else
{

View File

@ -37,7 +37,7 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
<Label Class="col-sm-3" For="category" HelpText="The fully qualified type type that was affected" ResourceKey="Category">Type Name: </Label>
<div class="col-sm-9">
<input id="category" class="form-control" @bind="@_category" readonly />
</div>
@ -130,13 +130,14 @@
private string _properties = string.Empty;
private string _server = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override string UrlParametersTemplate => "/{id}";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_logId = Int32.Parse(PageState.QueryString["id"]);
_logId = Int32.Parse(UrlParameters["id"]);
var log = await LogService.GetLogAsync(_logId);
if (log != null)
{
@ -191,13 +192,6 @@
private string CloseUrl()
{
if (!PageState.QueryString.ContainsKey("level"))
{
return NavigateUrl();
}
else
{
return NavigateUrl(PageState.Page.Path, "level=" + PageState.QueryString["level"] + "&function=" + PageState.QueryString["function"] + "&rows=" + PageState.QueryString["rows"] + "&page=" + PageState.QueryString["page"]);
}
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Logs
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ILogService LogService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@ -62,7 +63,7 @@ else
<th>@Localizer["Function"]</th>
</Header>
<Row>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"id=" + context.LogId.ToString() + "&level=" + _level + "&function=" + _function + "&rows=" + _rows + "&page=" + _page.ToString())" ResourceKey="LogDetails" /></td>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
<td class="@GetClass(context.Function)">@context.LogDate</td>
<td class="@GetClass(context.Function)">@context.Level</td>
<td class="@GetClass(context.Function)">@context.Feature</td>
@ -91,32 +92,33 @@ else
}
@code {
private string _level = "-";
private string _function = "-";
private string _rows = "10";
private string _level = "-";
private string _function = "-";
private string _rows = "10";
private int _page = 1;
private List<Log> _logs;
private string _retention = "";
private List<Log> _logs;
private string _retention = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
if (PageState.QueryString.ContainsKey("level"))
protected override async Task OnParametersSetAsync()
{
try
{
if (UrlParameters.ContainsKey("level"))
{
_level = PageState.QueryString["level"];
_level = UrlParameters["level"];
}
if (PageState.QueryString.ContainsKey("function"))
if (UrlParameters.ContainsKey("function"))
{
_function = PageState.QueryString["function"];
_function = UrlParameters["function"];
}
if (PageState.QueryString.ContainsKey("rows"))
if (UrlParameters.ContainsKey("rows"))
{
_rows = PageState.QueryString["rows"];
_rows = UrlParameters["rows"];
}
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
{
_page = page;
}
@ -233,4 +235,15 @@ else
_page = page;
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// external link to log item will display Details component
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id))
{
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"/{id}"));
}
}
}
}

View File

@ -85,7 +85,7 @@ else
private List<Template> _templates;
private string _template = "-";
private string[] _versions;
private string _reference = Constants.Version;
private string _reference = "local";
private string _minversion = "2.0.0";
private string _location = string.Empty;
@ -131,7 +131,7 @@ else
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
settings = SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
GetLocation();
@ -174,7 +174,7 @@ else
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private void TemplateChanged(ChangeEventArgs e)
@ -195,7 +195,7 @@ else
if (_owner != "" && _module != "" && _template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _module;
_location = template.Location + _owner + ".Module." + _module;
}
StateHasChanged();

View File

@ -35,6 +35,7 @@
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@ -64,13 +65,15 @@
</div>
}
}
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>
</div>
</div>
@ -90,9 +93,9 @@
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
@if (!string.IsNullOrEmpty(_packagelicense))
{
@((MarkupString)_license)
@((MarkupString)_packagelicense)
}
else
{
@ -110,7 +113,6 @@
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
@ -118,9 +120,9 @@
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
private string _packagelicense = "";
private string _packageversion = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -197,7 +199,7 @@
private void HideModal()
{
_productname = "";
_license = "";
_packagelicense = "";
StateHasChanged();
}
@ -209,14 +211,19 @@
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
_packageversion = package.Version;
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
StateHasChanged();
}
catch (Exception ex)
{
@ -229,30 +236,22 @@
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Module.Download"], MessageType.Success);
await PackageService.DownloadPackageAsync(_packageid, _packageversion, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
_productname = "";
_license = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task InstallModules()
private void OnUpload()
{
try
{
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Module");
}
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -139,7 +139,7 @@
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private void TemplateChanged(ChangeEventArgs e)
@ -151,7 +151,7 @@
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
}
GetLocation();
GetLocation();
}
private void GetLocation()

View File

@ -1,110 +1,223 @@
@namespace Oqtane.Modules.Admin.ModuleDefinitions
@inherits ModuleBase
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
@inject ILanguageService LanguageService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Definition" ResourceKey="Definition">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Definition" ResourceKey="Definition">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="The description of the module" ResourceKey="Description">Description: </Label>
<div class="col-sm-9">
<textarea id="description" class="form-control" @bind="@_description" rows="2" maxlength="2000" required></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
<div class="col-sm-9">
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is module enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="The description of the module" ResourceKey="Description">Description: </Label>
<div class="col-sm-9">
<textarea id="description" class="form-control" @bind="@_description" rows="2" maxlength="2000" required></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
<div class="col-sm-9">
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="moduledefinitionname" class="form-control" @bind="@_moduledefinitionname" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the module" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the module" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the module" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtimes" HelpText="The Blazor runtimes which this module supports" ResourceKey="Runtimes">Runtimes: </Label>
<div class="col-sm-9">
<input id="runtimes" class="form-control" @bind="@_runtimes" disabled />
</div>
</div>
</div>
</Section>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</form>
<Section Name="Information" ResourceKey="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="moduledefinitionname" class="form-control" @bind="@_moduledefinitionname" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the module" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the module" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the module" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtimes" HelpText="The Blazor runtimes which this module supports" ResourceKey="Runtimes">Runtimes: </Label>
<div class="col-sm-9">
<input id="runtimes" class="form-control" @bind="@_runtimes" disabled />
</div>
</div>
</div>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
<TabPanel Name="Translations" ResourceKey="Translations">
@if (_languages != null && _languages.Count > 0)
{
<Pager Items="@_languages">
<Header>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th>
<th style="width: 1px;">@Localizer["Version"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td>@context.Name</td>
<td>@context.Code</td>
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td>
@switch (TranslationAvailable(_packagename + "." + context.Code, context.Version))
{
case "install":
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button>
break;
case "upgrade":
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
break;
}
</td>
</Row>
</Pager>
}
else
{
<br />
<div class="mx-auto text-center">
@if (string.IsNullOrEmpty(_packagename))
{
@Localizer["Search.PackageNameMissing"]
}
else
{
@Localizer["Search.NoResults"]
}
</div>
<br />
}
</TabPanel>
</TabStrip>
@if (_package != null)
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h4 style="display: inline;"><a href="@_package.ProductUrl" target="_new">@_package.Name</a></h4><br />
@SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@_package.OwnerUrl" target="new">@_package.Owner</a></strong><br />
@(_package.Description.Length > 400 ? (_package.Description.Substring(0, 400) + "...") : _package.Description)<br />
<strong>@(String.Format("{0:n0}", _package.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@_package.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@_package.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(_package.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(_package.PackageUrl).Host + "</strong>" : ""))
<br /><br />
@if (!string.IsNullOrEmpty(_package.License))
{
@((MarkupString)_package.License.Replace("\n", "<br />"))
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
}
@code {
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private int _moduleDefinitionId;
private string _name;
private string _version;
private string _categories;
private string _moduledefinitionname = "";
private string _description = "";
private string _categories;
private string _isenabled;
private string _moduledefinitionname = "";
private string _version;
private string _packagename = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private string _runtimes = "";
private string _permissions;
private List<Permission> _permissions = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
@ -114,6 +227,10 @@
private PermissionGrid _permissionGrid;
#pragma warning restore 649
private List<Package> _packages;
private List<Language> _languages;
private Package _package;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
@ -125,20 +242,39 @@
if (moduleDefinition != null)
{
_name = moduleDefinition.Name;
_version = moduleDefinition.Version;
_categories = moduleDefinition.Categories;
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_description = moduleDefinition.Description;
_categories = moduleDefinition.Categories;
_isenabled = moduleDefinition.IsEnabled.ToString();
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_version = moduleDefinition.Version;
_packagename = moduleDefinition.PackageName;
_owner = moduleDefinition.Owner;
_url = moduleDefinition.Url;
_contact = moduleDefinition.Contact;
_license = moduleDefinition.License;
_runtimes = moduleDefinition.Runtimes;
_permissions = moduleDefinition.Permissions;
_permissions = moduleDefinition.PermissionList;
_createdby = moduleDefinition.CreatedBy;
_createdon = moduleDefinition.CreatedOn;
_modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn;
if (!string.IsNullOrEmpty(_packagename))
{
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
foreach (var package in _packages)
{
var code = package.PackageId.Split('.').Last();
if (!_languages.Any(item => item.Code == code))
{
_languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true });
}
}
_languages = _languages.OrderBy(item => item.Name).ToList();
}
_initialized = true;
}
}
catch (Exception ex)
@ -156,23 +292,32 @@
{
try
{
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduledefinition.Name != _name)
var moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
if (!moduleDefinitions.Any(item => item.Name.ToLower() == _name.ToLower() && item.ModuleDefinitionId != _moduleDefinitionId))
{
moduledefinition.Name = _name;
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduledefinition.Name != _name)
{
moduledefinition.Name = _name;
}
if (moduledefinition.Description != _description)
{
moduledefinition.Description = _description;
}
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
moduledefinition.PermissionList = _permissionGrid.GetPermissionList();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
}
if (moduledefinition.Description != _description)
else
{
moduledefinition.Description = _description;
AddModuleMessage(Localizer["Message.DuplicateName"], MessageType.Warning);
}
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.Permissions = _permissionGrid.GetPermissions();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
@ -185,4 +330,73 @@
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void HideModal()
{
_package = null;
StateHasChanged();
}
private string TranslationAvailable(string packagename, string version)
{
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
if (string.IsNullOrEmpty(version))
{
return "install";
}
else
{
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
return "upgrade";
}
}
}
}
return "";
}
private async Task GetPackage(string packagename)
{
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
try
{
_package = await PackageService.GetPackageAsync(packagename, version);
if (_package != null)
{
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", packagename, version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packagename, version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
AddModuleMessage(string.Format(Localizer["Success.Translation.Download"], NavigateUrl("admin/system")), MessageType.Success);
_package = null;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
}

View File

@ -12,32 +12,65 @@
}
else
{
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
<Pager Items="@_moduleDefinitions">
<div class="container">
<div class="row mb-3 align-items-center">
<div class="col-sm-6">
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
</div>
<div class="col-sm-6">
<select class="form-select" @onchange="(e => CategoryChanged(e))">
@foreach (var category in _categories)
{
if (category == _category)
{
<option value="@category" selected>@category @Localizer["Modules"]</option>
}
else
{
<option value="@category">@category @Localizer["Modules"]</option>
}
}
</select>
</div>
</div>
</div>
<Pager Items="@_moduleDefinitions.Where(item => item.Categories.Contains(_category))">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@Localizer["InUse"]</th>
<th>@SharedLocalizer["Support"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
<td>
@if (context.AssemblyName != "Oqtane.Client")
{
@if (context.AssemblyName != Constants.ClientId)
{
<ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete", context.Name])" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" />
}
}
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if(context.AssemblyName == "Oqtane.Client" || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
{
<span>@SharedLocalizer["Yes"]</span>
}
@ -46,6 +79,9 @@ else
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@((MarkupString)SupportLink(context.PackageName))
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>
@ -65,6 +101,8 @@ else
@code {
private List<ModuleDefinition> _moduleDefinitions;
private List<Package> _packages;
private List<string> _categories = new List<string>();
private string _category = "Common";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -74,6 +112,7 @@ else
{
_moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module");
_categories = _moduleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
catch (Exception ex)
{
@ -95,7 +134,7 @@ else
{
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span><br />";
if (!string.IsNullOrEmpty(package.PaymentUrl))
{
link += "&nbsp;&nbsp;<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + SharedLocalizer["Extend"] + "</a>";
@ -106,6 +145,20 @@ else
return link;
}
private string SupportLink(string packagename)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{
link += "<a class=\"btn btn-success\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
}
}
return link;
}
private string UpgradeAvailable(string packagename, string version)
{
if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -115,38 +168,43 @@ else
{
return package.Version;
}
}
return version;
}
}
return version;
}
private async Task DownloadModule(string packagename, string version)
{
try
{
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadModule(string packagename, string version)
{
try
{
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DeleteModule(ModuleDefinition moduleDefinition)
{
try
{
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message);
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
}
}
private async Task DeleteModule(ModuleDefinition moduleDefinition)
{
try
{
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message);
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
}
}
private void CategoryChanged(ChangeEventArgs e)
{
_category = (string)e.Value;
StateHasChanged();
}
}

View File

@ -19,7 +19,7 @@
@code {
private string _content = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Export Content";
@ -27,7 +27,7 @@
{
try
{
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId);
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId);
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
}
catch (Exception ex)

View File

@ -25,7 +25,7 @@
private ElementReference form;
private bool validated = false;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Import Content";
private async Task ImportModule()
@ -38,7 +38,7 @@
{
try
{
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content);
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, _content);
if (success)
{
AddModuleMessage(Localizer["Success.Content.Import"], MessageType.Success);

View File

@ -14,10 +14,16 @@
@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 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" name="Title" class="form-control" @bind="@_title" required />
<input id="title" type="text" class="form-control" @bind="@_title" required />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -44,12 +50,20 @@
<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>
@foreach (Page p in PageState.Pages)
@if (PageState.Page.UserId != null)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
}
else
{
foreach (Page p in PageState.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>
</div>
@ -62,7 +76,7 @@
{
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" Permissions="@_permissions" @ref="_permissionGrid" />
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" PermissionList="@_permissions" @ref="_permissionGrid" />
</div>
</div>
@ -95,13 +109,13 @@
private ElementReference form;
private bool validated = false;
private List<Theme> _themes;
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _module;
private string _title;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private string _permissions = null;
private List<Permission> _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
@ -116,90 +130,99 @@
private string modifiedby;
private DateTime modifiedon;
protected override async Task OnInitializedAsync()
protected override void OnInitialized()
{
_module = ModuleState.ModuleDefinition.Name;
_title = ModuleState.Title;
_themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.Permissions;
_permissionNames = ModuleState.ModuleDefinition.PermissionNames;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.PermissionList;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
}
else
{
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
}
if (_moduleSettingsType != null)
{
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
if (!string.IsNullOrEmpty(moduleobject.Title))
{
_moduleSettingsTitle = moduleobject.Title;
}
if (ModuleState.ModuleDefinition != null)
{
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
ModuleSettingsComponent = builder =>
{
builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent();
};
}
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
}
else
{
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
}
if (_moduleSettingsType != null)
{
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
if (!string.IsNullOrEmpty(moduleobject.Title))
{
_moduleSettingsTitle = moduleobject.Title;
}
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);
if (_containerSettingsType != null)
{
ContainerSettingsComponent = builder =>
{
builder.OpenComponent(0, _containerSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
builder.CloseComponent();
};
}
}
}
ModuleSettingsComponent = builder =>
{
builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent();
};
}
}
else
{
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
}
private async Task SaveModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (!string.IsNullOrEmpty(_title))
{
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);
if (_containerSettingsType != null)
{
ContainerSettingsComponent = builder =>
{
builder.OpenComponent(0, _containerSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
builder.CloseComponent();
};
}
}
}
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
private async Task SaveModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (!string.IsNullOrEmpty(_title))
{
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.PageModuleId = ModuleState.PageModuleId;
module.PermissionList = _permissionGrid.GetPermissionList();
await ModuleService.UpdateModuleAsync(module);
if (_moduleSettingsType != null)
{

View File

@ -6,12 +6,11 @@
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null)
{
@if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
@ -19,42 +18,67 @@
<input id="name" class="form-control" @bind="@_name" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pageList)
{
<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="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in PageState.Pages)
{
<option value="@(page.PageId)">@(page.Name)</option>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
}
</select>
}
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
{
<option value="@(page.PageId)">@(page.Name)</option>
}
</select>
}
</div>
</div>
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<option value="@(_parent.PageId)">@(new string('-', _parent.Level * 2))@(_parent.Name)</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value=">>">@Localizer["AtEnd"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<div class="col-sm-9">
@ -85,9 +109,24 @@
<input id="url" class="form-control" @bind="@_url" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<Section Name="Appearance" ResourceKey="Appearance">
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
@ -95,12 +134,6 @@
<input id="title" class="form-control" @bind="@_title" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="meta" HelpText="Optionally enter meta tags (in exactly the form you want them to be included in the page output)." ResourceKey="Meta">Meta: </Label>
<div class="col-sm-9">
<textarea id="meta" class="form-control" @bind="@_meta" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
@ -116,7 +149,6 @@
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
@ -124,92 +156,113 @@
</select>
</div>
</div>
</div>
</Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<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">
<input id="icon" class="form-control" @bind="@_icon" />
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
</div>
</div>
</div>
</Section>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
}
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pageList;
private string _name;
private string _title;
private string _meta;
private string _path = string.Empty;
private string _parentid = "-1";
private string _insert = ">>";
private List<Page> _children;
private int _childid = -1;
private string _isnavigation = "True";
private string _isclickable = "True";
private string _url;
private string _ispersonalizable = "False";
private string _themetype = string.Empty;
private string _containertype = string.Empty;
private string _icon = string.Empty;
private string _permissions = string.Empty;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private int _pageId;
private string _name;
private string _parentid = "-1";
private string _insert = ">>";
private List<Page> _children;
private int _childid = -1;
private string _isnavigation = "True";
private string _isclickable = "True";
private string _path = string.Empty;
private string _url;
private string _ispersonalizable = "False";
private string _title;
private string _icon = string.Empty;
private string _themetype = string.Empty;
private string _containertype = string.Empty;
private string _headcontent;
private string _bodycontent;
private string _permissions = null;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page _parent = null;
protected override async Task OnInitializedAsync()
{
try
{
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = PageState.Site.DefaultThemeType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_pageList = PageState.Pages;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_permissions = string.Empty;
ThemeSettings();
}
protected override async Task OnInitializedAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id"))
{
_pageId = Int32.Parse(PageState.QueryString["id"]);
_parent = await PageService.GetPageAsync(_pageId);
if (_parent != null)
{
_parentid = _parent.PageId.ToString();
}
}
// if admin or user has edit access to parent page
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
{
_themetype = PageState.Site.DefaultThemeType;
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
ThemeSettings();
_initialized = true;
}
else
{
await logger.LogWarning("Error Loading Page {ParentId}", _parentid);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error);
await logger.LogError(ex, "Error Loading Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
@ -223,7 +276,7 @@
{
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
@ -233,7 +286,7 @@
{
foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid)))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
@ -248,27 +301,23 @@
}
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = "-";
ThemeSettings();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error);
}
}
private void ThemeChanged(ChangeEventArgs e)
{
_themetype = (string)e.Value;
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _containers.First().TypeName;
ThemeSettings();
StateHasChanged();
}
}
private void ThemeSettings()
{
_themeSettingsType = null;
var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
@ -294,12 +343,11 @@
Page page = null;
try
{
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
{
page = new Page();
page.SiteId = PageState.Page.SiteId;
page.Name = _name;
page.Title = _title;
if (string.IsNullOrEmpty(_path))
{
@ -307,6 +355,10 @@
}
if (_path.Contains("/"))
{
if (_path.EndsWith("/") && _path != "/")
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
@ -329,15 +381,16 @@
}
}
if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (_pages.Any(item => item.Path == page.Path))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning);
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
return;
}
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList))
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
return;
}
@ -362,34 +415,42 @@
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
page.Url = _url;
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
// appearance
page.Title = _title;
page.Icon = (_icon == null ? string.Empty : _icon);
page.ThemeType = _themetype;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
page.DefaultContainerType = _containertype;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = (_icon == null ? string.Empty : _icon);
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
page.Meta = _meta;
// page content
page.HeadContent = _headcontent;
page.BodyContent = _bodycontent;
// permissions
page.PermissionList = _permissionGrid.GetPermissionList();
page = await PageService.AddPageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
await logger.LogInformation("Page Added {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
NavigationManager.NavigateTo(page.Path); // redirect to new page
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
}
}
else
@ -412,23 +473,13 @@
private void Cancel()
{
if (PageState.QueryString.ContainsKey("cp"))
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
else
{
NavigationManager.NavigateTo(NavigateUrl());
}
}
private static bool PagePathIsUnique(string pagePath, int siteId, List<Page> existingPages)
{
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath);
}
private static bool PagePathIsDeleted(string pagePath, int siteId, List<Page> existingPages)
{
return existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.IsDeleted == true);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.Pages != null)
@if (PageState.Pages != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />

View File

@ -64,6 +64,12 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="validation" HelpText="Optionally provide a regular expression (RegExp) for validating the value entered" ResourceKey="Validation">Validation: </Label>
<div class="col-sm-9">
<input id="validation" class="form-control" @bind="@_validation" maxlength="200" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
<div class="col-sm-9">
@ -97,6 +103,7 @@
private string _maxlength = "0";
private string _defaultvalue = string.Empty;
private string _options = string.Empty;
private string _validation = string.Empty;
private string _isrequired = "False";
private string _isprivate = "False";
private string createdby;
@ -104,7 +111,7 @@
private string modifiedby;
private DateTime modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit";
@ -126,6 +133,7 @@
_maxlength = profile.MaxLength.ToString();
_defaultvalue = profile.DefaultValue;
_options = profile.Options;
_validation = profile.Validation;
_isrequired = profile.IsRequired.ToString();
_isprivate = profile.IsPrivate.ToString();
createdby = profile.CreatedBy;
@ -169,6 +177,7 @@
profile.MaxLength = int.Parse(_maxlength);
profile.DefaultValue = _defaultvalue;
profile.Options = _options;
profile.Validation = _validation;
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
if (_profileid != -1)

View File

@ -10,7 +10,7 @@
}
else
{
<ActionLink Action="Add" Security="SecurityAccessLevel.Admin" Text="Add Profile" ResourceKey="AddProfile" />
<ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" />
<Pager Items="@_profiles">
<Header>
@ -19,8 +19,8 @@ else
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" ResourceKey="EditProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
<td>@context.Name</td>
</Row>
</Pager>
@ -29,7 +29,7 @@ else
@code {
private List<Profile> _profiles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync()
{

View File

@ -0,0 +1,20 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Profiles
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Profiles",
Description = "Manage Profiles",
Categories = "Admin",
Version = Constants.Version,
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
$"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}"
};
}
}

View File

@ -7,76 +7,79 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Pages" ResourceKey="Pages">
@if (_pages == null)
{
<br />
<p>@Localizer["NoPage.Deleted"]</p>
}
else
{
<Pager Items="@_pages">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["DeletedBy"]</th>
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td>@context.Name</td>
<td>@context.DeletedBy</td>
<td>@context.DeletedOn</td>
</Row>
</Pager>
@if (_pages.Any())
{
<br /><ActionDialog Header="Delete All Pages" Message="Are You Sure You Wish To Permanently Delete All Pages?" Action="Delete All Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
}
}
</TabPanel>
<TabPanel Name="Modules" ResourceKey="Modules">
@if (_modules == null)
{
<br />
<p>@Localizer["NoModule.Deleted"]</p>
}
else
{
<Pager Items="@_modules">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Page"]</th>
<th>@Localizer["Module"]</th>
<th>@Localizer["DeletedBy"]</th>
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td>
<td>@context.Title</td>
<td>@context.DeletedBy</td>
<td>@context.DeletedOn</td>
</Row>
</Pager>
@if (_modules.Any())
{
<br /><ActionDialog Header="Delete All Modules" Message="Are You Sure You Wish To Permanently Delete All Modules?" Action="Delete All Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
}
}
</TabPanel>
</TabStrip>
@if (_pages == null || _modules == null)
{
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<TabStrip>
<TabPanel Name="Pages" ResourceKey="Pages">
@if (!_pages.Where(item => item.IsDeleted).Any())
{
<br />
<p>@Localizer["NoPage.Deleted"]</p>
}
else
{
<Pager Items="@_pages.Where(item => item.IsDeleted)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["DeletedBy"]</th>
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td>@context.Name</td>
<td>@context.DeletedBy</td>
<td>@context.DeletedOn</td>
</Row>
</Pager>
<br />
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
}
</TabPanel>
<TabPanel Name="Modules" ResourceKey="Modules">
@if (!_modules.Where(item => item.IsDeleted).Any())
{
<br />
<p>@Localizer["NoModule.Deleted"]</p>
}
else
{
<Pager Items="@_modules.Where(item => item.IsDeleted)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Page"]</th>
<th>@Localizer["Module"]</th>
<th>@Localizer["DeletedBy"]</th>
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
<td>@context.Title</td>
<td>@context.DeletedBy</td>
<td>@context.DeletedOn</td>
</Row>
</Pager>
<br />
<ActionDialog Header="Remove All Deleted Modules" Message="Are You Sure You Wish To Permanently Remove All Deleted Modules?" Action="Remove All Deleted Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
}
</TabPanel>
</TabStrip>
}
@code {
private List<Page> _pages;
private List<Module> _modules;
private List<Page> _pages;
private List<Module> _modules;
private int _pagePage = 1;
private int _pageModule = 1;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
@ -95,10 +98,7 @@
private async Task Load()
{
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
_pages = _pages.Where(item => item.IsDeleted).ToList();
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
_modules = _modules.Where(item => item.IsDeleted).ToList();
}
private async Task RestorePage(Page page)
@ -140,7 +140,8 @@
{
try
{
foreach (Page page in _pages)
ModuleInstance.ShowProgressIndicator();
foreach (Page page in _pages.Where(item => item.IsDeleted))
{
await PageService.DeletePageAsync(page.PageId);
await logger.LogInformation("Page Permanently Deleted {Page}", page);
@ -148,6 +149,7 @@
await logger.LogInformation("Pages Permanently Deleted");
await Load();
ModuleInstance.HideProgressIndicator();
StateHasChanged();
NavigationManager.NavigateTo(NavigateUrl());
}
@ -155,6 +157,7 @@
{
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
ModuleInstance.HideProgressIndicator();
}
}
@ -181,14 +184,6 @@
try
{
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// check if there are any remaining module instances in the site
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
await logger.LogInformation("Module Permanently Deleted {Module}", module);
await Load();
StateHasChanged();
@ -204,26 +199,29 @@
{
try
{
foreach (Module module in _modules)
ModuleInstance.ShowProgressIndicator();
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
{
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// check if there are any remaining module instances in the site
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
}
await logger.LogInformation("Modules Permanently Deleted");
await Load();
ModuleInstance.HideProgressIndicator();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
ModuleInstance.HideProgressIndicator();
}
}
private void OnPageChangePage(int page)
{
_pagePage = page;
}
private void OnPageChangeModule(int page)
{
_pageModule = page;
}
}

View File

@ -4,6 +4,7 @@
@inject IUserService UserService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject ISettingService SettingService
@if (PageState.Site.AllowRegistration)
{
@ -15,7 +16,7 @@
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized>
<ModuleMessage Message="@Localizer["Info.Registration.InvalidEmail"]" Type="MessageType.Info" />
<ModuleMessage Message="@_passwordconstruction" Type="MessageType.Info" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
@ -29,7 +30,7 @@
<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">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -38,7 +39,7 @@
<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">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -68,21 +69,54 @@ else
}
@code {
private string _username = string.Empty;
private ElementReference form;
private bool validated = false;
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 _username = string.Empty;
private ElementReference form;
private bool validated = false;
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;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
//Password construction
private string _minimumlength;
private string _uniquecharacters;
private bool _requiredigit;
private bool _requireupper;
private bool _requirelower;
private bool _requirepunctuation;
private string _passwordconstruction;
protected override void OnParametersSet()
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override async Task OnInitializedAsync()
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
_requiredigit = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireDigit", "true"));
_requireupper = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireUppercase", "true"));
_requirelower = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireLowercase", "true"));
_requirepunctuation = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true"));
// Replace the placeholders with the actual values of the variables
string digitRequirement = _requiredigit ? Localizer["Password.DigitRequirement"] + ", " : "";
string uppercaseRequirement = _requireupper ? Localizer["Password.UppercaseRequirement"] + ", " : "";
string lowercaseRequirement = _requirelower ? Localizer["Password.LowercaseRequirement"] + ", " : "";
string punctuationRequirement = _requirepunctuation ? Localizer["Password.PunctuationRequirement"] + ", " : "";
// Replace the placeholders with the actual values of the variables
string passwordValidationCriteriaTemplate = Localizer["Password.ValidationCriteria"];
_passwordconstruction = Localizer["Info.Registration.InvalidEmail"] + ". " + string.Format(passwordValidationCriteriaTemplate,
_minimumlength, _uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement);
}
protected override void OnParametersSet()
{
_togglepassword = SharedLocalizer["ShowPassword"];
}
private async Task Register()

View File

@ -18,7 +18,7 @@
<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">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -27,7 +27,7 @@
<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">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>

View File

@ -42,7 +42,7 @@
private string _description = string.Empty;
private string _isautoassigned = "False";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
private async Task SaveRole()
{

View File

@ -49,7 +49,7 @@
private string _modifiedby;
private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync()
{

View File

@ -10,7 +10,7 @@
}
else
{
<ActionLink Action="Add" Text="Add Role" ResourceKey="AddRole" />
<ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" />
<Pager Items="@_roles">
<Header>
@ -20,9 +20,9 @@ else
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" ResourceKey="Users" /></td>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
<td>@context.Name</td>
</Row>
</Pager>
@ -31,7 +31,7 @@ else
@code {
private List<Role> _roles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync()
{
@ -59,7 +59,7 @@ else
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
_roles = _roles.Where(item => item.Name != RoleNames.Everyone).ToList();
_roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
}
else
{

View File

@ -0,0 +1,21 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Roles
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Roles",
Description = "Manage Roles",
Categories = "Admin",
Version = Constants.Version,
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
$"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}," +
$"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}"
};
}
}

View File

@ -23,13 +23,7 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label>
<div class="col-sm-9">
<select id="user" class="form-select" @bind="@userid" required>
<option value="-1">&lt;@Localizer["User.Select"]&gt;</option>
@foreach (UserRole userrole in users)
{
<option value="@(userrole.UserId)">@userrole.User.DisplayName</option>
}
</select>
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["User.Select"]" @ref="user" />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -64,7 +58,7 @@ else
<td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td>
<td>
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || PageState.User.Username == UserNames.Host)" ResourceKey="DeleteUserRole" />
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
</td>
</Row>
</Pager>
@ -75,81 +69,96 @@ else
}
@code {
private ElementReference form;
private bool validated = false;
private ElementReference form;
private bool validated = false;
private int roleid;
private string name = string.Empty;
private List<UserRole> users;
private int userid = -1;
private DateTime? effectivedate = null;
private DateTime? expirydate = null;
private List<UserRole> userroles;
private int roleid;
private string name = string.Empty;
private AutoComplete user;
private DateTime? effectivedate = null;
private DateTime? expirydate = null;
private List<UserRole> userroles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync()
{
try
{
roleid = Int32.Parse(PageState.QueryString["id"]);
Role role = await RoleService.GetRoleAsync(roleid);
name = role.Name;
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
await GetUserRoles();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
}
protected override async Task OnInitializedAsync()
{
try
{
roleid = Int32.Parse(PageState.QueryString["id"]);
Role role = await RoleService.GetRoleAsync(roleid);
name = role.Name;
await GetUserRoles();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
}
private async Task GetUserRoles()
{
try
{
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Roles {RoleId} {Error}", roleid, ex.Message);
AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error);
}
}
private async Task<Dictionary<string, string>> GetUsers(string filter)
{
try
{
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase))
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Users {filter} {Error}", filter, ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
return new Dictionary<string, string>();
}
private async Task SaveUserRole()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (userid != -1)
{
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null)
{
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
}
else
{
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
private async Task GetUserRoles()
{
try
{
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Roles {RoleId} {Error}", roleid, ex.Message);
AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error);
}
}
await UserRoleService.AddUserRoleAsync(userrole);
}
private async Task SaveUserRole()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (!string.IsNullOrEmpty(user.Key) && int.TryParse(user.Key, out int userid))
{
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null)
{
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
}
else
{
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success);
await GetUserRoles();
StateHasChanged();
await UserRoleService.AddUserRoleAsync(userrole);
}
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success);
await GetUserRoles();
user.Clear();
StateHasChanged();
}
else
{

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@ else
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they can be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<Label Class="col-sm-3" For="alias" HelpText="The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
@ -89,7 +89,8 @@ else
<select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
</select>
<option value="Hybrid">@SharedLocalizer["BlazorHybrid"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
@ -102,7 +103,7 @@ else
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="Select the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
<div class="col-sm-9">
<select id="tenant" class="form-select" @onchange="(e => TenantChanged(e))" required>
<option value="-">&lt;@Localizer["Tenant.Select"]&gt;</option>
@ -120,33 +121,51 @@ 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 tenant" ResourceKey="TenantName">Tenant Name: </Label>
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the database" ResourceKey="TenantName">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type" ResourceKey="DatabaseType">Type: </Label>
<div class="col-sm-9">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
if (database.IsDefault)
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
}
</select>
</div>
@if (_databases != null)
{
<div class="input-group">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
@if (!_showConnectionString)
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
}
</div>
}
</div>
</div>
if (_databaseConfigType != null)
{
@DatabaseConfigComponent;
}
@if (!_showConnectionString)
{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent
}
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of an existing host user" ResourceKey="HostUsername">Host Username:</Label>
<div class="col-sm-9">
@ -172,11 +191,12 @@ else
private List<Database> _databases;
private ElementReference form;
private bool validated = false;
private string _databaseName = "LocalDB";
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
@ -208,7 +228,16 @@ else
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
{
_databaseName = _databases.Find(item => item.IsDefault).Name;
}
else
{
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
}
@ -217,7 +246,7 @@ else
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
@ -259,12 +288,13 @@ else
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
_containertype = _containers.First().TypeName;
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_containertype = "-";
}
_admincontainertype = "";
StateHasChanged();
}
@ -286,7 +316,7 @@ else
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
foreach (string name in _urls.Split(',', StringSplitOptions.RemoveEmptyEntries))
{
if (aliases.Exists(item => item.Name == name))
{
@ -300,7 +330,7 @@ else
if (_tenantid == "+")
{
if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null)
if (!string.IsNullOrEmpty(_tenantName) && !_tenants.Exists(item => item.Name == _tenantName))
{
// validate host credentials
var user = new User();
@ -308,15 +338,22 @@ else
user.Username = _hostusername;
user.Password = _hostpassword;
user.LastIPAddress = PageState.RemoteIPAddress;
user = await UserService.LoginUserAsync(user);
user = await UserService.LoginUserAsync(user, false, false);
if (user.IsAuthenticated)
{
var connectionString = String.Empty;
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var connectionString = String.Empty;
if (_showConnectionString)
{
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
}
if (connectionString != "")
{
@ -397,4 +434,13 @@ else
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void ToggleConnectionString()
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
_connectionString = databaseConfigControl.GetConnectionString();
}
_showConnectionString = !_showConnectionString;
}
}

View File

@ -18,7 +18,7 @@ else
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["AliasName"]</th>
</Header>
<Row>
<td><button type="button" class="btn btn-primary" @onclick="@(async () => Edit(context.Name))">@SharedLocalizer["Edit"]</button></td>

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Modules.Admin.Sql
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ISystemService SystemService
@inject ITenantService TenantService
@inject IDatabaseService DatabaseService
@inject ISqlService SqlService
@ -14,125 +15,307 @@
else
{
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connection" HelpText="Select a database connection (from appsettings.json)" ResourceKey="Connection">Connection: </Label>
<div class="col-sm-9">
<select id="tenant" class="form-select" value="@_connection" @onchange="(e => ConnectionChanged(e))">
<option value="-">&lt;@Localizer["Connection.Select"]&gt;</option>
<option value="+">&lt;@Localizer["Connection.Add"]&gt;</option>
@foreach (var connection in _connections)
{
<option value="@connection.Key">@connection.Key</option>
}
</select>
</div>
</div>
@if (_connection == "+")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the name of the connection" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databasetype" HelpText="Select the database type" ResourceKey="DatabaseType">Type: </Label>
<div class="col-sm-9">
@if (_databases != null)
{
<div class="input-group">
<select id="databasetype" class="form-select" value="@_databasetype" @onchange="(e => DatabaseTypeChanged(e))" required>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
@if (!_showConnectionString)
{
<button type="button" class="btn btn-secondary" @onclick="ShowConnectionString">@Localizer["EnterConnectionString"]</button>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="ShowConnectionString">@Localizer["EnterConnectionParameters"]</button>
}
</div>
}
</div>
</div>
@if (!_showConnectionString)
{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent
}
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="3"></textarea>
</div>
</div>
}
<br />
<button type="button" class="btn btn-success" @onclick="Add">@Localizer["Add"]</button>
}
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">
<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 />
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="The connection string" ResourceKey="ConnectionString">Settings: </Label>
<div class="col-sm-9">
<div class="input-group">
<input id="connectionstring" type="@_connectionstringtype" class="form-control" @bind="@_connectionstring" readonly />
<button type="button" class="btn btn-secondary" @onclick="@ToggleConnectionString">@_connectionstringtoggle</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sqlQuery" HelpText="Enter a valid SQL query for the database" ResourceKey="SqlQuery">SQL Query: </Label>
<div class="col-sm-9">
<textarea id="sqlQuery" class="form-control" @bind="@_sql" rows="3"></textarea>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="Execute">@Localizer["Execute"]</button>
<br />
<br />
@if (_results != null)
{
@if (_results.Count > 0)
{
<Pager Class="table table-bordered" Items="@_results">
<Header>
@foreach (KeyValuePair<string, string> kvp in _results.First())
{
<th>@kvp.Key</th>
}
</Header>
<Row>
@foreach (KeyValuePair<string, string> kvp in context)
{
<td>@kvp.Value</td>
}
</Row>
</Pager>
}
else
{
@Localizer["Return.NoResult"]
}
<br />
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="Select the tenant for the SQL server" ResourceKey="Tenant">Tenant: </Label>
<div class="col-sm-9">
<select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))">
<option value="-1">&lt;@Localizer["Tenant.Select"]&gt;</option>
@foreach (Tenant tenant in _tenants)
{
<option value="@tenant.TenantId">@tenant.Name</option>
}
</select>
</div>
</div>
@if (_tenantid != "-1")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="database" HelpText="The database for the tenant" ResourceKey="Database">Database: </Label>
<div class="col-sm-9">
<input id="database" class="form-control" @bind="@_database" readonly />
</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>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sqlQeury" HelpText="Enter the query for the SQL server" ResourceKey="SqlQuery">SQL Query: </Label>
<div class="col-sm-9">
<textarea id="sqlQeury" class="form-control" @bind="@_sql" rows="3"></textarea>
</div>
</div>
}
</div>
<br />
<button type="button" class="btn btn-success" @onclick="Execute">@Localizer["Execute"]</button>
<br />
<br />
@if (_results != null)
{
@if (_results.Count > 0)
{
<Pager Class="table table-bordered" Items="@_results">
<Header>
@foreach (KeyValuePair<string, string> kvp in _results.First())
{
<th>@kvp.Key</th>
}
</Header>
<Row>
@foreach (KeyValuePair<string, string> kvp in context)
{
<td>@kvp.Value</td>
}
</Row>
</Pager>
}
else
{
@Localizer["Return.NoResult"]
}
<br />
<br />
}
<br />
}
}
}
</div>
}
@code {
private List<Tenant> _tenants;
private string _tenantid = "-1";
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _sql = string.Empty;
private List<Dictionary<string, string>> _results;
private string _connection = "-";
private Dictionary<string, object> _connections;
private List<Tenant> _tenants;
private List<Database> _databases;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
private string _name = string.Empty;
private string _databasetype = string.Empty;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _tenant = string.Empty;
private string _connectionstring = string.Empty;
private string _connectionstringtype = "password";
private string _connectionstringtoggle = string.Empty;
private string _sql = string.Empty;
private List<Dictionary<string, string>> _results;
protected override async Task OnInitializedAsync()
{
try
{
_tenants = await TenantService.GetTenantsAsync();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
private async void TenantChanged(ChangeEventArgs e)
{
try
{
_tenantid = (string)e.Value;
var tenants = await TenantService.GetTenantsAsync();
var _databases = await DatabaseService.GetDatabasesAsync();
var tenant = tenants.Find(item => item.TenantId == int.Parse(_tenantid));
if (tenant != null)
{
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_connectionstring = tenant.DBConnectionString;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Tenant {TenantId} {Error}", _tenantid, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
protected override async Task OnInitializedAsync()
{
try
{
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
_tenants = await TenantService.GetTenantsAsync();
_databases = await DatabaseService.GetDatabasesAsync();
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async Task Execute()
{
try
{
if (_tenantid != "-1" && !string.IsNullOrEmpty(_sql))
{
var sqlquery = new SqlQuery { TenantId = int.Parse(_tenantid), Query = _sql };
private async void ConnectionChanged(ChangeEventArgs e)
{
try
{
_connection = (string)e.Value;
if (_connection != "-" && _connection != "+")
{
_connectionstring = _connections[_connection].ToString();
_tenant = "";
_databasetype = "-";
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
if (tenant != null)
{
_tenant = tenant.Name;
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType).Name;
}
}
else
{
if (_databases.Exists(item => item.IsDefault))
{
_databasetype = _databases.Find(item => item.IsDefault).Name;
}
else
{
_databasetype = "LocalDB";
}
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private void DatabaseTypeChanged(ChangeEventArgs eventArgs)
{
try
{
_databasetype = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
{
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
}
}
private void LoadDatabaseConfigComponent()
{
var database = _databases.SingleOrDefault(d => d.Name == _databasetype);
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 ShowConnectionString()
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
_connectionstring = databaseConfigControl.GetConnectionString();
}
_showConnectionString = !_showConnectionString;
}
private async Task Add()
{
var connectionstring = _connectionstring;
if (!_showConnectionString && _databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionstring = databaseConfigControl.GetConnectionString();
}
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
{
var settings = new Dictionary<string, object>();
settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring);
await SystemService.UpdateSystemInfoAsync(settings);
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
_connection = "-";
AddModuleMessage(Localizer["Message.Connection.Added"], MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Required.Connection"], MessageType.Warning);
}
}
private void ToggleConnectionString()
{
if (_connectionstringtype == "password")
{
_connectionstringtype = "text";
_connectionstringtoggle = SharedLocalizer["HidePassword"];
}
else
{
_connectionstringtype = "password";
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
}
}
private async Task Execute()
{
try
{
if (_databasetype != "-" && !string.IsNullOrEmpty(_sql))
{
var dbtype = _databases.FirstOrDefault(item => item.Name == _databasetype).DBType;
var sqlquery = new SqlQuery { DBConnectionString = _connection, DBType = dbtype, Query = _sql };
sqlquery = await SqlService.ExecuteQueryAsync(sqlquery);
_results = sqlquery.Results;
AddModuleMessage(Localizer["Success.QueryExecuted"], MessageType.Success);

View File

@ -38,7 +38,13 @@
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
<div class="col-sm-9">
<input id="environment" class="form-control" @bind="@_environment" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
<div class="col-sm-9">
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
@ -141,7 +147,20 @@
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>&nbsp;
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
</TabPanel>
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
<div class="col-sm-9">
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
</div>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
</TabPanel>
</TabStrip>
<br /><br />
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -151,6 +170,7 @@
private string _osversion = string.Empty;
private string _machinename = string.Empty;
private string _ipaddress = string.Empty;
private string _environment = string.Empty;
private string _contentrootpath = string.Empty;
private string _webrootpath = string.Empty;
private string _servertime = string.Empty;
@ -164,17 +184,20 @@
private string _swagger = string.Empty;
private string _packageservice = string.Empty;
private string _log = string.Empty;
protected override async Task OnInitializedAsync()
{
_version = Constants.Version;
Dictionary<string, object> systeminfo = await SystemService.GetSystemInfoAsync("environment");
var systeminfo = await SystemService.GetSystemInfoAsync("environment");
if (systeminfo != null)
{
_clrversion = systeminfo["CLRVersion"].ToString();
_osversion = systeminfo["OSVersion"].ToString();
_machinename = systeminfo["MachineName"].ToString();
_ipaddress = systeminfo["IPAddress"].ToString();
_environment = systeminfo["Environment"].ToString();
_contentrootpath = systeminfo["ContentRootPath"].ToString();
_webrootpath = systeminfo["WebRootPath"].ToString();
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
@ -182,37 +205,60 @@
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
}
systeminfo = await SystemService.GetSystemInfoAsync();
systeminfo = await SystemService.GetSystemInfoAsync("configuration");
if (systeminfo != null)
{
_installationid = systeminfo["InstallationId"].ToString();
_detailederrors = systeminfo["DetailedErrors"].ToString();
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
_swagger = systeminfo["UseSwagger"].ToString();
_packageservice = systeminfo["PackageService"].ToString();
_detailederrors = systeminfo["DetailedErrors"].ToString();
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
_swagger = systeminfo["UseSwagger"].ToString();
_packageservice = systeminfo["PackageService"].ToString();
}
systeminfo = await SystemService.GetSystemInfoAsync("log");
if (systeminfo != null)
{
_log = systeminfo["Log"].ToString();
}
}
private async Task SaveConfig()
{
try
{
var settings = new Dictionary<string, object>();
settings.Add("DetailedErrors", _detailederrors);
settings.Add("Logging:LogLevel:Default", _logginglevel);
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
settings.Add("UseSwagger", _swagger);
settings.Add("PackageService", _packageservice);
await SystemService.UpdateSystemInfoAsync(settings);
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Configuration");
AddModuleMessage(Localizer["Error.UpdateConfig"], MessageType.Error);
}
}
private async Task SaveConfig()
{
try
{
var settings = new Dictionary<string, object>();
settings.Add("DetailedErrors", _detailederrors);
settings.Add("Logging:LogLevel:Default", _logginglevel);
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
settings.Add("UseSwagger", _swagger);
settings.Add("PackageService", _packageservice);
await SystemService.UpdateSystemInfoAsync(settings);
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Configuration");
AddModuleMessage(Localizer["Error.UpdateConfig"], MessageType.Error);
}
}
private async Task ClearLog()
{
try
{
var settings = new Dictionary<string, object>();
settings.Add("clearlog", "true");
await SystemService.UpdateSystemInfoAsync(settings);
_log = string.Empty;
AddModuleMessage(Localizer["Success.ClearLog"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Clearing Log");
AddModuleMessage(Localizer["Error.ClearLog"], MessageType.Error);
}
}
private async Task RestartApplication()
{

View File

@ -35,6 +35,7 @@
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@ -64,13 +65,15 @@
</div>
}
}
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation." ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>
</div>
</div>
@ -110,7 +113,6 @@
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
@ -215,8 +217,13 @@
}
_packageid = package.PackageId;
_version = package.Version;
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
}
StateHasChanged();
}
catch (Exception ex)
{
@ -231,7 +238,7 @@
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Theme.Download"], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
_productname = "";
_license = "";
StateHasChanged();
@ -243,16 +250,8 @@
}
}
private async Task InstallThemes()
private void OnUpload()
{
try
{
await ThemeService.InstallThemesAsync();
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Theme");
}
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -121,7 +121,7 @@
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private void TemplateChanged(ChangeEventArgs e)
@ -142,7 +142,7 @@
if (_owner != "" && _theme != "" && _template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _theme;
_location = template.Location + _owner + ".Theme." + _theme;
}
StateHasChanged();

View File

@ -0,0 +1,162 @@
@namespace Oqtane.Modules.Admin.Themes
@using System.Net
@inherits ModuleBase
@inject IThemeService ThemeService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="themename" class="form-control" @bind="@_themeName" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div>
</div>
</div>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
}
@code {
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private int _themeId;
private string _themeName = "";
private string _isenabled;
private string _name;
private string _version;
private string _packagename;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeId = Int32.Parse(PageState.QueryString["id"]);
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
if (theme != null)
{
_name = theme.Name;
_isenabled =theme.IsEnabled.ToString();
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
_createdby = theme.CreatedBy;
_createdon = theme.CreatedOn;
_modifiedby = theme.ModifiedBy;
_modifiedon = theme.ModifiedOn;
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
}
}
private async Task SaveTheme()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
theme.Name = _name;
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
await ThemeService.UpdateThemeAsync(theme);
await logger.LogInformation("Theme Saved {Theme}", theme);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Theme {ThemeId} {Error}", _themeId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@ -23,19 +23,34 @@ else
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@SharedLocalizer["Support"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th>&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditModule" /></td>
<td>
@if (context.AssemblyName != "Oqtane.Client")
@if (context.AssemblyName != Constants.ClientId)
{
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
}
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@((MarkupString)SupportLink(context.PackageName))
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>
@ -86,7 +101,7 @@ else
{
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span><br />";
if (!string.IsNullOrEmpty(package.PaymentUrl))
{
link += "&nbsp;&nbsp;<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + SharedLocalizer["Extend"] + "</a>";
@ -97,6 +112,20 @@ else
return link;
}
private string SupportLink(string packagename)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{
link += "<a class=\"btn btn-success\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
}
}
return link;
}
private string UpgradeAvailable(string packagename, string version)
{
if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -116,7 +145,6 @@ else
{
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version);
await ThemeService.InstallThemesAsync();
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)

View File

@ -1,89 +0,0 @@
@namespace Oqtane.Modules.Admin.Themes
@using System.Net
@inherits ModuleBase
@inject IThemeService ThemeService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<View> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="themename" class="form-control" @bind="@_themeName" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div>
</div>
</div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _themeName = "";
private string _name;
private string _version;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeName = WebUtility.UrlDecode(PageState.QueryString["name"]);
var themes = await ThemeService.GetThemesAsync();
var theme = themes.FirstOrDefault(item => item.ThemeName == _themeName);
if (theme != null)
{
_name = theme.Name;
_version = theme.Version;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
}
}
}

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.UserProfile
@using System.Text.RegularExpressions;
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -34,7 +35,7 @@ else
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -43,7 +44,7 @@ else
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -143,6 +144,11 @@ else
<TabPanel Name="Notifications" ResourceKey="Notifications">
@if (notifications != null)
{
<select class="form-select" @onchange="(e => FilterChanged(e))">
<option value="to">@Localizer["Inbox"]</option>
<option value="from">@Localizer["Items.Sent"]</option>
</select>
<br />
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" />
<br /><br />
@if (filter == "to")
@ -158,22 +164,41 @@ else
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.FromDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
@if (context.IsRead)
{
<td>@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>@context.Subject</b></td>
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
}
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
} }
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
}
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
}
@if (context.IsRead)
{
@notificationSummary
}
else
{
<b>@notificationSummary</b>
}
</td>
</Detail>
</Pager>
@ -191,33 +216,51 @@ else
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.ToDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
@if (context.IsRead)
{
<td>@context.ToDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
}
else
{
<td><b>@context.ToDisplayName</b></td>
<td><b>@context.Subject</b></td>
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
}
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
} }
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
}
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
}
@if (context.IsRead)
{
@notificationSummary
}
else
{
<b>@notificationSummary</b>
}
</td>
</Detail>
</Pager>
}
<br />
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
<br /><hr />
<select class="form-select" @onchange="(e => FilterChanged(e))">
<option value="to">@Localizer["Inbox"]</option>
<option value="from">@Localizer["Items.Sent"]</option>
</select>
@if (notifications.Any())
{
<br />
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
}
}
</TabPanel>
</TabStrip>
@ -226,8 +269,8 @@ else
@code {
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private bool allowtwofactor = false;
private string twofactor = "False";
@ -242,6 +285,7 @@ else
private string category = string.Empty;
private string filter = "to";
private List<Notification> notifications;
private string notificationSummary = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
@ -305,7 +349,14 @@ else
}
private string GetProfileValue(string SettingName, string DefaultValue)
=> SettingService.GetSetting(settings, SettingName, DefaultValue);
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task Save()
{
@ -337,12 +388,19 @@ else
photo = null;
}
await UserService.UpdateUserAsync(user);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged();
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged();
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
}
else
{
@ -353,6 +411,8 @@ else
{
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
}
await ScrollToPageTop();
}
catch (Exception ex)
{
@ -372,10 +432,15 @@ else
}
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{
valid = false;
}
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
}
}
}
return valid;
@ -429,6 +494,7 @@ else
{
try
{
ModuleInstance.ShowProgressIndicator();
foreach(var Notification in notifications)
{
if (!Notification.IsDeleted)
@ -444,12 +510,15 @@ else
}
await logger.LogInformation("Notifications Permanently Deleted");
await LoadNotificationsAsync();
ModuleInstance.HideProgressIndicator();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
ModuleInstance.HideProgressIndicator();
}
}

View File

@ -118,6 +118,9 @@
Notification notification = await NotificationService.GetNotificationAsync(notificationid);
if (notification != null)
{
notification.IsRead = true;
notification = await NotificationService.UpdateNotificationAsync(notification);
int userid = -1;
if (notification.ToUserId == PageState.User.UserId)
{

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Users
@using System.Text.RegularExpressions;
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -23,7 +24,7 @@
<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">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -32,7 +33,7 @@
<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">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -95,8 +96,8 @@
@code {
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = 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;
@ -104,7 +105,7 @@
private Dictionary<string, string> settings;
private string category = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync()
{
@ -122,7 +123,14 @@
}
private string GetProfileValue(string SettingName, string DefaultValue)
=> SettingService.GetSetting(settings, SettingName, DefaultValue);
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser()
{
@ -188,9 +196,17 @@
{
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
}
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
valid = false;
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{
valid = false;
}
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
}
}
}
return valid;

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Users
@using System.Text.RegularExpressions;
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -32,7 +33,7 @@ else
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -41,7 +42,7 @@ else
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -148,64 +149,64 @@ else
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
@code {
private int userid;
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int photofileid = -1;
private File photo = null;
private string isdeleted;
private string lastlogin;
private string lastipaddress;
private int userid;
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int photofileid = -1;
private File photo = null;
private string isdeleted;
private string lastlogin;
private string lastipaddress;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private string deletedby;
private DateTime? deletedon;
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private string deletedby;
private DateTime? deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnParametersSetAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id"))
{
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
userid = Int32.Parse(PageState.QueryString["id"]);
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null)
{
username = user.Username;
email = user.Email;
displayname = user.DisplayName;
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
protected override async Task OnParametersSetAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id"))
{
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
userid = Int32.Parse(PageState.QueryString["id"]);
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null)
{
username = user.Username;
email = user.Email;
displayname = user.DisplayName;
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
isdeleted = user.IsDeleted.ToString();
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId);
settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
@ -223,7 +224,14 @@ else
}
private string GetProfileValue(string SettingName, string DefaultValue)
=> SettingService.GetSetting(settings, SettingName, DefaultValue);
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser()
{
@ -249,10 +257,16 @@ else
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
user = await UserService.UpdateUserAsync(user);
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
}
else
{
@ -280,9 +294,17 @@ else
{
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
}
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
valid = false;
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{
valid = false;
}
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
}
}
}
return valid;

View File

@ -6,7 +6,6 @@
@inject ISiteService SiteService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject SiteState SiteState
@if (users == null)
{
@ -21,7 +20,7 @@ else
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" />
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
</div>
<div class="col-sm-4">
<input class="form-control" @bind="@_search" />
@ -36,27 +35,27 @@ else
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Username"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["LastLoginOn"]</th>
<th class="app-sort-th" @onclick="@(() => SortTable("Username"))">@Localizer["Username"]<i class="@(SetSortIcon("Username"))"></i></th>
<th class="app-sort-th" @onclick="@(() => SortTable("DisplayName"))">@Localizer["Name"]<i class="@(SetSortIcon("DisplayName"))"></i></th>
<th class="app-sort-th" @onclick="@(() => SortTable("LastLoginOn"))">@Localizer["LastLoginOn"]<i class="@(SetSortIcon("LastLoginOn"))"></i></th>
</Header>
<Row>
<td>
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" />
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
</td>
<td>
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
</td>
<td>
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
</td>
<td>@context.User.Username</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn)</td>
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td>
</Row>
</Pager>
</TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
</TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
<div class="container">
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
<div class="row mb-1 align-items-center">
@ -287,6 +286,21 @@ else
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
</div>
</div>
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
{
<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>
<div class="col-sm-9">
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
</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" />
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
<div class="col-sm-9">
@ -386,6 +400,8 @@ else
private string _redirecturl;
private string _identifierclaimtype;
private string _emailclaimtype;
private string _roleclaimtype;
private string _profileclaimtypes;
private string _domainfilter;
private string _createusers;
@ -397,7 +413,10 @@ else
private string _lifetime;
private string _token;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private bool isSortedAscending;
private string activeSortColumn;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnInitializedAsync()
{
@ -437,8 +456,10 @@ else
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_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");
@ -446,7 +467,8 @@ else
_togglesecret = SharedLocalizer["ShowPassword"];
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20"); }
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
}
}
private async Task LoadUsersAsync(bool load)
@ -512,7 +534,7 @@ else
private async Task UpdateUserSettingsAsync()
{
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
SettingService.SetSetting(settings, settingSearch, _search);
settings = SettingService.SetSetting(settings, settingSearch, _search);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
}
@ -556,6 +578,8 @@ else
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, 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);
@ -591,14 +615,10 @@ else
if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
{
_scopes = "openid,profile,email";
_identifierclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
_emailclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
}
else
{
_scopes = "";
_identifierclaimtype = "sub";
_emailclaimtype = "email";
}
}
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
@ -637,4 +657,43 @@ else
_togglesecret = SharedLocalizer["ShowPassword"];
}
}
private void SortTable(string columnName)
{
if (columnName != activeSortColumn)
{
users = users.OrderBy(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
isSortedAscending = true;
activeSortColumn = columnName;
}
else
{
if (isSortedAscending)
{
users = users.OrderByDescending(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
}
else
{
users = users.OrderBy(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
}
isSortedAscending = !isSortedAscending;
}
}
private string SetSortIcon(string columnName)
{
if (activeSortColumn != columnName)
{
return "app-fas pe-3 ";
}
if (isSortedAscending)
{
return "app-fas oi oi-sort-ascending";
}
else
{
return "app-fas oi oi-sort-descending";
}
}
}

View File

@ -0,0 +1,21 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Users
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Users",
Description = "Manage Users",
Categories = "Admin",
Version = Constants.Version,
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
$"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}," +
$"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}"
};
}
}

View File

@ -34,13 +34,13 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input id="effectiveDate" class="form-control" @bind="@effectivedate" />
<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 role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input id="expiryDate" class="form-control" @bind="@expirydate" />
<input type="date" id="expiryDate" class="form-control" @bind="@expirydate" />
</div>
</div>
@ -63,7 +63,7 @@ else
<td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td>
<td>
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
</td>
</Row>
</Pager>
@ -75,11 +75,11 @@ else
private string name = string.Empty;
private List<Role> roles;
private int roleid = -1;
private string effectivedate = string.Empty;
private string expirydate = string.Empty;
private DateTime? effectivedate = null;
private DateTime? expirydate = null;
private List<UserRole> userroles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync()
{
@ -88,15 +88,17 @@ else
userid = Int32.Parse(PageState.QueryString["id"]);
User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
name = user.DisplayName;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
roles = roles.Where(item => item.Name != RoleNames.Everyone).ToList();
roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
}
else
{
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
}
await GetUserRoles();
}
catch (Exception ex)
@ -128,23 +130,8 @@ else
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null)
{
if (string.IsNullOrEmpty(effectivedate))
{
userrole.EffectiveDate = null;
}
else
{
userrole.EffectiveDate = DateTime.Parse(effectivedate);
}
if (string.IsNullOrEmpty(expirydate))
{
userrole.ExpiryDate = null;
}
else
{
userrole.ExpiryDate = DateTime.Parse(expirydate);
}
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
}
else
@ -152,25 +139,8 @@ else
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
if (string.IsNullOrEmpty(effectivedate))
{
userrole.EffectiveDate = null;
}
else
{
userrole.EffectiveDate = DateTime.Parse(effectivedate);
}
if (string.IsNullOrEmpty(expirydate))
{
userrole.ExpiryDate = null;
}
else
{
userrole.ExpiryDate = DateTime.Parse(expirydate);
}
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.AddUserRoleAsync(userrole);
}

View File

@ -128,13 +128,6 @@
private string CloseUrl()
{
if (!PageState.QueryString.ContainsKey("type"))
{
return NavigateUrl();
}
else
{
return NavigateUrl(PageState.Page.Path, "type=" + PageState.QueryString["type"] + "&days=" + PageState.QueryString["days"] + "&page=" + PageState.QueryString["page"]);
}
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
}
}

View File

@ -43,7 +43,7 @@ else
<th>@Localizer["Created"]</th>
</Header>
<Row>
<td><ActionLink Action="Detail" Parameters="@($"id=" + context.VisitorId.ToString() + "&type=" + _type.ToString() + "&days=" + _days.ToString() + "&page=" + _page.ToString())" ResourceKey="Details" /></td>
<td><ActionLink Action="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
<td>@context.IPAddress</td>
<td>
@if (context.UserId != null)

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Controls
@using System.Text.Json
@inherits LocalizableComponent
@if (_visible)
@ -40,7 +41,7 @@
@code {
private bool _visible = false;
private string _permissions = string.Empty;
private List<Permission> _permissions;
private bool _editmode = false;
private bool _authorized = false;
private string _iconSpan = string.Empty;
@ -61,7 +62,10 @@
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public string Permissions { get; set; } // optional - can be used to specify a permission string
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
[Parameter]
public string Class { get; set; } // optional
@ -78,7 +82,15 @@
[Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
protected override void OnParametersSet()
protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
protected override void OnParametersSet()
{
base.OnParametersSet();
@ -109,7 +121,7 @@
Header = Localize(nameof(Header), Header);
Message = Localize(nameof(Message), Message);
_permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions;
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_authorized = IsAuthorized();
}

View File

@ -1,4 +1,6 @@
@namespace Oqtane.Modules.Controls
@using System.Net
@using System.Text.Json
@inherits LocalizableComponent
@inject IUserService UserService
@ -25,7 +27,7 @@
private string _text = string.Empty;
private string _parameters = string.Empty;
private string _url = string.Empty;
private string _permissions = string.Empty;
private List<Permission> _permissions;
private bool _editmode = false;
private bool _authorized = false;
private string _classname = "btn btn-primary";
@ -51,7 +53,10 @@
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public string Permissions { get; set; } // optional - can be used to specify a permission string
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
[Parameter]
public bool Disabled { get; set; } // optional
@ -71,6 +76,17 @@
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in link
[Parameter]
public string ReturnUrl { get; set; } // optional - used to set a url to redirect to
protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
protected override void OnParametersSet()
{
base.OnParametersSet();
@ -115,10 +131,14 @@
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
}
_permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions;
_text = Localize(nameof(Text), _text);
_url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters);
_authorized = IsAuthorized();
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_text = Localize(nameof(Text), _text);
_url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters);
if (!string.IsNullOrEmpty(ReturnUrl))
{
_url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}";
}
_authorized = IsAuthorized();
}
private bool IsAuthorized()

View File

@ -0,0 +1,153 @@
@namespace Oqtane.Modules.Controls
@inherits LocalizableComponent
<div class="app-autocomplete">
<input class="form-control" value="@Value" @oninput="OnInput" @onkeyup="OnKeyUp" placeholder="@Placeholder" autocomplete="off" />
@if (_results != null)
{
<select class="form-select" style="position: relative;" value="@Value" size="@Rows" @onkeyup="OnKeyUp" @onchange="(e => OnChange(e))">
@if (_results.Any())
{
@foreach (var result in _results)
{
if (result.Value == Value)
{
<option selected>@result.Value</option>
}
else
{
<option>@result.Value</option>
}
}
}
else
{
<option disabled>No Results</option>
}
</select>
}
</div>
@code {
Dictionary<string, string> _results;
[Parameter]
public Func<string, Task<Dictionary<string, string>>> OnSearch { get; set; } // required - an async delegate method which accepts a filter string parameter and returns a dictionary
[Parameter]
public int Characters { get; set; } = 3; // optional - number of characters before search is initiated
[Parameter]
public int Rows { get; set; } = 3; // optional - number of result rows to display
[Parameter]
public string Placeholder { get; set; } // optional - placeholder input text
[Parameter]
public string Value { get; set; } // value of item selected
[Parameter]
public string Key { get; set; } // key of item selected
private async Task OnInput(ChangeEventArgs e)
{
Value = e.Value?.ToString();
if (Value?.Length >= Characters)
{
_results = await OnSearch?.Invoke(Value);
}
else
{
_results = null;
}
SetKey();
}
private async Task OnKeyUp(KeyboardEventArgs e)
{
var index = -1;
switch (e.Key)
{
case "ArrowDown":
if (_results == null)
{
if (Value?.Length >= Characters)
{
_results = await OnSearch?.Invoke(Value);
}
}
else
{
index = GetIndex();
if (index < _results.Count - 1)
{
Value = _results.ElementAt(index + 1).Value;
Key = _results.ElementAt(index + 1).Key;
}
}
break;
case "ArrowUp":
index = GetIndex();
if (index > 0)
{
Value = _results.ElementAt(index - 1).Value;
Key = _results.ElementAt(index - 1).Key;
}
break;
case "ArrowRight":
case "Tab":
_results = null;
break;
case "Enter": // note within a form the enter key submits the entire form
case "NumpadEnter":
_results = null;
break;
case "Escape":
Value = "";
_results = null;
break;
}
}
private void OnChange(ChangeEventArgs e)
{
Value = (string)e.Value;
SetKey();
_results = null;
}
private int GetIndex()
{
if (_results != null)
{
for (int index = 0; index < _results.Count; index++)
{
if (_results.ElementAt(index).Value == Value)
{
return index;
}
}
}
return -1;
}
private void SetKey()
{
var index = GetIndex();
if (index != -1)
{
Key = _results.ElementAt(index).Key;
}
else
{
Key = "";
}
}
public void Clear()
{
Value = "";
Key = "";
_results = null;
}
}

View File

@ -1,11 +1,12 @@
@namespace Oqtane.Modules.Controls
@using System.Threading
@inherits ModuleControlBase
@inject IFolderService FolderService
@inject IFileService FileService
@inject IStringLocalizer<FileManager> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_folders != null)
@if (_initialized)
{
<div id="@Id" class="container-fluid px-0">
<div class="row">
@ -53,17 +54,27 @@
}
</div>
<div class="col mt-2 text-end">
<button type="button" class="btn btn-success" @onclick="UploadFile">@SharedLocalizer["Upload"]</button>
@if (ShowFiles && GetFileId() != -1)
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
@if (GetFileId() != -1)
{
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
}
</div>
</div>
<div class="row">
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
<div class="col text-center mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
</div>
@if (ShowProgress)
{
<div class="row">
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
<div class="col mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
</div>
}
else
{
if (_uploading)
{
<div class="app-progress-indicator"></div>
}
}
}
</div>
</div>
@ -86,7 +97,7 @@
}
@code {
private string _id;
private bool _initialized = false;
private List<Folder> _folders;
private List<File> _files = new List<File>();
private string _fileinputid = string.Empty;
@ -99,6 +110,7 @@
private string _guid;
private string _message = string.Empty;
private MessageType _messagetype;
private bool _uploading = false;
[Parameter]
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
@ -127,6 +139,9 @@
[Parameter]
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
[Parameter]
public bool ShowProgress { get; set; } = true; // optional - for indicating whether progress info should be displayed during upload - default is true
[Parameter]
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
@ -142,13 +157,8 @@
[Parameter]
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
if (!string.IsNullOrEmpty(Id))
{
_id = Id;
}
// packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder)
{
@ -158,11 +168,6 @@
ShowSuccess = true;
}
if (!ShowFiles)
{
ShowImage = false;
}
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
@ -186,7 +191,6 @@
if (file != null)
{
FolderId = file.FolderId;
await OnSelect.InvokeAsync(FileId);
}
else
{
@ -207,9 +211,11 @@
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = _guid + "FileInput";
_progressinfoid = _guid + "ProgressInfo";
_progressbarid = _guid + "ProgressBar";
_fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid;
_initialized = true;
}
private async Task GetFiles()
@ -225,26 +231,33 @@
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.Permissions);
_files = await FileService.GetFilesAsync(FolderId);
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
{
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
_files = new List<File>();
}
}
else
{
_haseditpermission = false;
_files = new List<File>();
}
}
if (_filter != "*")
{
List<File> filtered = new List<File>();
foreach (File file in _files)
if (_filter != "*")
{
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
List<File> filtered = new List<File>();
foreach (File file in _files)
{
filtered.Add(file);
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
{
filtered.Add(file);
}
}
_files = filtered;
}
_files = filtered;
}
}
@ -272,13 +285,10 @@
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
if (FileId != -1)
{
await OnSelect.InvokeAsync(FileId);
}
await SetImage();
await OnSelect.InvokeAsync(FileId);
StateHasChanged();
}
private async Task SetImage()
@ -298,65 +308,127 @@
var ratio = ratioX < ratioY ? ratioX : ratioY;
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
}
}
}
private async Task UploadFile()
private async Task UploadFiles()
{
_message = string.Empty;
var interop = new Interop(JSRuntime);
var upload = await interop.GetFiles(_fileinputid);
if (upload.Length > 0)
var uploads = await interop.GetFiles(_fileinputid);
if (uploads.Length > 0)
{
try
string restricted = "";
foreach (var upload in uploads)
{
string result;
if (Folder == Constants.PackagesFolder)
var extension = (upload.LastIndexOf(".") != -1) ? upload.Substring(upload.LastIndexOf(".") + 1) : "";
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{
result = await FileService.UploadFilesAsync(Folder, upload, _guid);
}
else
{
result = await FileService.UploadFilesAsync(FolderId, upload, _guid);
}
if (result == string.Empty)
{
await logger.LogInformation("File Upload Succeeded {Files}", upload);
if (ShowSuccess)
{
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
// set FileId to first file in upload collection
await GetFiles();
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
StateHasChanged();
}
else
{
await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", "));
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
restricted += (restricted == "" ? "" : ",") + extension;
}
}
catch (Exception ex)
if (restricted == "")
{
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
if (!ShowProgress)
{
_uploading = true;
StateHasChanged();
}
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
try
{
// upload the files
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken);
// uploading is asynchronous so we need to poll to determine if uploads are completed
var success = true;
int upload = 0;
while (upload < uploads.Length && success)
{
success = false;
// note that progressive retry will only wait a maximum of 15 seconds which may not be long enough for very large file uploads
int attempts = 0;
while (attempts < 5 && !success)
{
attempts += 1;
Thread.Sleep(1000 * attempts); // progressive retry
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[upload]);
if (file != null)
{
success = true;
}
}
if (success)
{
upload++;
}
}
// reset progress indicators
if (ShowProgress)
{
await interop.SetElementAttribute(_guid + "ProgressInfo", "style", "display: none;");
await interop.SetElementAttribute(_guid + "ProgressBar", "style", "display: none;");
}
else
{
_uploading = false;
StateHasChanged();
}
if (success)
{
await logger.LogInformation("File Upload Succeeded {Files}", uploads);
if (ShowSuccess)
{
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
}
else
{
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
if (Folder == Constants.PackagesFolder)
{
await OnUpload.InvokeAsync(-1);
}
else
{
// set FileId to first file in upload collection
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0]);
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
await GetFiles();
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
_uploading = false;
}
}
else
{
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
_messagetype = MessageType.Warning;
}
}
else
@ -375,26 +447,49 @@
await logger.LogInformation("File Deleted {File}", FileId);
await OnDelete.InvokeAsync(FileId);
_message = Localizer["Success.File.Delete"];
_messagetype = MessageType.Success;
if (ShowSuccess)
{
_message = Localizer["Success.File.Delete"];
_messagetype = MessageType.Success;
}
await GetFiles();
FileId = -1;
await SetImage();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
await GetFiles();
FileId = -1;
await SetImage();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
_message = Localizer["Error.File.Delete"];
_messagetype = MessageType.Error;
}
}
_message = Localizer["Error.File.Delete"];
_messagetype = MessageType.Error;
}
}
public int GetFileId() => FileId;
public int GetFileId() => FileId;
public int GetFolderId() => FolderId;
public int GetFolderId() => FolderId;
public File GetFile() => _file;
public File GetFile() => _file;
public async Task Refresh()
{
await Refresh(-1);
}
public async Task Refresh(int fileId)
{
await GetFiles();
if (fileId != -1)
{
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
}
}
StateHasChanged();
}
}

View File

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

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@inject IStringLocalizerFactory LocalizerFactory
@typeparam TableItem
@if (ItemList != null)
@ -7,16 +8,16 @@
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? "" : " disabled")">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? "" : " disabled")">
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? "" : " disabled")">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
@ -24,73 +25,76 @@
var pager = i;
if (pager == _page)
{
<li class="page-item active">
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item">
<li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? "" : " disabled")">
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? "" : " disabled")">
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? "" : " disabled")">
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@if (Format == "Table" && Row != null)
{
<div class="table-responsive">
<table class="@Class">
<thead>
<tr class="@RowClass">@Header</tr>
</thead>
<tbody>
@foreach (var item in ItemList)
{
<tr class="@RowClass">@Row(item)</tr>
@if (Detail != null)
{
<tr>@Detail(item)</tr>
}
}
</tbody>
</table>
</div>
<div class="table-responsive">
<table class="@Class">
<thead>
<tr class="@RowClass">@Header</tr>
</thead>
<tbody>
@foreach (var item in ItemList)
{
<tr class="@RowClass">@Row(item)</tr>
@if (Detail != null)
{
<tr>@Detail(item)</tr>
}
}
</tbody>
<tfoot>
<tr class="@RowClass">@Footer</tr>
</tfoot>
</table>
</div>
}
@if (Format == "Grid" && Row != null)
{
int count = 0;
int rows = 0;
int cols = 0;
if (ItemList != null)
int rows = 0;
int cols = 0;
if (ItemList != null)
{
if (_columns == 0)
{
count = ItemList.Count();
rows = 1;
cols = count;
}
else
{
count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns;
rows = count / _columns;
cols = _columns;
}
if (_columns == 0)
{
count = ItemList.Count();
rows = 1;
cols = count;
}
else
{
count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns;
rows = count / _columns;
cols = _columns;
}
}
<div class="@Class">
@for (int row = 0; row < rows; row++)
@ -112,19 +116,19 @@
}
</div>
}
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? "" : " disabled")">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? "" : " disabled")">
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? "" : " disabled")">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
@ -132,37 +136,38 @@
var pager = i;
if (pager == _page)
{
<li class="page-item active">
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item">
<li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? "" : " disabled")">
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? "" : " disabled")">
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? "" : " disabled")">
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
}
@code {
private IStringLocalizer Localizer;
private int _pages = 0;
private int _page = 1;
private int _maxItems = 10;
@ -183,6 +188,9 @@
[Parameter]
public RenderFragment<TableItem> Row { get; set; } = null; // required
[Parameter]
public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts
[Parameter]
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
@ -207,7 +215,7 @@
[Parameter]
public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid
[Parameter]
[Parameter]
public string ColumnClass { get; set; } // class for column element - only applicable to Grid format
[Parameter]
@ -215,6 +223,11 @@
private IEnumerable<TableItem> ItemList { get; set; }
protected override void OnInitialized()
{
Localizer = LocalizerFactory.Create(GetType().FullName);
}
protected override void OnParametersSet()
{
if (string.IsNullOrEmpty(Format))
@ -249,8 +262,8 @@
{
RowClass = "row";
}
}
}
if (string.IsNullOrEmpty(ColumnClass))
{
if (Format == "Table")
@ -261,9 +274,9 @@
{
ColumnClass = "col";
}
}
if (!string.IsNullOrEmpty(PageSize))
}
if (!string.IsNullOrEmpty(PageSize))
{
_maxItems = int.Parse(PageSize);
}
@ -286,6 +299,7 @@
{
_page = 1;
}
if (_page < 1) _page = 1;
_startPage = 0;
_endPage = 0;
@ -297,7 +311,6 @@
{
_page = _pages;
}
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
SetPagerSize();
}
}
@ -310,13 +323,13 @@
{
_endPage = _pages;
}
OnPageChange?.Invoke(_page);
StateHasChanged();
}
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
StateHasChanged();
OnPageChange?.Invoke(_page);
}
public void UpdateList(int page)
{
ItemList = Items.Skip((page - 1) * _maxItems).Take(_maxItems);
_page = page;
SetPagerSize();
}

View File

@ -1,7 +1,9 @@
@namespace Oqtane.Modules.Controls
@using System.Text.Json
@inherits ModuleControlBase
@inject IRoleService RoleService
@inject IUserService UserService
@inject IUserRoleService UserRoleService
@inject IStringLocalizer<PermissionGrid> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -14,20 +16,19 @@
<tbody>
<tr>
<th scope="col">@Localizer["Role"]</th>
@foreach (PermissionString permission in _permissions)
@foreach (var permissionname in _permissionnames)
{
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th>
}
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
}
</tr>
@foreach (Role role in _roles)
{
<tr>
<td>@role.Name</td>
@foreach (PermissionString permission in _permissions)
@foreach (var permissionname in _permissionnames)
{
var p = permission;
<td style="text-align: center;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled=@GetPermissionDisabled(role.Name) OnChange="@(e => PermissionChanged(e, p.PermissionName, role.Name))" />
<TriStateCheckBox Value=@GetPermissionValue(permissionname, role.Name, -1) Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
</td>
}
</tr>
@ -49,23 +50,21 @@
<thead>
<tr>
<th scope="col">@Localizer["User"]</th>
@foreach (PermissionString permission in _permissions)
{
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th>
}
</tr>
@foreach (var permissionname in _permissionnames)
{
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
}
</tr>
</thead>
<tbody>
@foreach (User user in _users)
{
string userid = "[" + user.UserId.ToString() + "]";
<tr>
<td>@user.DisplayName</td>
@foreach (PermissionString permission in _permissions)
@foreach (var permissionname in _permissionnames)
{
var p = permission;
<td style="text-align: center; width: 1px;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled=false OnChange="@(e => PermissionChanged(e, p.PermissionName, userid))" />
<TriStateCheckBox Value=@GetPermissionValue(permissionname, "", user.UserId) Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
</td>
}
</tr>
@ -77,34 +76,27 @@
</div>
</div>
<div class="row">
<div class="col">
<table class="table table-borderless">
<tbody>
<tr>
<td class="input-group">
<input type="text" name="Username" class="form-control" placeholder="@Localizer["Username.Enter"]" @bind="@_username" />
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
</td>
</tr>
</tbody>
</table>
<br />
<div class="col-11">
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
</div>
<div class="col-1">
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
</div>
</div>
<div class="row">
<div class="col">
<ModuleMessage Type="MessageType.Error" Message="@_message" />
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
</div>
</div>
</div>
}
@code {
private string _permissionnames = string.Empty;
private List<string> _permissionnames;
private List<Permission> _permissions;
private List<Role> _roles;
private List<PermissionString> _permissions;
private List<User> _users = new List<User>();
private string _username = string.Empty;
private AutoComplete _user;
private string _message = string.Empty;
[Parameter]
@ -114,159 +106,230 @@
public string PermissionNames { get; set; }
[Parameter]
public string Permissions { get; set; }
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public List<Permission> PermissionList { get; set; }
protected override async Task OnInitializedAsync()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_roles.RemoveAll(item => item.Name == RoleNames.Host);
}
// get permission names
if (string.IsNullOrEmpty(PermissionNames))
{
_permissionnames = Shared.PermissionNames.View + "," + Shared.PermissionNames.Edit;
_permissionnames = new List<string>();
_permissionnames.Add(Shared.PermissionNames.View);
_permissionnames.Add(Shared.PermissionNames.Edit);
}
else
{
_permissionnames = PermissionNames;
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
}
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId);
_roles.Insert(0, new Role { Name = RoleNames.Everyone });
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
// initialize permissions
_permissions = new List<Permission>();
if (PermissionList != null && PermissionList.Any())
{
_roles.Add(new Role { Name = RoleNames.Host });
}
_permissions = new List<PermissionString>();
foreach (string permissionname in _permissionnames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
// initialize with admin role
_permissions.Add(new PermissionString { PermissionName = permissionname, Permissions = RoleNames.Admin });
}
if (!string.IsNullOrEmpty(Permissions))
{
// populate permissions
foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions))
foreach (var permission in PermissionList)
{
if (_permissions.Find(item => item.PermissionName == permissionstring.PermissionName) != null)
_permissions.Add(permission);
if (permission.UserId != null)
{
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionstring.PermissionName)].Permissions = permissionstring.Permissions;
}
if (permissionstring.Permissions.Contains("["))
{
foreach (string user in permissionstring.Permissions.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries))
if (!_users.Any(item => item.UserId == permission.UserId.Value))
{
if (user.Contains("]"))
{
var userid = int.Parse(user.Substring(0, user.IndexOf("]")));
if (_users.Where(item => item.UserId == userid).FirstOrDefault() == null)
{
_users.Add(await UserService.GetUserAsync(userid, ModuleState.SiteId));
}
}
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
}
}
}
}
else
{
foreach (string permissionname in _permissionnames)
{
// permission names can be in the form of "EntityName:PermissionName:Roles"
if (permissionname.Contains(":"))
{
var segments = permissionname.Split(':');
if (segments.Length == 3)
{
foreach (var role in segments[2].Split(';'))
{
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
}
// ensure admin access
if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
{
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
}
}
}
else
{
_permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
}
}
}
}
private bool? GetPermissionValue(string permissions, string securityKey)
private string GetPermissionName(string permissionName)
{
if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";"))
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
}
private string GetEntityName(string permissionName)
{
return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
}
private string DisplayPermissionName(string permissionName)
{
var name = Localizer[GetPermissionName(permissionName)].ToString();
name += " " + Localizer[GetEntityName(permissionName)].ToString();
return name;
}
private bool? GetPermissionValue(string permissionName, string roleName, int userId)
{
bool? isauthorized = null;
if (roleName != "")
{
return false; // deny permission
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
if (permission != null)
{
isauthorized = permission.IsAuthorized;
}
}
else
{
if ((";" + permissions + ";").Contains(";" + securityKey + ";"))
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
if (permission != null)
{
return true; // grant permission
isauthorized = permission.IsAuthorized;
}
}
return isauthorized;
}
private bool GetPermissionDisabled(string permissionName, string roleName)
{
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
return true;
}
else
{
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
return true;
}
else
{
return null; // not specified
return false;
}
}
}
private bool GetPermissionDisabled(string roleName)
=> (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) ? true : false;
private void PermissionChanged(bool? value, string permissionName, string roleName, int userId)
{
if (roleName != "")
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
if (permission != null)
{
_permissions.Remove(permission);
}
if (value != null)
{
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
}
}
else
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
if (permission != null)
{
_permissions.Remove(permission);
}
if (value != null)
{
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
}
}
}
private async Task<Dictionary<string, string>> GetUsers(string filter)
{
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase))
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName);
}
private async Task AddUser()
{
if (_users.Where(item => item.Username == _username).FirstOrDefault() == null)
if (!string.IsNullOrEmpty(_user.Key))
{
try
var user = await UserService.GetUserAsync(int.Parse(_user.Key), ModuleState.SiteId);
if (user != null && !_users.Any(item => item.UserId == user.UserId))
{
var user = await UserService.GetUserAsync(_username, ModuleState.SiteId);
if (user != null)
{
_users.Add(user);
}
}
catch
{
_message = Localizer["Message.Username.DontExist"];
_users.Add(user);
}
}
_username = string.Empty;
}
private void PermissionChanged(bool? value, string permissionName, string securityId)
{
var selected = value;
var permission = _permissions.Find(item => item.PermissionName == permissionName);
if (permission != null)
else
{
var ids = permission.Permissions.Split(';').ToList();
ids.Remove(securityId); // remove grant permission
ids.Remove("!" + securityId); // remove deny permission
switch (selected)
{
case true:
ids.Add(securityId); // add grant permission
break;
case false:
ids.Add("!" + securityId); // add deny permission
break;
case null:
break; // permission not specified
}
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionName)].Permissions = string.Join(";", ids.ToArray());
_message = Localizer["Message.Username.DontExist"];
}
_user.Clear();
}
public string GetPermissions()
{
ValidatePermissions();
return UserSecurity.SetPermissionStrings(_permissions);
return JsonSerializer.Serialize(_permissions);
}
public List<Permission> GetPermissionList()
{
ValidatePermissions();
return _permissions;
}
private void ValidatePermissions()
{
PermissionString permission;
for (int i = 0; i < _permissions.Count; i++)
// remove deny all users, unauthenticated, and registered users
var permissions = _permissions.Where(item => !item.IsAuthorized &&
(item.RoleName == RoleNames.Everyone || item.RoleName == RoleNames.Unauthenticated || item.RoleName == RoleNames.Registered)).ToList();
foreach (var permission in permissions)
{
permission = _permissions[i];
List<string> ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList();
ids.Remove("!" + RoleNames.Everyone); // remove deny all users
ids.Remove("!" + RoleNames.Registered); // remove deny registered users
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
_permissions.Remove(permission);
}
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
// remove deny administrators and host users
permissions = _permissions.Where(item => !item.IsAuthorized &&
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)).ToList();
foreach (var permission in permissions)
{
ids.Remove("!" + RoleNames.Admin); // remove deny administrators
ids.Remove("!" + RoleNames.Host); // remove deny host users
if (!ids.Contains(RoleNames.Host) && !ids.Contains(RoleNames.Admin))
_permissions.Remove(permission);
}
foreach (var permissionname in _permissionnames)
{
// add administrators role if neither host or administrator is assigned
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) &&
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)))
{
// add administrators role if host user role is not assigned
ids.Add(RoleNames.Admin);
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Admin, null, true));
}
}
permission.Permissions = string.Join(";", ids.ToArray());
_permissions[i] = permission;
}
}
}
}

View File

@ -5,79 +5,100 @@
<div class="row" style="margin-bottom: 50px;">
<div class="col">
<TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor">
@if (AllowFileManagement)
{
@if (_filemanagervisible)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>&nbsp;&nbsp;
<button type="button" class="btn btn-primary" @onclick="InsertImage">@Localizer["InsertImage"]</button>
@if (_filemanagervisible)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseFileManager">@Localizer["Close"]</button>
}
</div>
}
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@if (ToolbarContent != null)
{
@ToolbarContent
}
else
{
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
}
</div>
<div @ref="@_editorElement">
</div>
</div>
</div>
</TabPanel>
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
<div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>
</div>
@if (ReadOnly)
{
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
}
else
{
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
}
</TabPanel>
<TabPanel Name="Rich" Heading="Rich Text Editor">
@if (_richfilemanager)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
@if (AllowRawHtml)
{
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>@((MarkupString)"&nbsp;&nbsp;")
}
@if (AllowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
}
@if (_richfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
}
</div>
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@if (ToolbarContent != null)
{
@ToolbarContent
}
else
{
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
}
</div>
<div @ref="@_editorElement">
</div>
</div>
</div>
</TabPanel>
@if (AllowRawHtml)
{
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
@if (_rawfilemanager)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>&nbsp;&nbsp;
@if (AllowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
}
@if (_rawfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
}
</div>
@if (ReadOnly)
{
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
}
else
{
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
}
</TabPanel>
}
</TabStrip>
</div>
</div>
@ -85,10 +106,11 @@
@code {
private ElementReference _editorElement;
private ElementReference _toolBar;
private bool _filemanagervisible = false;
private bool _richfilemanager = false;
private FileManager _fileManager;
private string _richhtml = string.Empty;
private string _originalrichhtml = string.Empty;
private bool _rawfilemanager = false;
private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty;
private string _message = string.Empty;
@ -102,6 +124,12 @@
[Parameter]
public string Placeholder { get; set; } = "Enter Your Content...";
[Parameter]
public bool AllowFileManagement { get; set; } = true;
[Parameter]
public bool AllowRawHtml { get; set; } = true;
// parameters only applicable to rich text editor
[Parameter]
public RenderFragment ToolbarContent { get; set; }
@ -112,9 +140,6 @@
[Parameter]
public string DebugLevel { get; set; } = "info";
[Parameter]
public bool AllowFileManagement { get; set; } = true;
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
@ -150,15 +175,18 @@
// preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
_originalrichhtml = await interop.GetHtml(_editorElement);
}
else
{
await interop.LoadEditorContent(_editorElement, _richhtml);
}
}
public void CloseFileManager()
public void CloseRichFileManager()
{
_filemanagervisible = false;
_richfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseRawFileManager()
{
_rawfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
@ -198,29 +226,55 @@
return _originalrawhtml;
}
}
}
}
public async Task InsertImage()
{
_message = string.Empty;
if (_filemanagervisible)
{
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new RichTextEditorInterop(JSRuntime);
await interop.InsertImage(_editorElement, file.Url, file.Name);
_filemanagervisible = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_filemanagervisible = true;
}
StateHasChanged();
}
public async Task InsertRichImage()
{
_message = string.Empty;
if (_richfilemanager)
{
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new RichTextEditorInterop(JSRuntime);
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
_richfilemanager = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_richfilemanager = true;
}
StateHasChanged();
}
public async Task InsertRawImage()
{
_message = string.Empty;
if (_rawfilemanager)
{
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new Interop(JSRuntime);
int pos = await interop.GetCaretPosition("rawhtmleditor");
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
_rawfilemanager = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_rawfilemanager = true;
}
StateHasChanged();
}
}

View File

@ -17,7 +17,10 @@
<hr class="app-rule" />
</div>
<div class="collapse @_show" id="@Name">
@ChildContent
@if (ChildContent != null)
{
@ChildContent
}
</div>
@code {
@ -26,7 +29,7 @@
private string _show = string.Empty;
[Parameter]
public RenderFragment ChildContent { get; set; }
public RenderFragment ChildContent { get; set; } = null;
[Parameter]
public string Name { get; set; } // required - the name of the section
@ -37,19 +40,10 @@
[Parameter]
public string Expanded { get; set; } // optional - will default to false if not provided
protected override void OnInitialized()
{
_heading = (!string.IsNullOrEmpty(Heading)) ? Heading : Name;
_expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded : "false";
if (_expanded == "true") { _show = "show"; }
}
protected override void OnParametersSet()
{
base.OnParametersSet();
_heading = !string.IsNullOrEmpty(Heading)
? Localize(nameof(Heading), Heading)
: Localize(nameof(Name), Name);
_heading = !string.IsNullOrEmpty(Heading) ? Localize(nameof(Heading), Heading) : Localize(nameof(Name), Name);
_expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded.ToLower() : "false";
if (_expanded == "true") { _show = "show"; }
}
}

View File

@ -3,13 +3,13 @@
@if (Name == Parent.ActiveTab)
{
<div id="@Name" class="tab-pane fade show active" role="tabpanel">
<div id="@(Parent.Id + Name)" class="tab-pane fade show active" role="tabpanel">
@ChildContent
</div>
}
else
{
<div id="@Name" class="tab-pane fade" role="tabpanel">
<div id="@(Parent.Id + Name)" class="tab-pane fade" role="tabpanel">
@ChildContent
</div>
}

View File

@ -8,15 +8,15 @@
@foreach (TabPanel tabPanel in _tabPanels)
{
<li class="nav-item" @key="tabPanel.Name">
@if (tabPanel.Name == ActiveTab)
@if (tabPanel.Name.ToLower() == ActiveTab.ToLower())
{
<a class="nav-link active" data-bs-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
<a class="nav-link active" data-bs-toggle="tab" href="#@(Id + tabPanel.Name)" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()
</a>
}
else
{
<a class="nav-link" data-bs-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
<a class="nav-link" data-bs-toggle="tab" href="#@(Id + tabPanel.Name)" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()
</a>
}
@ -32,18 +32,31 @@
</CascadingValue>
@code {
private List<TabPanel> _tabPanels;
private List<TabPanel> _tabPanels;
private string _tabpanelid = string.Empty;
[Parameter]
public RenderFragment ChildContent { get; set; } // contains the TabPanels
[Parameter]
public RenderFragment ChildContent { get; set; } // contains the TabPanels
[Parameter]
public string ActiveTab { get; set; } // optional - defaults to first TabPanel if not specified. Can also be set using a "tab=" querystring parameter.
[Parameter]
public string ActiveTab { get; set; } // optional - defaults to first TabPanel if not specified. Can also be set using a "tab=" querystring parameter.
[Parameter]
public bool Refresh { get; set; } // optional - used in scenarios where TabPanels are added/removed dynamically within a parent form. ActiveTab may need to be reset as well when this property is used.
[Parameter]
public bool Refresh { get; set; } // optional - used in scenarios where TabPanels are added/removed dynamically within a parent form. ActiveTab may need to be reset as well when this property is used.
protected override void OnParametersSet()
[Parameter]
public string Id { get; set; } // optional - used to uniquely identify an instance of a tab strip component (will be set automatically if no value provided)
protected override void OnInitialized()
{
if (string.IsNullOrEmpty(Id))
{
// create unique id for component
Id = "TabStrip_" + Guid.NewGuid().ToString("N") + "_" ;
}
}
protected override void OnParametersSet()
{
if (PageState.QueryString.ContainsKey("tab"))
{
@ -80,10 +93,10 @@
authorized = true;
break;
case SecurityAccessLevel.View:
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, ModuleState.Permissions);
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, ModuleState.PermissionList);
break;
case SecurityAccessLevel.Edit:
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.Permissions);
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList);
break;
case SecurityAccessLevel.Admin:
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);

View File

@ -13,7 +13,7 @@
<TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit">
@if (_content != null)
{
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" @ref="@RichTextEditorHtml"></RichTextEditor>
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" AllowRawHtml="@_allowrawhtml" @ref="@RichTextEditorHtml"></RichTextEditor>
<br />
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@ -53,13 +53,13 @@
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" }
};
private RichTextEditor RichTextEditorHtml;
private bool _allowfilemanagement;
private bool _allowrawhtml;
private string _content = null;
private string _createdby;
private DateTime _createdon;
@ -73,6 +73,7 @@
try
{
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
_allowrawhtml = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true"));
await LoadContent();
}
catch (Exception ex)

View File

@ -15,11 +15,6 @@
}
@code {
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
private string content = "";
protected override async Task OnParametersSetAsync()

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText
{
@ -13,7 +15,11 @@ namespace Oqtane.Modules.HtmlText
Version = "1.0.1",
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
ReleaseVersions = "1.0.0,1.0.1",
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client",
Resources = new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" }
}
};
}
}

View File

@ -15,18 +15,29 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="files" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify If Editors Can Enter Raw HTML">Allow Raw HTML: </Label>
<div class="col-sm-9">
<select id="files" class="form-select" @bind="@_allowrawhtml">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
@code {
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
private string _allowfilemanagement;
private string _allowrawhtml;
protected override void OnInitialized()
{
try
{
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
}
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
_allowrawhtml = SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true");
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
@ -39,7 +50,8 @@
{
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "AllowRawHtml", _allowrawhtml);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{

View File

@ -15,6 +15,8 @@ namespace Oqtane.Modules
public abstract class ModuleBase : ComponentBase, IModuleControl
{
private Logger _logger;
private string _urlparametersstate;
private Dictionary<string, string> _urlparameters;
protected Logger logger => _logger ?? (_logger = new Logger(this));
@ -24,6 +26,9 @@ namespace Oqtane.Modules
[Inject]
protected IJSRuntime JSRuntime { get; set; }
[Inject]
protected SiteState SiteState { get; set; }
[CascadingParameter]
protected PageState PageState { get; set; }
@ -44,22 +49,63 @@ namespace Oqtane.Modules
public virtual List<Resource> Resources { get; set; }
// url parameters
public virtual string UrlParametersTemplate { get; set; }
public Dictionary<string, string> UrlParameters {
get
{
if (_urlparametersstate == null || _urlparametersstate != PageState.UrlParameters)
{
_urlparametersstate = PageState.UrlParameters;
_urlparameters = GetUrlParameters(UrlParametersTemplate);
}
return _urlparameters;
}
}
// base lifecycle method for handling JSInterop script registration
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script))
List<Resource> resources = null;
var type = GetType();
if (type.BaseType == typeof(ModuleBase))
{
var scripts = new List<object>();
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
if (PageState.Page.Resources != null)
{
scripts.Add(new { href = resource.Url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList();
}
}
else // modulecontrolbase
{
if (Resources != null)
{
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
}
}
if (resources != null &&resources.Any())
{
var interop = new Interop(JSRuntime);
var scripts = new List<object>();
var inline = 0;
foreach (Resource resource in resources)
{
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() });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
}
}
if (scripts.Any())
{
var interop = new Interop(JSRuntime);
await interop.IncludeScripts(scripts.ToArray());
}
}
@ -70,10 +116,11 @@ namespace Oqtane.Modules
public string ModulePath()
{
return "Modules/" + GetType().Namespace + "/";
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
}
// url methods
public string NavigateUrl()
{
return NavigateUrl(PageState.Page.Path);
@ -124,14 +171,23 @@ namespace Oqtane.Modules
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
}
public string ContentUrl(int fileid)
public string FileUrl(string folderpath, string filename)
{
return ContentUrl(fileid, false);
return FileUrl(folderpath, filename, false);
}
public string ContentUrl(int fileid, bool asAttachment)
public string FileUrl(string folderpath, string filename, bool download)
{
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment);
return Utilities.FileUrl(PageState.Alias, folderpath, filename, download);
}
public string FileUrl(int fileid)
{
return FileUrl(fileid, false);
}
public string FileUrl(int fileid, bool download)
{
return Utilities.FileUrl(PageState.Alias, fileid, download);
}
public string ImageUrl(int fileid, int width, int height)
@ -149,15 +205,21 @@ namespace Oqtane.Modules
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate);
}
public virtual Dictionary<string, string> GetUrlParameters(string parametersTemplate = "")
public string AddUrlParameters(params object[] parameters)
{
return Utilities.AddUrlParameters(parameters);
}
// template is in the form of a standard route template ie. "/{id}/{name}" and produces dictionary of key/value pairs
// if url parameters belong to a specific module you should embed a unique key into the route (ie. /!/blog/1) and validate the url parameter key in the module
public virtual Dictionary<string, string> GetUrlParameters(string template = "")
{
var urlParameters = new Dictionary<string, string>();
string[] templateSegments;
var parameters = PageState.UrlParameters.Split('/', StringSplitOptions.RemoveEmptyEntries);
var parameterId = 0;
var parameters = _urlparametersstate.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (string.IsNullOrEmpty(parametersTemplate))
if (string.IsNullOrEmpty(template))
{
// no template will populate dictionary with generic "parameter#" keys
for (int i = 0; i < parameters.Length; i++)
{
urlParameters.TryAdd("parameter" + i, parameters[i]);
@ -165,39 +227,37 @@ namespace Oqtane.Modules
}
else
{
templateSegments = parametersTemplate.Split('/', StringSplitOptions.RemoveEmptyEntries);
var segments = template.Split('/', StringSplitOptions.RemoveEmptyEntries);
string key;
if (parameters.Length == templateSegments.Length)
for (int i = 0; i < parameters.Length; i++)
{
for (int i = 0; i < parameters.Length; i++)
if (i < segments.Length)
{
if (parameters.Length > i)
key = segments[i];
if (key.StartsWith("{") && key.EndsWith("}"))
{
if (templateSegments[i] == parameters[i])
{
urlParameters.TryAdd("parameter" + parameterId, parameters[i]);
parameterId++;
}
else if (templateSegments[i].StartsWith("{") && templateSegments[i].EndsWith("}"))
{
var key = templateSegments[i].Replace("{", "");
key = key.Replace("}", "");
urlParameters.TryAdd(key, parameters[i]);
}
else
{
i = parameters.Length;
urlParameters.Clear();
}
// dynamic segment
key = key.Substring(1, key.Length - 2);
}
else
{
// static segments use generic "parameter#" keys
key = "parameter" + i.ToString();
}
}
else // unspecified segments use generic "parameter#" keys
{
key = "parameter" + i.ToString();
}
urlParameters.TryAdd(key, parameters[i]);
}
}
return urlParameters;
}
// user feedback methods
// UI methods
public void AddModuleMessage(string message, MessageType type)
{
ModuleInstance.AddModuleMessage(message, type);
@ -218,6 +278,45 @@ namespace Oqtane.Modules
ModuleInstance.HideProgressIndicator();
}
public void SetModuleTitle(string title)
{
var obj = new { PageModuleId = ModuleState.PageModuleId, Title = title };
SiteState.Properties.ModuleTitle = obj;
}
public void SetModuleVisibility(bool visible)
{
var obj = new { PageModuleId = ModuleState.PageModuleId, Visible = visible };
SiteState.Properties.ModuleVisibility = obj;
}
public void SetPageTitle(string title)
{
SiteState.Properties.PageTitle = title;
}
// note - only supports links and meta tags - not scripts
public void AddHeadContent(string content)
{
SiteState.AppendHeadContent(content);
}
public void AddScript(Resource resource)
{
resource.ResourceType = ResourceType.Script;
if (Resources == null) Resources = new List<Resource>();
if (!Resources.Any(item => (!string.IsNullOrEmpty(resource.Url) && item.Url == resource.Url) || (!string.IsNullOrEmpty(resource.Content) && item.Content == resource.Content)))
{
Resources.Add(resource);
}
}
public async Task ScrollToPageTop()
{
var interop = new Interop(JSRuntime);
await interop.ScrollTo(0, 0, "smooth");
}
// logging methods
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
{
@ -255,15 +354,10 @@ namespace Oqtane.Modules
{
int pageId = ModuleState.PageId;
int moduleId = ModuleState.ModuleId;
int? userId = null;
if (PageState.User != null)
{
userId = PageState.User.UserId;
}
string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1);
await LoggingService.Log(alias, pageId, moduleId, userId, category, feature, function, level, exception, message, args);
await LoggingService.Log(alias, pageId, moduleId, PageState.User?.UserId, category, feature, function, level, exception, message, args);
}
public class Logger
@ -365,5 +459,17 @@ namespace Oqtane.Modules
await _moduleBase.Log(null, LogLevel.Critical, "", exception, message, args);
}
}
[Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)]
public string ContentUrl(int fileid)
{
return ContentUrl(fileid, false);
}
[Obsolete("ContentUrl(int fileId, bool asAttachment) is deprecated. Use FileUrl(int fileId, bool download) instead.", false)]
public string ContentUrl(int fileid, bool asAttachment)
{
return Utilities.FileUrl(PageState.Alias, fileid, asAttachment);
}
}
}

View File

@ -1,19 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations>
<Version>3.1.2</Version>
<Version>4.0.1</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
<Description>Modular Application Framework for Blazor</Description>
<Description>CMS and Application Framework for Blazor and .NET MAUI</Description>
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -22,26 +21,21 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="System.Runtime" />
<TrimmerRootAssembly Include="System.Linq.Parallel" />
<TrimmerRootAssembly Include="System.Runtime.CompilerServices.VisualC" />
</ItemGroup>
<PropertyGroup>
<PublishTrimmed>false</PublishTrimmed>
<BlazorEnableCompression>false</BlazorEnableCompression>
</PropertyGroup>

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.Loader;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization;
@ -15,7 +16,6 @@ using Microsoft.JSInterop;
using Oqtane.Documentation;
using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Client
@ -33,7 +33,7 @@ namespace Oqtane.Client
builder.Services.AddOptions();
// Register localization services
// register localization services
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services
@ -42,7 +42,9 @@ namespace Oqtane.Client
// register scoped core services
builder.Services.AddOqtaneScopedServices();
await LoadClientAssemblies(httpClient);
var serviceProvider = builder.Services.BuildServiceProvider();
await LoadClientAssemblies(httpClient, serviceProvider);
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
@ -58,33 +60,106 @@ namespace Oqtane.Client
await SetCultureFromLocalizationCookie(host.Services);
ServiceActivator.Configure(host.Services);
await host.RunAsync();
}
private static async Task LoadClientAssemblies(HttpClient http)
private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider)
{
// get list of loaded assemblies on the client
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList();
var dlls = new Dictionary<string, byte[]>();
var pdbs = new Dictionary<string, byte[]>();
var list = new List<string>();
// get assemblies from server and load into client app domain
var zip = await http.GetByteArrayAsync($"/api/Installation/load");
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime);
var files = await interop.GetIndexedDBKeys(".dll");
// asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
if (files.Count() != 0)
{
var dlls = new Dictionary<string, byte[]>();
var pdbs = new Dictionary<string, byte[]>();
// get list of assemblies from server
var json = await http.GetStringAsync("/api/Installation/list");
var assemblies = JsonSerializer.Deserialize<List<string>>(json);
foreach (ZipArchiveEntry entry in archive.Entries)
// determine which assemblies need to be downloaded
foreach (var assembly in assemblies)
{
if (!assemblies.Contains(Path.GetFileNameWithoutExtension(entry.FullName)))
var file = files.FirstOrDefault(item => item.Contains(assembly));
if (file == null)
{
list.Add(assembly);
}
else
{
// check if newer version available
if (GetFileDate(assembly) > GetFileDate(file))
{
list.Add(assembly);
}
}
}
// get assemblies already downloaded
foreach (var file in files)
{
if (assemblies.Contains(file) && !list.Contains(file))
{
try
{
dlls.Add(file, await interop.GetIndexedDBItem<byte[]>(file));
var pdb = file.Replace(".dll", ".pdb");
if (files.Contains(pdb))
{
pdbs.Add(pdb, await interop.GetIndexedDBItem<byte[]>(pdb));
}
}
catch
{
// ignore
}
}
else // file is deprecated
{
try
{
await interop.RemoveIndexedDBItem(file);
await interop.RemoveIndexedDBItem(file.Replace(".dll", ".pdb"));
}
catch
{
// ignore
}
}
}
}
else
{
list.Add("*");
}
if (list.Count != 0)
{
// get assemblies from server and load into client app domain
var zip = await http.GetByteArrayAsync($"/api/Installation/load?list=" + string.Join(",", list));
// asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
using (var memoryStream = new MemoryStream())
{
entry.Open().CopyTo(memoryStream);
byte[] file = memoryStream.ToArray();
// save assembly to indexeddb
try
{
await interop.SetIndexedDBItem(entry.FullName, file);
}
catch
{
// ignore
}
switch (Path.GetExtension(entry.FullName))
{
case ".dll":
@ -97,40 +172,62 @@ namespace Oqtane.Client
}
}
}
}
foreach (var item in dlls)
// load assemblies into app domain
foreach (var item in dlls)
{
if (pdbs.ContainsKey(item.Key.Replace(".dll", ".pdb")))
{
if (pdbs.ContainsKey(item.Key))
{
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value), new MemoryStream(pdbs[item.Key]));
}
else
{
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value));
}
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value), new MemoryStream(pdbs[item.Key.Replace(".dll", ".pdb")]));
}
else
{
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value));
}
}
}
private static DateTime GetFileDate(string filepath)
{
var segments = filepath.Split('.');
return DateTime.ParseExact(segments[segments.Length - 2], "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
}
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
{
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
// dynamically register module scoped services
try
{
if (implementationType.AssemblyQualifiedName != null)
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
services.AddScoped(serviceType ?? implementationType, implementationType);
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
}
catch
{
// could not interrogate assembly - likely missing dependencies
}
}
private static void RegisterClientStartups(Assembly assembly, IServiceCollection services)
{
var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
try
{
startup.ConfigureServices(services);
var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(services);
}
}
catch
{
// could not interrogate assembly - likely missing dependencies
}
}
@ -141,7 +238,7 @@ namespace Oqtane.Client
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync();
var cultures = await localizationService.GetCulturesAsync(false);
if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase)))
{

View File

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

View File

@ -124,7 +124,7 @@
<value>Database:</value>
</data>
<data name="ApplicationAdmin" xml:space="preserve">
<value>Application Administrator</value>
<value>Application Administration</value>
</data>
<data name="InstallNow" xml:space="preserve">
<value>Install Now</value>
@ -135,10 +135,10 @@
<data name="Message.Require.DbInfo" xml:space="preserve">
<value>Please Enter All Required Fields. Ensure Passwords Match And Email Address Provided Is Valid.</value>
</data>
<data name="Message.Password.Invalid" xml:space="preserve">
<value>The Password Provided Does Not Meet The Password Policy. Please Verify The Minimum Password Length And Complexity Requirements.</value>
<data name="Message.Password.Invalid" xml:space="preserve">
<value>The Password Provided Does Not Meet The Complexity Policy. Passwords Must Be At Least 6 Characters In Length And Contain Uppercase, Lowercase, Numeric, And Punctuation Characters.</value>
</data>
<data name="Register" xml:space="preserve">
<data name="Register" xml:space="preserve">
<value>Please Register Me For Major Product Updates And Security Bulletins</value>
</data>
<data name="Confirm.HelpText" xml:space="preserve">
@ -168,4 +168,19 @@
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>Enter a complete connection string including all parameters and delimiters</value>
</data>
<data name="ConnectionString.Text" xml:space="preserve">
<value>Settings:</value>
</data>
<data name="EnterConnectionParameters" xml:space="preserve">
<value>Enter Connection Parameters</value>
</data>
<data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value>
</data>
<data name="Template" xml:space="preserve">
<value>Select a site template</value>
</data>
</root>

View File

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

View File

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

View File

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

View File

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

View File

@ -117,9 +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="The Only Supported Culture That Has Been Defined Is English" xml:space="preserve">
<value>The Only Supported Culture That Has Been Defined Is English</value>
</data>
<data name="Error.Language.Add" xml:space="preserve">
<value>Error Adding Language</value>
</data>
@ -135,32 +132,14 @@
<data name="IsDefault.Text" xml:space="preserve">
<value>Default?</value>
</data>
<data name="AllLanguages" xml:space="preserve">
<value>All The Installed Languages Have Been Added.</value>
</data>
<data name="Error.Language.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="OnlyEnglish" xml:space="preserve">
<value>The Only Installed Language Is English</value>
</data>
<data name="Success.Language.Download" xml:space="preserve">
<value>Translation Downloaded Successfully. Click Install To Complete Installation.</value>
</data>
<data name="Success.Language.Install" xml:space="preserve">
<value>Translations Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Translations Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Download</value>
<value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; To Complete The Installation.</value>
</data>
<data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translations. Once they are uploaded click Install to complete the installation.</value>
<value>Upload one or more translation packages.</value>
</data>
<data name="LanguageUpload.Text" xml:space="preserve">
<value>Upload Language</value>
<value>Translation</value>
</data>
<data name="Manage.Heading" xml:space="preserve">
<value>Manage</value>

View File

@ -132,16 +132,19 @@
<data name="DeleteLanguage.Header" xml:space="preserve">
<value>Delete Language</value>
</data>
<data name="Success.Language.Download" xml:space="preserve">
<value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Error.Language.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="Success.Language.Install" xml:space="preserve">
<value>Translation Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Default" xml:space="preserve">
<value>Default</value>
</data>
<data name="DeleteLanguage.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Translation" xml:space="preserve">
<value>Translation</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -136,7 +136,7 @@
<value>The function that was performed</value>
</data>
<data name="Category.HelpText" xml:space="preserve">
<value>The categories that were affected</value>
<value>The fully qualified type type that was affected</value>
</data>
<data name="Page.HelpText" xml:space="preserve">
<value>The page that was affected</value>
@ -178,7 +178,7 @@
<value>Function: </value>
</data>
<data name="Category.Text" xml:space="preserve">
<value>Category: </value>
<value>Type Name: </value>
</data>
<data name="Page.Text" xml:space="preserve">
<value>Page: </value>

View File

@ -123,17 +123,14 @@
<data name="Error.Package.Load" xml:space="preserve">
<value>Error Loading Packages</value>
</data>
<data name="Success.Module.Install" xml:space="preserve">
<value>Module Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Success.Module.Download" xml:space="preserve">
<value>Module Downloaded Successfully. Click Install To Complete Installation.</value>
<value>Module Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Error.Module.Download" xml:space="preserve">
<value>Error Downloading Module</value>
</data>
<data name="Module.HelpText" xml:space="preserve">
<value>Upload one or more module packages. Once they are uploaded click Install to complete the installation.</value>
<value>Upload one or more module packages.</value>
</data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Modules Match The Criteria Provided Or Package Service Is Disabled</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
@ -195,4 +195,34 @@
<data name="Information.Text" xml:space="preserve">
<value>Information</value>
</data>
<data name="PackageName.HelpText" xml:space="preserve">
<value>The unique name of the package from which this module was installed</value>
</data>
<data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value>
</data>
<data name="Error.Translation.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="Search.PackageNameMissing" xml:space="preserve">
<value>A Package Name Was Not Provided For The Module</value>
</data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Translations Exist For This Module Or Package Service Is Disabled</value>
</data>
<data name="Success.Translation.Download" xml:space="preserve">
<value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Translations.Heading" xml:space="preserve">
<value>Translations</value>
</data>
<data name="Message.DuplicateName" xml:space="preserve">
<value>A Module With The Name Specified Already Exists</value>
</data>
<data name="IsEnabled.HelpText" xml:space="preserve">
<value>Is module enabled for this site?</value>
</data>
<data name="IsEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
</root>

View File

@ -145,9 +145,15 @@
<value>Delete Module</value>
</data>
<data name="InUse" xml:space="preserve">
<value>In Use</value>
<value>In Use?</value>
</data>
<data name="EditModule.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Modules" xml:space="preserve">
<value>Modules</value>
</data>
<data name="Enabled" xml:space="preserve">
<value>Enabled?</value>
</data>
</root>

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -150,8 +150,8 @@
<data name="Container.Select" xml:space="preserve">
<value>Select Container</value>
</data>
<data name="Error.Page.Initialize" xml:space="preserve">
<value>Error Initializing Page</value>
<data name="Error.Page.Load" xml:space="preserve">
<value>Error Loading Page</value>
</data>
<data name="Error.ChildPage.Load" xml:space="preserve">
<value>Error Loading Child Pages For Parent</value>
@ -160,7 +160,7 @@
<value>Error Loading Pane Layouts For Theme</value>
</data>
<data name="Message.Page.Exists" xml:space="preserve">
<value>A page with path {0} already exists for the selected parent page. The page path needs to be unique for the selected parent.</value>
<value>A page with path '{0}' already exists for this site. Page paths must be unique. You may need to check if a page with this path exists in the Recycle Bin.</value>
</data>
<data name="Message.Required.PageInfo" xml:space="preserve">
<value>You Must Provide Page Name, Theme, and Container</value>
@ -228,13 +228,25 @@
<data name="Appearance.Name" xml:space="preserve">
<value>Appearance</value>
</data>
<data name="Message.Page.Deleted" xml:space="preserve">
<value>A page with path {0} already exists for the selected parent page in the Recycle Bin. Either recover the page or remove from the Recycle Bin and create it again.</value>
<data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
<data name="Meta.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value>
<data name="HeadContent.Text" xml:space="preserve">
<value>Head Content:</value>
</data>
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
<data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="ThemeChanged.Message" xml:space="preserve">
<value>Please Note That Overriding The Default Site Theme With An Unrelated Page Theme May Result In Compatibility Issues For Your Site</value>
</data>
</root>

View File

@ -151,7 +151,7 @@
<value>Error Loading Pane Layouts For Theme</value>
</data>
<data name="Mesage.Page.PathExists" xml:space="preserve">
<value>A page with path {0} already exists for the selected parent page. The page path needs to be unique for the selected parent.</value>
<value>A page with path '{0}' already exists for this site. Page paths must be unique. You may need to check if a page with this path exists in the Recycle Bin.</value>
</data>
<data name="Message.Required.PageInfo" xml:space="preserve">
<value>You Must Provide Page Name, Theme, and Container</value>
@ -264,10 +264,25 @@
<data name="Clickable.Text" xml:space="preserve">
<value>Clickable?</value>
</data>
<data name="Meta.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value>
<data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
<data name="HeadContent.Text" xml:space="preserve">
<value>Head Content:</value>
</data>
<data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="ThemeChanged.Message" xml:space="preserve">
<value>Please Note That Overriding The Default Site Theme With An Unrelated Page Theme May Result In Compatibility Issues For Your Site</value>
</data>
</root>

View File

@ -183,4 +183,10 @@
<data name="Private.Text" xml:space="preserve">
<value>Private? </value>
</data>
<data name="Validation.HelpText" xml:space="preserve">
<value>Optionally provide a regular expression (RegExp) for validating the value entered</value>
</data>
<data name="Validation.Text" xml:space="preserve">
<value>Validation:</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -166,16 +166,22 @@
<value>Error Permanently Deleting Modules</value>
</data>
<data name="DeleteAllPages.Header" xml:space="preserve">
<value>Delete All Pages</value>
<value>Remove All Deleted Pages</value>
</data>
<data name="DeleteAllPages.Message" xml:space="preserve">
<value>Are You Sure You Wish To Permanently Delete All Pages?</value>
<value>Are You Sure You Wish To Permanently Remove All Deleted Pages?</value>
</data>
<data name="DeleteAllPages.Text" xml:space="preserve">
<value>Remove All Deleted Pages</value>
</data>
<data name="DeleteAllModules.Header" xml:space="preserve">
<value>Delete All Modules</value>
<value>Remove All Deleted Modules</value>
</data>
<data name="DeleteAllModules.Message" xml:space="preserve">
<value>Are You Sure You Wish To Permanently Delete All Modules?</value>
<value>Are You Sure You Wish To Permanently Remove All Deleted Modules?</value>
</data>
<data name="DeleteAllModules.Text" xml:space="preserve">
<value>Remove All Deleted Modules</value>
</data>
<data name="Pages.Heading" xml:space="preserve">
<value>Pages</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
@ -166,7 +166,7 @@
<value>Email:</value>
</data>
<data name="Password.HelpText" xml:space="preserve">
<value>Please choose a sufficiently secure password and enter it here</value>
<value>Please enter a sufficiently secure password which meets the password complexity requirements</value>
</data>
<data name="Password.Text" xml:space="preserve">
<value>Password:</value>
@ -177,4 +177,19 @@
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="Password.ValidationCriteria" xml:space="preserve">
<value>Passwords Must Have A Minimum Length Of {0} Characters, Including At Least {1} Unique Character(s), {2}{3}{4}{5} To Satisfy Password Compexity Requirements For This Site.</value>
</data>
<data name="Password.DigitRequirement" xml:space="preserve">
<value>At Least One Digit</value>
</data>
<data name="Password.LowercaseRequirement" xml:space="preserve">
<value>At Least One Lowercase Letter</value>
</data>
<data name="Password.PunctuationRequirement" xml:space="preserve">
<value>At Least One Punctuation Mark</value>
</data>
<data name="Password.UppercaseRequirement" xml:space="preserve">
<value>At Least One Uppercase Letter</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -121,7 +121,7 @@
<value>User: </value>
</data>
<data name="User.Select" xml:space="preserve">
<value>Select User</value>
<value>Enter User's Name</value>
</data>
<data name="Users" xml:space="preserve">
<value>Users</value>
@ -129,9 +129,6 @@
<data name="Error.User.Load" xml:space="preserve">
<value>Error Loading Users</value>
</data>
<data name="Error.User.LoadRole" xml:space="preserve">
<value>Error Loading User Roles</value>
</data>
<data name="Success.User.AssignedRole" xml:space="preserve">
<value>User Assigned To Role</value>
</data>
@ -151,7 +148,7 @@
<value>The role you are assigning users to</value>
</data>
<data name="User.HelpText" xml:space="preserve">
<value>Select a user</value>
<value>Enter the name of a user</value>
</data>
<data name="EffectiveDate.HelpText" xml:space="preserve">
<value>The date that this role assignment is active</value>

View File

@ -142,7 +142,7 @@
<value>Site Settings Saved</value>
</data>
<data name="Message.Aliases.Taken" xml:space="preserve">
<value>The Default Alias Has Not Been Specified Or An Alias Was Specified That Has Already Been Used For Another Site</value>
<value>An Alias Was Specified That Has Already Been Used For Another Site</value>
</data>
<data name="Message.Required.SiteName" xml:space="preserve">
<value>You Must Provide A Site Name, Alias, And Default Theme/Container</value>
@ -163,10 +163,10 @@
<value>Enter the site name</value>
</data>
<data name="Tenant.HelpText" xml:space="preserve">
<value>Enter the tenant for the site</value>
<value>The name of the database used for the site</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve">
<value>The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas.</value>
<value>The 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>
</data>
<data name="IsDeleted.HelpText" xml:space="preserve">
<value>Is this site deleted?</value>
@ -175,7 +175,7 @@
<value>Specify a logo for the site</value>
</data>
<data name="FavoriteIcon.HelpText" xml:space="preserve">
<value>Specify a Favicon</value>
<value>Specify a Favicon. The format for the image must be 16×16, 32×32, 48×48, or 64×64 pixels in size, and 8-bit, 24-bit, or 32-bit in color depth. The format of the image must be ICO, PNG, or GIF.</value>
</data>
<data name="DefaultTheme.HelpText" xml:space="preserve">
<value>Select the sites default theme</value>
@ -195,13 +195,13 @@
<data name="UseSsl.HelpText" xml:space="preserve">
<value>Specify if SSL is required for your SMTP server</value>
</data>
<data name="SmptUsername.HelpText" xml:space="preserve">
<data name="SmtpUsername.HelpText" xml:space="preserve">
<value>Enter the username for your SMTP account</value>
</data>
<data name="SmtpPassword.HelpText" xml:space="preserve">
<value>Enter the password for your SMTP account</value>
</data>
<data name="SmptSender.HelpText" xml:space="preserve">
<data name="SmtpSender.HelpText" xml:space="preserve">
<value>Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server.</value>
</data>
<data name="EnablePWA.HelpText" xml:space="preserve">
@ -214,10 +214,10 @@
<value>Include a splash icon for your PWA. It should be a PNG which is 512 X 512 pixels in dimension.</value>
</data>
<data name="Tenant.Text" xml:space="preserve">
<value>Tenant: </value>
<value>Database: </value>
</data>
<data name="Aliases.Text" xml:space="preserve">
<value>Aliases: </value>
<value>Urls:</value>
</data>
<data name="IsDeleted.Text" xml:space="preserve">
<value>Deleted? </value>
@ -243,13 +243,13 @@
<data name="UseSsl.Text" xml:space="preserve">
<value>SSL Enabled: </value>
</data>
<data name="SmptUsername.Text" xml:space="preserve">
<data name="SmtpUsername.Text" xml:space="preserve">
<value>Username: </value>
</data>
<data name="SmtpPassword.Text" xml:space="preserve">
<value>Password: </value>
</data>
<data name="SmptSender.Text" xml:space="preserve">
<data name="SmtpSender.Text" xml:space="preserve">
<value>Email Sender: </value>
</data>
<data name="EnablePWA.Text" xml:space="preserve">
@ -292,7 +292,7 @@
<value>Browse</value>
</data>
<data name="TenantInformation.Heading" xml:space="preserve">
<value>Tenant Information</value>
<value>Database</value>
</data>
<data name="PWASettings.Heading" xml:space="preserve">
<value>PWA Settings</value>
@ -304,13 +304,13 @@
<value>Connection:</value>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database:</value>
<value>Type:</value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>The connection information for the database</value>
</data>
<data name="Database.HelpText" xml:space="preserve">
<value>The database for the tenant</value>
<value>The type of database</value>
</data>
<data name="DeleteSite.Text" xml:space="preserve">
<value>Delete Site</value>
@ -322,6 +322,63 @@
<value>Default Alias: </value>
</data>
<data name="Aliases.Heading" xml:space="preserve">
<value>Aliases</value>
<value>Urls</value>
</data>
<data name="AliasName" xml:space="preserve">
<value>Url</value>
</data>
<data name="AliasDefault" xml:space="preserve">
<value>Default?</value>
</data>
<data name="Confirm.Alias.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete {0}?</value>
</data>
<data name="HomePage.HelpText" xml:space="preserve">
<value>Select the home page for the site (to be used if there is no page with a path of '/')</value>
</data>
<data name="HomePage.Text" xml:space="preserve">
<value>Home Page:</value>
</data>
<data name="SmtpRelay.HelpText" xml:space="preserve">
<value>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.</value>
</data>
<data name="SmtpRelay.Text" xml:space="preserve">
<value>Relay Configured?</value>
</data>
<data name="SiteMap.HelpText" xml:space="preserve">
<value>The site map url for this site which can be submitted to search engines for indexing</value>
</data>
<data name="SiteMap.Text" xml:space="preserve">
<value>Site Map:</value>
</data>
<data name="Appearance.Heading" xml:space="preserve">
<value>Appearance</value>
</data>
<data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
<data name="HeadContent.Text" xml:space="preserve">
<value>Head Content:</value>
</data>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="SMTPEnabled.HelpText" xml:space="preserve">
<value>Specify if SMTP is enabled for this site</value>
</data>
<data name="SMTPEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
<data name="Version.HelpText" xml:space="preserve">
<value>The site version (for site content migrations)</value>
</data>
<data name="Version.Text" xml:space="preserve">
<value>Version:</value>
</data>
</root>

View File

@ -123,9 +123,6 @@
<data name="SqlServer" xml:space="preserve">
<value>SQL Server</value>
</data>
<data name="Server.Text" xml:space="preserve">
<value>Server: </value>
</data>
<data name="Container.Select" xml:space="preserve">
<value>Select Container</value>
</data>
@ -139,16 +136,16 @@
<value>Default Admin Container</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve">
<value>Enter the alias for the server</value>
<value>The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder).</value>
</data>
<data name="DefaultContainer.HelpText" xml:space="preserve">
<value>Select the default container for the site</value>
</data>
<data name="Tenant.Text" xml:space="preserve">
<value>Tenant: </value>
<value>Database: </value>
</data>
<data name="Aliases.Text" xml:space="preserve">
<value>Aliases: </value>
<value>Urls: </value>
</data>
<data name="DefaultTheme.Text" xml:space="preserve">
<value>Default Theme: </value>
@ -157,10 +154,10 @@
<value>Select Site Template</value>
</data>
<data name="Tenant.Select" xml:space="preserve">
<value>Select Tenant</value>
<value>Select Database</value>
</data>
<data name="Tenant.Add" xml:space="preserve">
<value>Create New Tenant</value>
<value>Create Database</value>
</data>
<data name="Error.Theme.LoadContainers" xml:space="preserve">
<value>Error Loading Containers For Theme</value>
@ -172,19 +169,19 @@
<value>Invalid Host Password</value>
</data>
<data name="Error.TenantName.Exists" xml:space="preserve">
<value>Tenant Name Is Missing Or Already Exists</value>
<value>Database Name Is Missing Or Already Exists</value>
</data>
<data name="Message.SiteName.InUse" xml:space="preserve">
<value>{0} Already Used For Another Site</value>
</data>
<data name="Message.Required.Tenant" xml:space="preserve">
<value>You Must Provide A Tenant, Site Name, Alias, Default Theme/Container, And Site Template</value>
<value>You Must Provide A Database, Site Name, Alias, Default Theme/Container, And Site Template</value>
</data>
<data name="Name.HelpText" xml:space="preserve">
<value>Enter the name of the site</value>
</data>
<data name="DefaultTheme.HelpText" xml:space="preserve">
<value>Select the default theme for the website</value>
<value>Select the default theme for the site</value>
</data>
<data name="AdminContainer.HelpText" xml:space="preserve">
<value>Select the admin container for the site</value>
@ -193,28 +190,13 @@
<value>Select the site template</value>
</data>
<data name="Tenant.HelpText" xml:space="preserve">
<value>Select the tenant for the site</value>
<value>Select the database for the site</value>
</data>
<data name="TenantName.HelpText" xml:space="preserve">
<value>Enter the name for the tenant</value>
<value>Enter the name for the database</value>
</data>
<data name="DatabaseType.HelpText" xml:space="preserve">
<value>Select the database type for the tenant</value>
</data>
<data name="DatabaseServer.HelpText" xml:space="preserve">
<value>Enter the server for the tenant</value>
</data>
<data name="Database.HelpText" xml:space="preserve">
<value>Enter the database for the tenant</value>
</data>
<data name="IntegratedSecurity.HelpText" xml:space="preserve">
<value>Select if you want integrated security or not</value>
</data>
<data name="DatabaseUsername.HelpText" xml:space="preserve">
<value>Enter the username for the integrated security</value>
</data>
<data name="DatabasePassword.HelpText" xml:space="preserve">
<value>Enter the password for the integrated security</value>
<value>Select the database type</value>
</data>
<data name="HostUsername.HelpText" xml:space="preserve">
<value>Enter the username of an existing host user</value>
@ -232,23 +214,14 @@
<value>Site Template: </value>
</data>
<data name="TenantName.Text" xml:space="preserve">
<value>Tenant Name: </value>
<value>Name: </value>
</data>
<data name="DatabaseType.Text" xml:space="preserve">
<value>Database Type: </value>
<value>Type: </value>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database: </value>
</data>
<data name="IntegratedSecurity.Text" xml:space="preserve">
<value>Integrated Security: </value>
</data>
<data name="DatabaseUsername.Text" xml:space="preserve">
<value>Database Username: </value>
</data>
<data name="DatabasePassword.Text" xml:space="preserve">
<value>Database Password: </value>
</data>
<data name="HostUsername.Text" xml:space="preserve">
<value>Host Username:</value>
</data>
@ -270,4 +243,16 @@
<data name="Runtime.Text" xml:space="preserve">
<value>Runtime: </value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>Enter a complete connection string including all parameters and delimiters</value>
</data>
<data name="ConnectionString.Text" xml:space="preserve">
<value>Settings:</value>
</data>
<data name="EnterConnectionParameters" xml:space="preserve">
<value>Enter Connection Parameters</value>
</data>
<data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value>
</data>
</root>

View File

@ -135,4 +135,7 @@
<data name="Browse" xml:space="preserve">
<value>Browse</value>
</data>
<data name="AliasName" xml:space="preserve">
<value>Url</value>
</data>
</root>

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