Compare commits

..

308 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
6eb4ea2a2d Update README.md 2023-03-13 22:41:30 -04:00
225 changed files with 6140 additions and 4582 deletions

View File

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

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

@ -5,15 +5,17 @@
@inject ISiteService SiteService @inject ISiteService SiteService
@inject IUserService UserService @inject IUserService UserService
@inject IDatabaseService DatabaseService @inject IDatabaseService DatabaseService
@inject ISiteTemplateService SiteTemplateService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject IStringLocalizer<Installer> Localizer @inject IStringLocalizer<Installer> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@inject SiteState SiteState
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="mx-auto text-center"> <div class="mx-auto text-center">
<img src="oqtane-black.png" /> <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>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />
@ -61,7 +63,7 @@
</div> </div>
</div> </div>
} }
</div> </div>
</div> </div>
<div class="col text-center"> <div class="col text-center">
<h2>@Localizer["ApplicationAdmin"]</h2><br /> <h2>@Localizer["ApplicationAdmin"]</h2><br />
@ -77,7 +79,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" autocomplete="new-password" /> <input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" 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> </div>
</div> </div>
@ -86,7 +88,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" /> <input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword">@_toggleConfirmPassword</button> <button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
</div> </div>
</div> </div>
</div> </div>
@ -96,6 +98,20 @@
<input type="text" class="form-control" @bind="@_hostEmail" /> <input type="text" class="form-control" @bind="@_hostEmail" />
</div> </div>
</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> </div>
</div> </div>
@ -103,7 +119,13 @@
<div class="row"> <div class="row">
<div class="mx-auto text-center"> <div class="mx-auto text-center">
<button type="button" class="btn btn-success" @onclick="Install">@Localizer["InstallNow"]</button><br /><br /> <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>
<div class="app-progress-indicator" style="@_loadingDisplay"></div> <div class="app-progress-indicator" style="@_loadingDisplay"></div>
</div> </div>
@ -115,29 +137,35 @@
</div> </div>
@code { @code {
private List<Database> _databases; private List<Database> _databases;
private string _databaseName; private string _databaseName;
private Type _databaseConfigType; private Type _databaseConfigType;
private object _databaseConfig; private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; } private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false; private bool _showConnectionString = false;
private string _connectionString = string.Empty; private string _connectionString = string.Empty;
private string _hostUsername = string.Empty; private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty; private string _hostPassword = string.Empty;
private string _passwordType = "password"; private string _passwordType = "password";
private string _confirmPasswordType = "password"; private string _confirmPasswordType = "password";
private string _togglePassword = string.Empty; private string _togglePassword = string.Empty;
private string _toggleConfirmPassword = string.Empty; private string _toggleConfirmPassword = string.Empty;
private string _confirmPassword = string.Empty; private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty; private string _hostEmail = string.Empty;
private bool _register = true; private List<SiteTemplate> _templates;
private string _template = Constants.DefaultSiteTemplate;
private bool _register = true;
private string _message = string.Empty; private string _message = string.Empty;
private string _loadingDisplay = "display: none;"; private string _loadingDisplay = "display: none;";
protected override async Task OnInitializedAsync() 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"]; _toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync(); _databases = await DatabaseService.GetDatabasesAsync();
@ -150,7 +178,9 @@
_databaseName = "LocalDB"; _databaseName = "LocalDB";
} }
LoadDatabaseConfigComponent(); LoadDatabaseConfigComponent();
}
_templates = await SiteTemplateService.GetSiteTemplatesAsync();
}
private void DatabaseChanged(ChangeEventArgs eventArgs) private void DatabaseChanged(ChangeEventArgs eventArgs)
{ {
@ -185,9 +215,9 @@
{ {
if (firstRender) if (firstRender)
{ {
// include JavaScript
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css", "text/css", "sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q==", "anonymous", ""); 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");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", "anonymous", "", "head");
} }
} }
@ -229,7 +259,8 @@
TenantName = TenantNames.Master, TenantName = TenantNames.Master,
IsNewTenant = true, IsNewTenant = true,
SiteName = Constants.DefaultSite, SiteName = Constants.DefaultSite,
Register = _register Register = _register,
SiteTemplate = _template
}; };
var installation = await InstallationService.Install(config); var installation = await InstallationService.Install(config);
@ -291,4 +322,9 @@
_showConnectionString = !_showConnectionString; _showConnectionString = !_showConnectionString;
} }
private void DismissModal()
{
_message = "";
StateHasChanged();
}
} }

View File

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

View File

@ -20,15 +20,6 @@ else
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="translated" HelpText="Specify If You Wish To Select Languages That Have Translations Installed" ResourceKey="Translated">Translated?</Label>
<div class="col-sm-9">
<select id="translated" class="form-select" value="@_translated" @onchange="(e => TranslatedChanged(e))" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label> <Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="_code" class="form-select" @bind="@_code" required> <select id="_code" class="form-select" @bind="@_code" required>
@ -57,13 +48,12 @@ else
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host"> <TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <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">Translation: </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"> <div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" /> <FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div> </div>
</div> </div>
</div> </div>
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
@ -73,7 +63,6 @@ else
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private string _translated = "True";
private string _code = "-"; private string _code = "-";
private string _default = "False"; private string _default = "False";
private List<string> _languages; private List<string> _languages;
@ -91,18 +80,11 @@ else
private async Task LoadCultures() private async Task LoadCultures()
{ {
_cultures = await LocalizationService.GetCulturesAsync(bool.Parse(_translated)); _cultures = await LocalizationService.GetCulturesAsync(false);
_cultures = _cultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !_languages.Contains(c.Name)); _cultures = _cultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !_languages.Contains(c.Name));
_code = "-"; _code = "-";
} }
private async void TranslatedChanged(ChangeEventArgs e)
{
_translated = (string)e.Value;
await LoadCultures();
StateHasChanged();
}
private async Task SaveLanguage() private async Task SaveLanguage()
{ {
validated = true; validated = true;
@ -136,24 +118,11 @@ else
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error); AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
private async Task InstallTranslations()
{
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");
}
}
private async Task SetCultureAsync(string culture) private async Task SetCultureAsync(string culture)
{ {
@ -166,4 +135,9 @@ else
NavigationManager.NavigateTo(NavigationManager.Uri, true); NavigationManager.NavigateTo(NavigationManager.Uri, true);
} }
} }
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
} }

View File

@ -35,23 +35,27 @@ else
{ {
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td> <td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td> <td>
@switch (TranslationAvailable(context.Code, context.Version)) @{
var translation = TranslationAvailable(context.Code, context.Version);
}
@if (translation != null)
{ {
case "install": if (string.IsNullOrEmpty(context.Version))
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code))>@SharedLocalizer["Download"]</button> {
break; <button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code, translation.Version))>@SharedLocalizer["Download"]</button>
case "upgrade": }
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code))>@SharedLocalizer["Upgrade"]</button> else
break; {
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> </td>
} }
</Row> </Row>
</Pager> </Pager>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
} }
@if (_package != null) @if (_package != null)
@ -98,7 +102,6 @@ else
private List<Language> _languages; private List<Language> _languages;
private List<Package> _packages; private List<Package> _packages;
private Package _package; private Package _package;
private bool _install = false;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -134,38 +137,16 @@ else
} }
} }
private string TranslationAvailable(string code, string version) private Package TranslationAvailable(string code, string version)
{ {
if (_packages != null) return _packages?.FirstOrDefault(item => item.PackageId == (Constants.PackageId + "." + code));
{
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault();
if (package != null)
{
// package version needs to match current framework version
if (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0)
{
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 code) private async Task GetPackage(string code, string version)
{ {
try try
{ {
_package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, Constants.Version); _package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, version);
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -181,9 +162,8 @@ else
{ {
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _package.PackageId, _package.Version); await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
AddModuleMessage(Localizer["Success.Language.Download"], MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
_package = null; _package = null;
_install = true;
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -198,19 +178,4 @@ else
_package = null; _package = null;
StateHasChanged(); StateHasChanged();
} }
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
} }

View File

@ -34,7 +34,7 @@
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label> <Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
<div class="input-group"> <div class="input-group">
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" required /> <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> </div>
<div class="form-group mt-2"> <div class="form-group mt-2">

View File

@ -195,7 +195,7 @@ else
if (_owner != "" && _module != "" && _template != "-") if (_owner != "" && _module != "" && _template != "-")
{ {
var template = _templates.FirstOrDefault(item => item.Name == _template); var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _module; _location = template.Location + _owner + ".Module." + _module;
} }
StateHasChanged(); StateHasChanged();

View File

@ -65,13 +65,15 @@
</div> </div>
} }
} }
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel> </TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload"> <TabPanel Name="Upload" ResourceKey="Upload">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <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> <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"> <div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" /> <FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div> </div>
</div> </div>
</div> </div>
@ -111,153 +113,140 @@
</div> </div>
} }
<button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
@code { @code {
private List<Package> _packages; private List<Package> _packages;
private string _price = "free"; private string _price = "free";
private string _search = ""; private string _search = "";
private string _productname = ""; private string _productname = "";
private string _packageid = ""; private string _packageid = "";
private string _packagelicense = ""; private string _packagelicense = "";
private string _packageversion = ""; private string _packageversion = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
await LoadModuleDefinitions(); await LoadModuleDefinitions();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message); await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Package.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.Package.Load"], MessageType.Error);
} }
} }
private async Task LoadModuleDefinitions() private async Task LoadModuleDefinitions()
{ {
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, ""); _packages = await PackageService.GetPackagesAsync("module", _search, _price, "");
if (_packages != null) if (_packages != null)
{ {
foreach (Package package in _packages.ToArray()) foreach (Package package in _packages.ToArray())
{ {
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId)) if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
{ {
_packages.Remove(package); _packages.Remove(package);
} }
} }
} }
} }
private async void PriceChanged(ChangeEventArgs e) private async void PriceChanged(ChangeEventArgs e)
{ {
try try
{ {
_price = (string)e.Value; _price = (string)e.Value;
_search = ""; _search = "";
await LoadModuleDefinitions(); await LoadModuleDefinitions();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error On PriceChanged"); await logger.LogError(ex, "Error On PriceChanged");
} }
} }
private async Task Search() private async Task Search()
{ {
try try
{ {
await LoadModuleDefinitions(); await LoadModuleDefinitions();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error On Search"); await logger.LogError(ex, "Error On Search");
} }
} }
private async Task Reset() private async Task Reset()
{ {
try try
{ {
_search = ""; _search = "";
await LoadModuleDefinitions(); await LoadModuleDefinitions();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error On Reset"); await logger.LogError(ex, "Error On Reset");
} }
} }
private void HideModal() private void HideModal()
{ {
_productname = ""; _productname = "";
_packagelicense = ""; _packagelicense = "";
StateHasChanged(); StateHasChanged();
} }
private async Task GetPackage(string packageid, string version) private async Task GetPackage(string packageid, string version)
{ {
try try
{ {
var package = await PackageService.GetPackageAsync(packageid, version); var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null) if (package != null)
{ {
_productname = package.Name; _productname = package.Name;
_packageid = package.PackageId; _packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License)) if (!string.IsNullOrEmpty(package.License))
{ {
_packagelicense = package.License.Replace("\n", "<br />"); _packagelicense = package.License.Replace("\n", "<br />");
} }
_packageversion = package.Version; _packageversion = package.Version;
} }
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version); await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error); AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
} }
} }
private async Task DownloadPackage() private async Task DownloadPackage()
{ {
try try
{ {
await PackageService.DownloadPackageAsync(_packageid, _packageversion, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(_packageid, _packageversion, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion); await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
AddModuleMessage(Localizer["Success.Module.Download"], MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
_productname = ""; _productname = "";
_packagelicense = ""; _packagelicense = "";
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion); await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error); AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
} }
} }
private async Task InstallModules() private void OnUpload()
{ {
try AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
{ }
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Modules");
}
}
} }

View File

@ -33,7 +33,16 @@
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required /> <input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
</div> </div>
</div> </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>
</form> </form>
<Section Name="Information" ResourceKey="Information"> <Section Name="Information" ResourceKey="Information">
<div class="container"> <div class="container">
@ -131,10 +140,6 @@
</td> </td>
</Row> </Row>
</Pager> </Pager>
@if (_install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
} }
else else
{ {
@ -196,114 +201,124 @@
} }
@code { @code {
private bool _initialized = false; private bool _initialized = false;
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private int _moduleDefinitionId; private int _moduleDefinitionId;
private string _name; private string _name;
private string _description = ""; private string _description = "";
private string _categories; private string _categories;
private string _moduledefinitionname = ""; private string _isenabled;
private string _version; private string _moduledefinitionname = "";
private string _packagename = ""; private string _version;
private string _owner = ""; private string _packagename = "";
private string _url = ""; private string _owner = "";
private string _contact = ""; private string _url = "";
private string _license = ""; private string _contact = "";
private string _runtimes = ""; private string _license = "";
private List<Permission> _permissions = null; private string _runtimes = "";
private string _createdby; private List<Permission> _permissions = null;
private DateTime _createdon; private string _createdby;
private string _modifiedby; private DateTime _createdon;
private DateTime _modifiedon; private string _modifiedby;
private DateTime _modifiedon;
#pragma warning disable 649 #pragma warning disable 649
private PermissionGrid _permissionGrid; private PermissionGrid _permissionGrid;
#pragma warning restore 649 #pragma warning restore 649
private List<Package> _packages; private List<Package> _packages;
private List<Language> _languages; private List<Language> _languages;
private Package _package; private Package _package;
private bool _install = false;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_moduleDefinitionId = Int32.Parse(PageState.QueryString["id"]); _moduleDefinitionId = Int32.Parse(PageState.QueryString["id"]);
var moduleDefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId); var moduleDefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduleDefinition != null) if (moduleDefinition != null)
{ {
_name = moduleDefinition.Name; _name = moduleDefinition.Name;
_description = moduleDefinition.Description; _description = moduleDefinition.Description;
_categories = moduleDefinition.Categories; _categories = moduleDefinition.Categories;
_moduledefinitionname = moduleDefinition.ModuleDefinitionName; _isenabled = moduleDefinition.IsEnabled.ToString();
_version = moduleDefinition.Version; _moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_packagename = moduleDefinition.PackageName; _version = moduleDefinition.Version;
_owner = moduleDefinition.Owner; _packagename = moduleDefinition.PackageName;
_url = moduleDefinition.Url; _owner = moduleDefinition.Owner;
_contact = moduleDefinition.Contact; _url = moduleDefinition.Url;
_license = moduleDefinition.License; _contact = moduleDefinition.Contact;
_runtimes = moduleDefinition.Runtimes; _license = moduleDefinition.License;
_permissions = moduleDefinition.PermissionList; _runtimes = moduleDefinition.Runtimes;
_createdby = moduleDefinition.CreatedBy; _permissions = moduleDefinition.PermissionList;
_createdon = moduleDefinition.CreatedOn; _createdby = moduleDefinition.CreatedBy;
_modifiedby = moduleDefinition.ModifiedBy; _createdon = moduleDefinition.CreatedOn;
_modifiedon = moduleDefinition.ModifiedOn; _modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn;
if (!string.IsNullOrEmpty(_packagename)) if (!string.IsNullOrEmpty(_packagename))
{ {
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename); _packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename); _languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
foreach (var package in _packages) foreach (var package in _packages)
{ {
var code = package.PackageId.Split('.').Last(); var code = package.PackageId.Split('.').Last();
if (!_languages.Any(item => item.Code == code)) if (!_languages.Any(item => item.Code == code))
{ {
_languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true }); _languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true });
} }
} }
_languages = _languages.OrderBy(item => item.Name).ToList(); _languages = _languages.OrderBy(item => item.Name).ToList();
} }
_initialized = true; _initialized = true;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message); await logger.LogError(ex, "Error Loading ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
} }
} }
private async Task SaveModuleDefinition() private async Task SaveModuleDefinition()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
try try
{ {
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId); var moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
if (moduledefinition.Name != _name) 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)
if (moduledefinition.Description != _description) {
{ moduledefinition.Name = _name;
moduledefinition.Description = _description; }
} if (moduledefinition.Description != _description)
if (moduledefinition.Categories != _categories) {
{ moduledefinition.Description = _description;
moduledefinition.Categories = _categories; }
} if (moduledefinition.Categories != _categories)
moduledefinition.PermissionList = _permissionGrid.GetPermissionList(); {
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition); moduledefinition.Categories = _categories;
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition); }
NavigationManager.NavigateTo(NavigateUrl()); 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());
}
else
{
AddModuleMessage(Localizer["Message.DuplicateName"], MessageType.Warning);
}
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message); await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
@ -366,9 +381,8 @@
{ {
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _package.PackageId, _package.Version); await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Translation.Download"], NavigateUrl("admin/system")), MessageType.Success);
_package = null; _package = null;
_install = true;
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -377,19 +391,4 @@
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error); AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
} }
} }
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
} }

View File

@ -43,6 +43,7 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th> <th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@Localizer["InUse"]</th> <th>@Localizer["InUse"]</th>
<th>@SharedLocalizer["Expires"]</th> <th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
@ -57,6 +58,16 @@ else
</td> </td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Version</td> <td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td> <td>
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null) @if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
{ {
@ -149,7 +160,6 @@ else
{ {
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version); await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -44,12 +44,20 @@
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label> <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"> <div class="col-sm-9">
<select id="page" class="form-select" @bind="@_pageId" required> <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.PermissionList)) <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> </select>
</div> </div>
@ -90,37 +98,35 @@
</form> </form>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings"; public override string Title => "Module Settings";
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private List<Theme> _themes; private List<ThemeControl> _containers = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private string _title;
private string _title; private string _containerType;
private string _containerType; private string _allPages = "false";
private string _allPages = "false"; private string _permissionNames = "";
private string _permissionNames = ""; private List<Permission> _permissions = null;
private List<Permission> _permissions = null; private string _pageId;
private string _pageId; private PermissionGrid _permissionGrid;
private PermissionGrid _permissionGrid; private Type _moduleSettingsType;
private Type _moduleSettingsType; private object _moduleSettings;
private object _moduleSettings; private string _moduleSettingsTitle = "Module Settings";
private string _moduleSettingsTitle = "Module Settings"; private RenderFragment ModuleSettingsComponent { get; set; }
private RenderFragment ModuleSettingsComponent { get; set; } private Type _containerSettingsType;
private Type _containerSettingsType; private object _containerSettings;
private object _containerSettings; private RenderFragment ContainerSettingsComponent { get; set; }
private RenderFragment ContainerSettingsComponent { get; set; } private string createdby;
private string createdby; private DateTime createdon;
private DateTime createdon; private string modifiedby;
private string modifiedby; private DateTime modifiedon;
private DateTime modifiedon;
protected override async Task OnInitializedAsync() protected override void OnInitialized()
{ {
_title = ModuleState.Title; _title = ModuleState.Title;
_themes = await ThemeService.GetThemesAsync(); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType; _containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString(); _allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.PermissionList; _permissions = ModuleState.PermissionList;
@ -165,7 +171,7 @@
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error); AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
} }
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType))); var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType)) if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{ {
_containerSettingsType = Type.GetType(theme.ContainerSettingsType); _containerSettingsType = Type.GetType(theme.ContainerSettingsType);

View File

@ -6,12 +6,11 @@
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> {
<TabStrip Refresh="@_refresh"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabPanel Name="Settings" ResourceKey="Settings"> <TabStrip Refresh="@_refresh">
@if (_themeList != null) <TabPanel Name="Settings" ResourceKey="Settings">
{
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label> <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 /> <input id="name" class="form-control" @bind="@_name" required />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
<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"> <div class="row mb-1 align-items-center">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required> <Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option> <div class="col-sm-9">
@foreach (Page page in PageState.Pages) <select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
{ <option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option> @foreach (Page page in PageState.Pages)
}
</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)
{ {
<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> </select>
} </div>
</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"> <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> <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"> <div class="col-sm-9">
@ -85,9 +109,24 @@
<input id="url" class="form-control" @bind="@_url" /> <input id="url" class="form-control" @bind="@_url" />
</div> </div>
</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> </div>
<Section Name="Appearance" ResourceKey="Appearance"> <Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label> <Label Class="col-sm-3" For="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,16 +134,10 @@
<input id="title" class="form-control" @bind="@_title" /> <input id="title" class="form-control" @bind="@_title" />
</div> </div>
</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"> <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> <Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required> <select id="theme" class="form-select" @bind="@_themetype" required>
@foreach (var theme in _themes) @foreach (var theme in _themes)
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
@ -116,7 +149,6 @@
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label> <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"> <div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required> <select id="container" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="@container.TypeName">@container.Name</option>
@ -124,90 +156,113 @@
</select> </select>
</div> </div>
</div> </div>
</div>
</Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
<div class="container">
<div class="row mb-1 align-items-center"> <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"> <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> </div>
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required> <textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div> </div>
</div> </div>
</div> </div>
</Section> </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> </TabPanel>
} <TabPanel Name="Permissions" ResourceKey="Permissions">
</TabStrip> <div class="container">
<br /> <div class="row mb-1 align-items-center">
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> </div>
</form> </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 { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private List<Theme> _themeList; private bool _initialized = false;
private List<ThemeControl> _themes = new List<ThemeControl>(); private ElementReference form;
private List<ThemeControl> _containers = new List<ThemeControl>(); private bool validated = false;
private string _name; private List<ThemeControl> _themes = new List<ThemeControl>();
private string _title; private List<ThemeControl> _containers = new List<ThemeControl>();
private string _meta; private int _pageId;
private string _path = string.Empty; private string _name;
private string _parentid = "-1"; private string _parentid = "-1";
private string _insert = ">>"; private string _insert = ">>";
private List<Page> _children; private List<Page> _children;
private int _childid = -1; private int _childid = -1;
private string _isnavigation = "True"; private string _isnavigation = "True";
private string _isclickable = "True"; private string _isclickable = "True";
private string _url; private string _path = string.Empty;
private string _ispersonalizable = "False"; private string _url;
private string _themetype = string.Empty; private string _ispersonalizable = "False";
private string _containertype = string.Empty; private string _title;
private string _icon = string.Empty; private string _icon = string.Empty;
private string _permissions = null; private string _themetype = string.Empty;
private PermissionGrid _permissionGrid; private string _containertype = string.Empty;
private Type _themeSettingsType; private string _headcontent;
private object _themeSettings; private string _bodycontent;
private RenderFragment ThemeSettingsComponent { get; set; } private string _permissions = null;
private bool _refresh = false; private PermissionGrid _permissionGrid;
private ElementReference form; private Type _themeSettingsType;
private bool validated = false; private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page _parent = null;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_themeList = await ThemeService.GetThemesAsync(); if (PageState.QueryString.ContainsKey("id"))
_themes = ThemeService.GetThemeControls(_themeList); {
_themetype = PageState.Site.DefaultThemeType; _pageId = Int32.Parse(PageState.QueryString["id"]);
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _parent = await PageService.GetPageAsync(_pageId);
_containertype = PageState.Site.DefaultContainerType; if (_parent != null)
_children = PageState.Pages.Where(item => item.ParentId == null).ToList(); {
ThemeSettings(); _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, _themetype);
_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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message); await logger.LogError(ex, "Error Loading Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error); AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
} }
} }
@ -246,27 +301,10 @@
} }
} }
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 ThemeSettings() private void ThemeSettings()
{ {
_themeSettingsType = null; _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)) if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{ {
_themeSettingsType = Type.GetType(theme.ThemeSettingsType); _themeSettingsType = Type.GetType(theme.ThemeSettingsType);
@ -292,12 +330,11 @@
Page page = null; Page page = null;
try try
{ {
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-") if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
{ {
page = new Page(); page = new Page();
page.SiteId = PageState.Page.SiteId; page.SiteId = PageState.Page.SiteId;
page.Name = _name; page.Name = _name;
page.Title = _title;
if (string.IsNullOrEmpty(_path)) if (string.IsNullOrEmpty(_path))
{ {
@ -365,34 +402,42 @@
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation)); page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable)); page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url; page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty; 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) if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{ {
page.ThemeType = string.Empty; page.ThemeType = string.Empty;
} }
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty; page.DefaultContainerType = _containertype;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType) if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{ {
page.DefaultContainerType = string.Empty; page.DefaultContainerType = string.Empty;
} }
page.Icon = (_icon == null ? string.Empty : _icon);
// page content
page.HeadContent = _headcontent;
page.BodyContent = _bodycontent;
// permissions
page.PermissionList = _permissionGrid.GetPermissionList(); page.PermissionList = _permissionGrid.GetPermissionList();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
page.Meta = _meta;
page = await PageService.AddPageAsync(page); page = await PageService.AddPageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId); await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
await logger.LogInformation("Page Added {Page}", page); 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 else
{ {
NavigationManager.NavigateTo(NavigateUrl(page.Path)); NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
} }
} }
else else
@ -415,9 +460,9 @@
private void Cancel() 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 else
{ {

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @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" /> <ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />

View File

@ -64,6 +64,12 @@
</select> </select>
</div> </div>
</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"> <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> <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"> <div class="col-sm-9">
@ -97,6 +103,7 @@
private string _maxlength = "0"; private string _maxlength = "0";
private string _defaultvalue = string.Empty; private string _defaultvalue = string.Empty;
private string _options = string.Empty; private string _options = string.Empty;
private string _validation = string.Empty;
private string _isrequired = "False"; private string _isrequired = "False";
private string _isprivate = "False"; private string _isprivate = "False";
private string createdby; private string createdby;
@ -126,6 +133,7 @@
_maxlength = profile.MaxLength.ToString(); _maxlength = profile.MaxLength.ToString();
_defaultvalue = profile.DefaultValue; _defaultvalue = profile.DefaultValue;
_options = profile.Options; _options = profile.Options;
_validation = profile.Validation;
_isrequired = profile.IsRequired.ToString(); _isrequired = profile.IsRequired.ToString();
_isprivate = profile.IsPrivate.ToString(); _isprivate = profile.IsPrivate.ToString();
createdby = profile.CreatedBy; createdby = profile.CreatedBy;
@ -169,6 +177,7 @@
profile.MaxLength = int.Parse(_maxlength); profile.MaxLength = int.Parse(_maxlength);
profile.DefaultValue = _defaultvalue; profile.DefaultValue = _defaultvalue;
profile.Options = _options; profile.Options = _options;
profile.Validation = _validation;
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired)); profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate)); profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
if (_profileid != -1) if (_profileid != -1)

View File

@ -76,8 +76,8 @@ else
} }
@code { @code {
private List<Page> _pages; private List<Page> _pages;
private List<Module> _modules; private List<Module> _modules;
private int _pagePage = 1; private int _pagePage = 1;
private int _pageModule = 1; private int _pageModule = 1;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -184,13 +184,6 @@ else
try try
{ {
await PageModuleService.DeletePageModuleAsync(module.PageModuleId); await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// check if there are any remaining module instances in the site
if (!_modules.Exists (item => item.ModuleId == module.ModuleId && item.PageModuleId != module.PageModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
await logger.LogInformation("Module Permanently Deleted {Module}", module); await logger.LogInformation("Module Permanently Deleted {Module}", module);
await Load(); await Load();
StateHasChanged(); StateHasChanged();
@ -210,16 +203,7 @@ else
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList()) foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
{ {
await PageModuleService.DeletePageModuleAsync(module.PageModuleId); await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// DeletePageModuleAsync does not update _modules so remove it.
_modules.Remove(module);
// check if there are any remaining module instances in the site
if (!_modules.Exists(item => item.ModuleId == module.ModuleId && item.PageModuleId != module.PageModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
} }
await logger.LogInformation("Modules Permanently Deleted"); await logger.LogInformation("Modules Permanently Deleted");
await Load(); await Load();
ModuleInstance.HideProgressIndicator(); ModuleInstance.HideProgressIndicator();

View File

@ -4,6 +4,7 @@
@inject IUserService UserService @inject IUserService UserService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@inject ISettingService SettingService
@if (PageState.Site.AllowRegistration) @if (PageState.Site.AllowRegistration)
{ {
@ -15,7 +16,7 @@
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" /> <ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
</Authorized> </Authorized>
<NotAuthorized> <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> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -29,7 +30,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required /> <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> </div>
</div> </div>
@ -38,7 +39,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required /> <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> </div>
</div> </div>
@ -68,21 +69,54 @@ else
} }
@code { @code {
private string _username = string.Empty; private string _username = string.Empty;
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private string _password = string.Empty; private string _password = string.Empty;
private string _passwordtype = "password"; private string _passwordtype = "password";
private string _togglepassword = string.Empty; private string _togglepassword = string.Empty;
private string _confirm = string.Empty; private string _confirm = string.Empty;
private string _email = string.Empty; private string _email = string.Empty;
private string _displayname = 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"]; _togglepassword = SharedLocalizer["ShowPassword"];
} }
private async Task Register() private async Task Register()

View File

@ -18,7 +18,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required /> <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> </div>
</div> </div>
@ -27,7 +27,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required /> <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> </div>
</div> </div>

View File

@ -22,55 +22,6 @@
<input id="name" class="form-control" @bind="@_name" maxlength="200" required /> <input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" 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>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label> <Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -101,7 +52,80 @@
<input id="sitemap" class="form-control" @bind="@_sitemap" required disabled /> <input id="sitemap" class="form-control" @bind="@_sitemap" required disabled />
</div> </div>
</div> </div>
</div> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The site version (for site content migrations)" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" required disabled />
</div>
</div>
</div>
<br />
<Section Name="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</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="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
<div class="col-sm-9">
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<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">
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
</div>
</div>
</div>
</Section>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings"> <Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -124,9 +148,9 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label> <Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="enabledSSl" class="form-select" @bind="@_smtpssl" > <select id="smtpssl" class="form-select" @bind="@_smtpssl" >
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
@ -143,7 +167,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" /> <input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword">@_togglesmtppassword</button> <button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
</div> </div>
</div> </div>
</div> </div>
@ -162,7 +186,16 @@
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SMTPEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="smtpenabled" class="form-select" @bind="@_smtpenabled">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label> <Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retention" /> <input id="retention" class="form-control" @bind="@_retention" />
@ -306,222 +339,234 @@
} }
@code { @code {
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private bool _initialized = false; private bool _initialized = false;
private List<Theme> _themeList; private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private string _name = string.Empty;
private string _name = string.Empty; private string _homepageid = "-";
private List<Alias> _aliases; private string _isdeleted;
private int _aliasid = -1; private string _sitemap = "";
private string _aliasname; private string _version = "";
private string _defaultalias; private int _logofileid = -1;
private string _runtime = ""; private FileManager _logofilemanager;
private string _prerender = ""; private int _faviconfileid = -1;
private int _logofileid = -1; private FileManager _faviconfilemanager;
private FileManager _logofilemanager; private string _themetype = "";
private int _faviconfileid = -1; private string _containertype = "";
private FileManager _faviconfilemanager; private string _admincontainertype = "";
private string _themetype = "-"; private string _headcontent = string.Empty;
private string _containertype = "-"; private string _bodycontent = string.Empty;
private string _admincontainertype = "-"; private string _smtphost = string.Empty;
private string _homepageid = "-"; private string _smtpport = string.Empty;
private string _sitemap = ""; private string _smtpssl = "False";
private string _smtphost = string.Empty; private string _smtpusername = string.Empty;
private string _smtpport = string.Empty; private string _smtppassword = string.Empty;
private string _smtpssl = "False"; private string _smtppasswordtype = "password";
private string _smtpusername = string.Empty; private string _togglesmtppassword = string.Empty;
private string _smtppassword = string.Empty; private string _smtpsender = string.Empty;
private string _smtppasswordtype = "password"; private string _smtprelay = "False";
private string _togglesmtppassword = string.Empty; private string _smtpenabled = "True";
private string _smtpsender = string.Empty; private string _retention = string.Empty;
private string _smtprelay = "False"; private string _pwaisenabled;
private string _retention = string.Empty; private int _pwaappiconfileid = -1;
private string _pwaisenabled; private FileManager _pwaappiconfilemanager;
private int _pwaappiconfileid = -1; private int _pwasplashiconfileid = -1;
private FileManager _pwaappiconfilemanager; private FileManager _pwasplashiconfilemanager;
private int _pwasplashiconfileid = -1; private List<Alias> _aliases;
private FileManager _pwasplashiconfilemanager; private int _aliasid = -1;
private string _tenant = string.Empty; private string _aliasname;
private string _database = string.Empty; private string _defaultalias;
private string _connectionstring = string.Empty; private string _runtime = "";
private string _createdby; private string _prerender = "";
private DateTime _createdon; private string _tenant = string.Empty;
private string _modifiedby; private string _database = string.Empty;
private DateTime _modifiedon; private string _connectionstring = string.Empty;
private string _deletedby; private string _createdby;
private DateTime? _deletedon; private DateTime _createdon;
private string _isdeleted; private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_themeList = await ThemeService.GetThemesAsync(); Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null)
if (site != null) {
{ _name = site.Name;
_name = site.Name; if (site.HomePageId != null)
_runtime = site.Runtime; {
_prerender = site.RenderMode.Replace(_runtime, ""); _homepageid = site.HomePageId.Value.ToString();
_isdeleted = site.IsDeleted.ToString(); }
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml"; _isdeleted = site.IsDeleted.ToString();
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml";
_version = site.Version;
await GetAliases(); // appearance
if (site.LogoFileId != null)
{
_logofileid = site.LogoFileId.Value;
}
if (site.LogoFileId != null) if (site.FaviconFileId != null)
{ {
_logofileid = site.LogoFileId.Value; _faviconfileid = site.FaviconFileId.Value;
} }
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
if (site.FaviconFileId != null) // page content
{ _headcontent = site.HeadContent;
_faviconfileid = site.FaviconFileId.Value; _bodycontent = site.BodyContent;
}
_themes = ThemeService.GetThemeControls(_themeList); // PWA
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme; _pwaisenabled = site.PwaIsEnabled.ToString();
_containers = ThemeService.GetContainerControls(_themeList, _themetype); if (site.PwaAppIconFileId != null)
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer; {
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer; _pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
if (site.HomePageId != null) // SMTP
{ var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_homepageid = site.HomePageId.Value.ToString(); _smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
} _smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
_pwaisenabled = site.PwaIsEnabled.ToString(); // aliases
if (site.PwaAppIconFileId != null) await GetAliases();
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); // hosting model
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty); _runtime = site.Runtime;
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty); _prerender = site.RenderMode.Replace(_runtime, "");
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) // database
{ if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
var tenants = await TenantService.GetTenantsAsync(); {
var _databases = await DatabaseService.GetDatabasesAsync(); var tenants = await TenantService.GetTenantsAsync();
var tenant = tenants.Find(item => item.TenantId == site.TenantId); var _databases = await DatabaseService.GetDatabasesAsync();
if (tenant != null) var tenant = tenants.Find(item => item.TenantId == site.TenantId);
{ if (tenant != null)
_tenant = tenant.Name; {
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name; _tenant = tenant.Name;
_connectionstring = tenant.DBConnectionString; _database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
} _connectionstring = tenant.DBConnectionString;
} }
}
_createdby = site.CreatedBy; // audit
_createdon = site.CreatedOn; _createdby = site.CreatedBy;
_modifiedby = site.ModifiedBy; _createdon = site.CreatedOn;
_modifiedon = site.ModifiedOn; _modifiedby = site.ModifiedBy;
_deletedby = site.DeletedBy; _modifiedon = site.ModifiedOn;
_deletedon = site.DeletedOn; _deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
_initialized = true; _initialized = true;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(ex.Message, MessageType.Error);
} }
} }
private async void ThemeChanged(ChangeEventArgs e) private async void ThemeChanged(ChangeEventArgs e)
{ {
try try
{ {
_themetype = (string)e.Value; _themetype = (string)e.Value;
if (_themetype != "-") _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
{ _containertype = _containers.First().TypeName;
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _admincontainertype = Constants.DefaultAdminContainer;
} StateHasChanged();
else }
{ catch (Exception ex)
_containers = new List<ThemeControl>(); {
} await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
_containertype = "-"; AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
_admincontainertype = Constants.DefaultAdminContainer; }
StateHasChanged(); }
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
}
}
private async Task SaveSite() private async Task SaveSite()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
try try
{ {
if (_name != string.Empty && _themetype != "-" && _containertype != "-") if (_name != string.Empty && _themetype != "-" && _containertype != "-")
{ {
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId); var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null) if (site != null)
{ {
bool refresh = false; bool refresh = false;
bool reload = false; bool reload = false;
site.Name = _name; site.Name = _name;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
{ site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
reload = true; // needs to be reloaded on server
}
}
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.LogoFileId = null; // appearance
var logofileid = _logofilemanager.GetFileId(); site.LogoFileId = null;
if (logofileid != -1) var logofileid = _logofilemanager.GetFileId();
{ if (logofileid != -1)
site.LogoFileId = logofileid; {
} site.LogoFileId = logofileid;
int? faviconFieldId = _faviconfilemanager.GetFileId(); }
if (faviconFieldId == -1) faviconFieldId = null; int? faviconFieldId = _faviconfilemanager.GetFileId();
if (site.FaviconFileId != faviconFieldId) if (faviconFieldId == -1) faviconFieldId = null;
{ if (site.FaviconFileId != faviconFieldId)
site.FaviconFileId = faviconFieldId; {
reload = true; // needs to be reloaded on server site.FaviconFileId = faviconFieldId;
} reload = true; // needs to be reloaded on server
if (site.DefaultThemeType != _themetype) }
{ if (site.DefaultThemeType != _themetype)
site.DefaultThemeType = _themetype; {
refresh = true; // needs to be refreshed on client site.DefaultThemeType = _themetype;
} refresh = true; // needs to be refreshed on client
if (site.DefaultContainerType != _containertype) }
{ if (site.DefaultContainerType != _containertype)
site.DefaultContainerType = _containertype; {
refresh = true; // needs to be refreshed on client site.DefaultContainerType = _containertype;
} refresh = true; // needs to be refreshed on client
site.AdminContainerType = _admincontainertype; }
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null); site.AdminContainerType = _admincontainertype;
// page content
if (site.HeadContent != _headcontent)
{
site.HeadContent = _headcontent;
reload = true;
}
if (site.BodyContent != _bodycontent)
{
site.BodyContent = _bodycontent;
reload = true;
}
// PWA
if (site.PwaIsEnabled.ToString() != _pwaisenabled) if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{ {
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled); site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
@ -542,8 +587,20 @@
reload = true; // needs to be reloaded on server reload = true; // needs to be reloaded on server
} }
// hosting model
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
reload = true; // needs to be reloaded on server
}
}
site = await SiteService.UpdateSiteAsync(site); site = await SiteService.UpdateSiteAsync(site);
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
@ -552,7 +609,8 @@
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true); settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true); settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Settings Saved {Site}", site); await logger.LogInformation("Site Settings Saved {Site}", site);
@ -564,8 +622,8 @@
else else
{ {
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success); AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await interop.ScrollTo(0, 0, "smooth"); await ScrollToPageTop();
} }
} }
} }
else else
@ -633,9 +691,8 @@
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly.")); await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info); AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
var interop = new Interop(JSRuntime); await ScrollToPageTop();
await interop.ScrollTo(0, 0, "smooth"); }
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Testing SMTP Configuration"); await logger.LogError(ex, "Error Testing SMTP Configuration");

View File

@ -65,13 +65,15 @@
</div> </div>
} }
} }
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel> </TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload"> <TabPanel Name="Upload" ResourceKey="Upload">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <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> <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"> <div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" /> <FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div> </div>
</div> </div>
</div> </div>
@ -111,13 +113,8 @@
</div> </div>
} }
<button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
@code { @code {
private List<Package> _packages; private List<Package> _packages;
private string _price = "free"; private string _price = "free";
@ -236,7 +233,7 @@
{ {
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version); 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 = ""; _productname = "";
_license = ""; _license = "";
StateHasChanged(); StateHasChanged();
@ -248,16 +245,8 @@
} }
} }
private async Task InstallThemes() private void OnUpload()
{ {
try AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
{
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");
}
} }
} }

View File

@ -142,7 +142,7 @@
if (_owner != "" && _theme != "" && _template != "-") if (_owner != "" && _theme != "" && _template != "-")
{ {
var template = _templates.FirstOrDefault(item => item.Name == _template); var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _theme; _location = template.Location + _owner + ".Theme." + _theme;
} }
StateHasChanged(); 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,11 +23,12 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th> <th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@SharedLocalizer["Expires"]</th> <th>@SharedLocalizer["Expires"]</th>
<th>&nbsp;</th> <th>&nbsp;</th>
</Header> </Header>
<Row> <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> <td>
@if (context.AssemblyName != Constants.ClientId) @if (context.AssemblyName != Constants.ClientId)
{ {
@ -36,6 +37,16 @@ else
</td> </td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Version</td> <td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td> <td>
@((MarkupString)PurchaseLink(context.PackageName)) @((MarkupString)PurchaseLink(context.PackageName))
</td> </td>
@ -116,7 +127,6 @@ else
{ {
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version); await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version);
await ThemeService.InstallThemesAsync();
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -1,97 +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="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>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _themeName = "";
private string _name;
private string _version;
private string _packagename;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeName = WebUtility.UrlDecode(PageState.QueryString["name"]);
var themes = await ThemeService.GetThemesAsync();
var theme = themes.FirstOrDefault(item => item.ThemeName == _themeName);
if (theme != null)
{
_name = theme.Name;
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
}
}
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 @namespace Oqtane.Modules.Admin.UserProfile
@using System.Text.RegularExpressions;
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@ -34,7 +35,7 @@ else
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" /> <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> </div>
</div> </div>
@ -43,7 +44,7 @@ else
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" /> <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> </div>
</div> </div>
@ -227,140 +228,140 @@ else
<br /><br /> <br /><br />
@code { @code {
private string username = string.Empty; private string username = string.Empty;
private string _password = string.Empty; private string _password = string.Empty;
private string _passwordtype = "password"; private string _passwordtype = "password";
private string _togglepassword = string.Empty; private string _togglepassword = string.Empty;
private string confirm = string.Empty; private string confirm = string.Empty;
private bool allowtwofactor = false; private bool allowtwofactor = false;
private string twofactor = "False"; private string twofactor = "False";
private string email = string.Empty; private string email = string.Empty;
private string displayname = string.Empty; private string displayname = string.Empty;
private FileManager filemanager; private FileManager filemanager;
private int folderid = -1; private int folderid = -1;
private int photofileid = -1; private int photofileid = -1;
private File photo = null; private File photo = null;
private List<Profile> profiles; private List<Profile> profiles;
private Dictionary<string, string> settings; private Dictionary<string, string> settings;
private string category = string.Empty; private string category = string.Empty;
private string filter = "to"; private string filter = "to";
private List<Notification> notifications; private List<Notification> notifications;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
try try
{ {
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"])) if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
{ {
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true"); allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
} }
if (PageState.User != null) if (PageState.User != null)
{ {
username = PageState.User.Username; username = PageState.User.Username;
twofactor = PageState.User.TwoFactorRequired.ToString(); twofactor = PageState.User.TwoFactorRequired.ToString();
email = PageState.User.Email; email = PageState.User.Email;
displayname = PageState.User.DisplayName; displayname = PageState.User.DisplayName;
// get user folder // get user folder
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
if (folder != null) if (folder != null)
{ {
folderid = folder.FolderId; folderid = folder.FolderId;
} }
if (PageState.User.PhotoFileId != null) if (PageState.User.PhotoFileId != null)
{ {
photofileid = PageState.User.PhotoFileId.Value; photofileid = PageState.User.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid); photo = await FileService.GetFileAsync(photofileid);
} }
else else
{ {
photofileid = -1; photofileid = -1;
photo = null; photo = null;
} }
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
await LoadNotificationsAsync(); await LoadNotificationsAsync();
} }
else else
{ {
AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning); AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message); await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
} }
} }
private async Task LoadNotificationsAsync() private async Task LoadNotificationsAsync()
{ {
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId); notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList(); notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
{ {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue); string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]")) if (value.Contains("]"))
{ {
value = value.Substring(value.IndexOf("]") + 1); value = value.Substring(value.IndexOf("]") + 1);
} }
return value; return value;
} }
private async Task Save() private async Task Save()
{ {
try try
{ {
if (username != string.Empty && email != string.Empty && ValidateProfiles()) if (username != string.Empty && email != string.Empty && ValidateProfiles())
{ {
if (_password == confirm) if (_password == confirm)
{ {
var user = PageState.User; var user = PageState.User;
user.Username = username; user.Username = username;
user.Password = _password; user.Password = _password;
user.TwoFactorRequired = bool.Parse(twofactor); user.TwoFactorRequired = bool.Parse(twofactor);
user.Email = email; user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname); user.DisplayName = (displayname == string.Empty ? username : displayname);
user.PhotoFileId = filemanager.GetFileId(); user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1) if (user.PhotoFileId == -1)
{ {
user.PhotoFileId = null; user.PhotoFileId = null;
} }
if (user.PhotoFileId != null) if (user.PhotoFileId != null)
{ {
photofileid = user.PhotoFileId.Value; photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid); photo = await FileService.GetFileAsync(photofileid);
} }
else else
{ {
photofileid = -1; photofileid = -1;
photo = null; photo = null;
} }
user = await UserService.UpdateUserAsync(user); user = await UserService.UpdateUserAsync(user);
if (user != null) if (user != null)
{ {
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved"); await logger.LogInformation("User Profile Saved");
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success); AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged(); StateHasChanged();
} }
else else
{ {
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning); AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);
@ -370,6 +371,8 @@ else
{ {
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
} }
await ScrollToPageTop();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -389,10 +392,15 @@ else
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) 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; 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; return valid;

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Users @namespace Oqtane.Modules.Admin.Users
@using System.Text.RegularExpressions;
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@ -23,7 +24,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required /> <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> </div>
</div> </div>
@ -32,7 +33,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" required /> <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> </div>
</div> </div>
@ -95,8 +96,8 @@
@code { @code {
private string username = string.Empty; private string username = string.Empty;
private string _password = string.Empty; private string _password = string.Empty;
private string _passwordtype = "password"; private string _passwordtype = "password";
private string _togglepassword = string.Empty; private string _togglepassword = string.Empty;
private string confirm = string.Empty; private string confirm = string.Empty;
private string email = string.Empty; private string email = string.Empty;
private string displayname = string.Empty; private string displayname = string.Empty;
@ -121,15 +122,15 @@
} }
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
{ {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue); string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]")) if (value.Contains("]"))
{ {
value = value.Substring(value.IndexOf("]") + 1); value = value.Substring(value.IndexOf("]") + 1);
} }
return value; return value;
} }
private async Task SaveUser() private async Task SaveUser()
{ {
@ -195,9 +196,17 @@
{ {
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); 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; return valid;

View File

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

View File

@ -87,299 +87,306 @@
} }
@code { @code {
private bool _initialized = false; private bool _initialized = false;
private List<Folder> _folders; private List<Folder> _folders;
private List<File> _files = new List<File>(); private List<File> _files = new List<File>();
private string _fileinputid = string.Empty; private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty; private string _progressinfoid = string.Empty;
private string _progressbarid = string.Empty; private string _progressbarid = string.Empty;
private string _filter = "*"; private string _filter = "*";
private bool _haseditpermission = false; private bool _haseditpermission = false;
private string _image = string.Empty; private string _image = string.Empty;
private File _file = null; private File _file = null;
private string _guid; private string _guid;
private string _message = string.Empty; private string _message = string.Empty;
private MessageType _messagetype; private MessageType _messagetype;
[Parameter] [Parameter]
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
[Parameter] [Parameter]
public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid
[Parameter] [Parameter]
public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path
[Parameter] [Parameter]
public int FileId { get; set; } = -1; // optional - for selecting a specific file by default public int FileId { get; set; } = -1; // optional - for selecting a specific file by default
[Parameter] [Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif" public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
[Parameter] [Parameter]
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
[Parameter] [Parameter]
public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true
[Parameter] [Parameter]
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter] [Parameter]
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
[Parameter] [Parameter]
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
[Parameter] [Parameter]
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter] [Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
[Parameter] [Parameter]
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
[Parameter] [Parameter]
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted 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 OnInitializedAsync()
{ {
// packages folder is a framework folder for uploading installable nuget packages // packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder) if (Folder == Constants.PackagesFolder)
{ {
ShowFiles = false; ShowFiles = false;
ShowFolders = false; ShowFolders = false;
Filter = "nupkg"; Filter = "nupkg";
ShowSuccess = true; ShowSuccess = true;
} }
if (!ShowFiles) if (!ShowFiles)
{ {
ShowImage = false; ShowImage = false;
} }
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId); _folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder) if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
{ {
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder); Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
if (folder != null) if (folder != null)
{ {
FolderId = folder.FolderId; FolderId = folder.FolderId;
} }
else else
{ {
FolderId = -1; FolderId = -1;
_message = "Folder Path " + Folder + "Does Not Exist"; _message = "Folder Path " + Folder + "Does Not Exist";
_messagetype = MessageType.Error; _messagetype = MessageType.Error;
} }
} }
if (FileId != -1) if (FileId != -1)
{ {
File file = await FileService.GetFileAsync(FileId); File file = await FileService.GetFileAsync(FileId);
if (file != null) if (file != null)
{ {
FolderId = file.FolderId; FolderId = file.FolderId;
await OnSelect.InvokeAsync(FileId); await OnSelect.InvokeAsync(FileId);
} }
else else
{ {
FileId = -1; // file does not exist FileId = -1; // file does not exist
_message = "FileId " + FileId.ToString() + "Does Not Exist"; _message = "FileId " + FileId.ToString() + "Does Not Exist";
_messagetype = MessageType.Error; _messagetype = MessageType.Error;
} }
} }
await SetImage(); await SetImage();
if (!string.IsNullOrEmpty(Filter)) if (!string.IsNullOrEmpty(Filter))
{ {
_filter = "." + Filter.Replace(",", ",."); _filter = "." + Filter.Replace(",", ",.");
} }
await GetFiles(); await GetFiles();
// create unique id for component // create unique id for component
_guid = Guid.NewGuid().ToString("N"); _guid = Guid.NewGuid().ToString("N");
_fileinputid = "FileInput_" + _guid; _fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid; _progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid; _progressbarid = "ProgressBar_" + _guid;
_initialized = true; _initialized = true;
} }
private async Task GetFiles() private async Task GetFiles()
{ {
_haseditpermission = false; _haseditpermission = false;
if (Folder == Constants.PackagesFolder) if (Folder == Constants.PackagesFolder)
{ {
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host); _haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = new List<File>(); _files = new List<File>();
} }
else else
{ {
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId); Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null) if (folder != null)
{ {
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList); _haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
_files = await FileService.GetFilesAsync(FolderId); _files = await FileService.GetFilesAsync(FolderId);
} }
else else
{ {
_haseditpermission = false; _haseditpermission = false;
_files = new List<File>(); _files = new List<File>();
} }
} if (_filter != "*")
if (_filter != "*") {
{ List<File> filtered = new List<File>();
List<File> filtered = new List<File>(); foreach (File file in _files)
foreach (File file in _files) {
{ if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1) {
{ filtered.Add(file);
filtered.Add(file); }
} }
} _files = filtered;
_files = filtered; }
} }
} }
private async Task FolderChanged(ChangeEventArgs e) private async Task FolderChanged(ChangeEventArgs e)
{ {
_message = string.Empty; _message = string.Empty;
try try
{ {
FolderId = int.Parse((string)e.Value); FolderId = int.Parse((string)e.Value);
await GetFiles(); await GetFiles();
FileId = -1; FileId = -1;
_file = null; _file = null;
_image = string.Empty; _image = string.Empty;
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message); await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error.File.Load"]; _message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error; _messagetype = MessageType.Error;
} }
} }
private async Task FileChanged(ChangeEventArgs e) private async Task FileChanged(ChangeEventArgs e)
{ {
_message = string.Empty; _message = string.Empty;
FileId = int.Parse((string)e.Value); FileId = int.Parse((string)e.Value);
if (FileId != -1) if (FileId != -1)
{ {
await OnSelect.InvokeAsync(FileId); await OnSelect.InvokeAsync(FileId);
} }
await SetImage(); await SetImage();
StateHasChanged(); StateHasChanged();
} }
private async Task SetImage() private async Task SetImage()
{ {
_image = string.Empty; _image = string.Empty;
_file = null; _file = null;
if (FileId != -1) if (FileId != -1)
{ {
_file = await FileService.GetFileAsync(FileId); _file = await FileService.GetFileAsync(FileId);
if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0) if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
{ {
var maxwidth = 200; var maxwidth = 200;
var maxheight = 200; var maxheight = 200;
var ratioX = (double)maxwidth / (double)_file.ImageWidth; var ratioX = (double)maxwidth / (double)_file.ImageWidth;
var ratioY = (double)maxheight / (double)_file.ImageHeight; var ratioY = (double)maxheight / (double)_file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY; var ratio = ratioX < ratioY ? ratioX : ratioY;
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name + _image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() + "\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />"; "\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
} }
} }
} }
private async Task UploadFiles() private async Task UploadFiles()
{ {
_message = string.Empty; _message = string.Empty;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
var uploads = await interop.GetFiles(_fileinputid); var uploads = await interop.GetFiles(_fileinputid);
if (uploads.Length > 0) if (uploads.Length > 0)
{ {
string restricted = ""; string restricted = "";
foreach (var upload in uploads) foreach (var upload in uploads)
{ {
var extension = (upload.LastIndexOf(".") != -1) ? upload.Substring(upload.LastIndexOf(".") + 1) : ""; var extension = (upload.LastIndexOf(".") != -1) ? upload.Substring(upload.LastIndexOf(".") + 1) : "";
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower())) if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{ {
restricted += (restricted == "" ? "" : ",") + extension; restricted += (restricted == "" ? "" : ",") + extension;
} }
} }
if (restricted == "") if (restricted == "")
{ {
try try
{ {
// upload the files // upload the files
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload"); var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString(); var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken); await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken);
// uploading is asynchronous so we need to wait for the uploads to complete // uploading is asynchronous so we need to wait for the uploads to complete
// note that this will only wait a maximum of 15 seconds which may not be long enough for very large file uploads // note that this will only wait a maximum of 15 seconds which may not be long enough for very large file uploads
bool success = false; bool success = false;
int attempts = 0; int attempts = 0;
while (attempts < 5 && !success) while (attempts < 5 && !success)
{ {
attempts += 1; attempts += 1;
Thread.Sleep(1000 * attempts); // progressive retry Thread.Sleep(1000 * attempts); // progressive retry
success = true; success = true;
List<File> files = await FileService.GetFilesAsync(folder); List<File> files = await FileService.GetFilesAsync(folder);
if (files.Count > 0) if (files.Count > 0)
{ {
foreach (string upload in uploads) foreach (string upload in uploads)
{ {
if (!files.Exists(item => item.Name == upload)) if (!files.Exists(item => item.Name == upload))
{ {
success = false; success = false;
} }
} }
} }
} }
// reset progress indicators // reset progress indicators
await interop.SetElementAttribute(_guid + "ProgressInfo", "style", "display: none;"); await interop.SetElementAttribute(_guid + "ProgressInfo", "style", "display: none;");
await interop.SetElementAttribute(_guid + "ProgressBar", "style", "display: none;"); await interop.SetElementAttribute(_guid + "ProgressBar", "style", "display: none;");
if (success) if (success)
{ {
await logger.LogInformation("File Upload Succeeded {Files}", uploads); await logger.LogInformation("File Upload Succeeded {Files}", uploads);
if (ShowSuccess) if (ShowSuccess)
{ {
_message = Localizer["Success.File.Upload"]; _message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success; _messagetype = MessageType.Success;
} }
} }
else else
{ {
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads); await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
_message = Localizer["Error.File.Upload"]; _message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error; _messagetype = MessageType.Error;
} }
// set FileId to first file in upload collection if (Folder == Constants.PackagesFolder)
await GetFiles(); {
var file = _files.Where(item => item.Name == uploads[0]).FirstOrDefault(); await OnUpload.InvokeAsync(-1);
if (file != null) }
{ else
FileId = file.FileId; {
await SetImage(); // set FileId to first file in upload collection
await OnUpload.InvokeAsync(FileId); await GetFiles();
} var file = _files.Where(item => item.Name == uploads[0]).FirstOrDefault();
StateHasChanged(); if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
StateHasChanged();
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -53,7 +53,6 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" } new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" }
}; };

View File

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

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText namespace Oqtane.Modules.HtmlText
{ {
@ -13,7 +15,11 @@ namespace Oqtane.Modules.HtmlText
Version = "1.0.1", Version = "1.0.1",
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server", ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
ReleaseVersions = "1.0.0,1.0.1", 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

@ -9,6 +9,7 @@ using Oqtane.UI;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using System.Linq; using System.Linq;
using Oqtane.Themes;
namespace Oqtane.Modules namespace Oqtane.Modules
{ {
@ -70,17 +71,33 @@ namespace Oqtane.Modules
{ {
if (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))
{
if (PageState.Page.Resources != null)
{
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 interop = new Interop(JSRuntime);
var scripts = new List<object>(); var scripts = new List<object>();
var inline = 0; var inline = 0;
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) foreach (Resource resource in resources)
{ {
if (!string.IsNullOrEmpty(resource.Url)) if (!string.IsNullOrEmpty(resource.Url))
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + 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 }); scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
} }
else else
{ {
@ -104,6 +121,7 @@ namespace Oqtane.Modules
} }
// url methods // url methods
public string NavigateUrl() public string NavigateUrl()
{ {
return NavigateUrl(PageState.Page.Path); return NavigateUrl(PageState.Page.Path);
@ -273,6 +291,33 @@ namespace Oqtane.Modules
SiteState.Properties.ModuleVisibility = obj; 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 // logging methods
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args) public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
{ {

View File

@ -1,19 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion> <RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>3.4.1</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
<Description>Modular Application Framework for Blazor and MAUI</Description> <Description>CMS and Application Framework for Blazor and .NET MAUI</Description>
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>
@ -22,13 +22,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.3" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.3" /> <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.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>
<ItemGroup> <ItemGroup>

View File

@ -16,7 +16,6 @@ using Microsoft.JSInterop;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
namespace Oqtane.Client namespace Oqtane.Client
@ -198,23 +197,37 @@ namespace Oqtane.Client
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services) private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
{ {
// dynamically register module scoped services // dynamically register module scoped services
var implementationTypes = assembly.GetInterfaces<IService>(); try
foreach (var implementationType in implementationTypes)
{ {
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}")); if (implementationType.AssemblyQualifiedName != null)
services.AddScoped(serviceType ?? implementationType, implementationType); {
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) private static void RegisterClientStartups(Assembly assembly, IServiceCollection services)
{ {
var startUps = assembly.GetInstances<IClientStartup>(); try
foreach (var startup in startUps)
{ {
startup.ConfigureServices(services); var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(services);
}
}
catch
{
// could not interrogate assembly - likely missing dependencies
} }
} }

View File

@ -124,7 +124,7 @@
<value>Database:</value> <value>Database:</value>
</data> </data>
<data name="ApplicationAdmin" xml:space="preserve"> <data name="ApplicationAdmin" xml:space="preserve">
<value>Application Administrator</value> <value>Application Administration</value>
</data> </data>
<data name="InstallNow" xml:space="preserve"> <data name="InstallNow" xml:space="preserve">
<value>Install Now</value> <value>Install Now</value>
@ -180,4 +180,7 @@
<data name="EnterConnectionString" xml:space="preserve"> <data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value> <value>Enter Connection String</value>
</data> </data>
<data name="Template" xml:space="preserve">
<value>Select a site template</value>
</data>
</root> </root>

View File

@ -120,29 +120,23 @@
<data name="Error.Language.Add" xml:space="preserve"> <data name="Error.Language.Add" xml:space="preserve">
<value>Error Adding Language</value> <value>Error Adding Language</value>
</data> </data>
<data name="Translated.HelpText" xml:space="preserve">
<value>Specify If You Wish To Select Languages That Have Translations Installed</value>
</data>
<data name="Name.HelpText" xml:space="preserve"> <data name="Name.HelpText" xml:space="preserve">
<value>Name Of The Langauage</value> <value>Name Of The Langauage</value>
</data> </data>
<data name="IsDefault.HelpText" xml:space="preserve"> <data name="IsDefault.HelpText" xml:space="preserve">
<value>Indicates Whether Or Not This Language Is The Default For The Site</value> <value>Indicates Whether Or Not This Language Is The Default For The Site</value>
</data> </data>
<data name="Translated.Text" xml:space="preserve">
<value>Translated?</value>
</data>
<data name="Name.Text" xml:space="preserve"> <data name="Name.Text" xml:space="preserve">
<value>Name:</value> <value>Name:</value>
</data> </data>
<data name="IsDefault.Text" xml:space="preserve"> <data name="IsDefault.Text" xml:space="preserve">
<value>Default?</value> <value>Default?</value>
</data> </data>
<data name="Success.Language.Install" xml:space="preserve"> <data name="Success.Language.Download" 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> <value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; To Complete The Installation.</value>
</data> </data>
<data name="LanguageUpload.HelpText" xml:space="preserve"> <data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translation packages. Once they are uploaded click Install to complete the installation.</value> <value>Upload one or more translation packages.</value>
</data> </data>
<data name="LanguageUpload.Text" xml:space="preserve"> <data name="LanguageUpload.Text" xml:space="preserve">
<value>Translation</value> <value>Translation</value>

View File

@ -132,12 +132,12 @@
<data name="DeleteLanguage.Header" xml:space="preserve"> <data name="DeleteLanguage.Header" xml:space="preserve">
<value>Delete Language</value> <value>Delete Language</value>
</data> </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"> <data name="Error.Language.Download" xml:space="preserve">
<value>Error Downloading Translation</value> <value>Error Downloading Translation</value>
</data> </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"> <data name="Default" xml:space="preserve">
<value>Default</value> <value>Default</value>
</data> </data>

View File

@ -123,17 +123,14 @@
<data name="Error.Package.Load" xml:space="preserve"> <data name="Error.Package.Load" xml:space="preserve">
<value>Error Loading Packages</value> <value>Error Loading Packages</value>
</data> </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"> <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>
<data name="Error.Module.Download" xml:space="preserve"> <data name="Error.Module.Download" xml:space="preserve">
<value>Error Downloading Module</value> <value>Error Downloading Module</value>
</data> </data>
<data name="Module.HelpText" xml:space="preserve"> <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>
<data name="Search.NoResults" xml:space="preserve"> <data name="Search.NoResults" xml:space="preserve">
<value>No Modules Match The Criteria Provided Or Package Service Is Disabled</value> <value>No Modules Match The Criteria Provided Or Package Service Is Disabled</value>

View File

@ -211,12 +211,18 @@
<value>No Translations Exist For This Module Or Package Service Is Disabled</value> <value>No Translations Exist For This Module Or Package Service Is Disabled</value>
</data> </data>
<data name="Success.Translation.Download" xml:space="preserve"> <data name="Success.Translation.Download" xml:space="preserve">
<value>Translation Downloaded Successfully. Click Install To Complete Installation.</value> <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="Success.Translation.Install" xml:space="preserve">
<value>Translation Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data> </data>
<data name="Translations.Heading" xml:space="preserve"> <data name="Translations.Heading" xml:space="preserve">
<value>Translations</value> <value>Translations</value>
</data> </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> </root>

View File

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

View File

@ -150,8 +150,8 @@
<data name="Container.Select" xml:space="preserve"> <data name="Container.Select" xml:space="preserve">
<value>Select Container</value> <value>Select Container</value>
</data> </data>
<data name="Error.Page.Initialize" xml:space="preserve"> <data name="Error.Page.Load" xml:space="preserve">
<value>Error Initializing Page</value> <value>Error Loading Page</value>
</data> </data>
<data name="Error.ChildPage.Load" xml:space="preserve"> <data name="Error.ChildPage.Load" xml:space="preserve">
<value>Error Loading Child Pages For Parent</value> <value>Error Loading Child Pages For Parent</value>
@ -228,13 +228,22 @@
<data name="Appearance.Name" xml:space="preserve"> <data name="Appearance.Name" xml:space="preserve">
<value>Appearance</value> <value>Appearance</value>
</data> </data>
<data name="Meta.HelpText" xml:space="preserve"> <data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value> <value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data> </data>
<data name="Meta.Text" xml:space="preserve"> <data name="HeadContent.Text" xml:space="preserve">
<value>Meta:</value> <value>Head Content:</value>
</data> </data>
<data name="Message.Page.Reserved" xml:space="preserve"> <data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value> <value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data> </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>
</root> </root>

View File

@ -264,13 +264,22 @@
<data name="Clickable.Text" xml:space="preserve"> <data name="Clickable.Text" xml:space="preserve">
<value>Clickable?</value> <value>Clickable?</value>
</data> </data>
<data name="Meta.HelpText" xml:space="preserve"> <data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value> <value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data> </data>
<data name="Meta.Text" xml:space="preserve"> <data name="HeadContent.Text" xml:space="preserve">
<value>Meta:</value> <value>Head Content:</value>
</data> </data>
<data name="Message.Page.Reserved" xml:space="preserve"> <data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value> <value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data> </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>
</root> </root>

View File

@ -183,4 +183,10 @@
<data name="Private.Text" xml:space="preserve"> <data name="Private.Text" xml:space="preserve">
<value>Private? </value> <value>Private? </value>
</data> </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> </root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -166,7 +166,7 @@
<value>Email:</value> <value>Email:</value>
</data> </data>
<data name="Password.HelpText" xml:space="preserve"> <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>
<data name="Password.Text" xml:space="preserve"> <data name="Password.Text" xml:space="preserve">
<value>Password:</value> <value>Password:</value>
@ -177,4 +177,19 @@
<data name="Username.Text" xml:space="preserve"> <data name="Username.Text" xml:space="preserve">
<value>Username:</value> <value>Username:</value>
</data> </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> </root>

View File

@ -175,7 +175,7 @@
<value>Specify a logo for the site</value> <value>Specify a logo for the site</value>
</data> </data>
<data name="FavoriteIcon.HelpText" xml:space="preserve"> <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>
<data name="DefaultTheme.HelpText" xml:space="preserve"> <data name="DefaultTheme.HelpText" xml:space="preserve">
<value>Select the sites default theme</value> <value>Select the sites default theme</value>
@ -351,4 +351,34 @@
<data name="SiteMap.Text" xml:space="preserve"> <data name="SiteMap.Text" xml:space="preserve">
<value>Site Map:</value> <value>Site Map:</value>
</data> </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> </root>

View File

@ -123,17 +123,14 @@
<data name="Theme.Text" xml:space="preserve"> <data name="Theme.Text" xml:space="preserve">
<value>Theme: </value> <value>Theme: </value>
</data> </data>
<data name="Success.Theme.Install" xml:space="preserve">
<value>Theme Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Success.Theme.Download" xml:space="preserve"> <data name="Success.Theme.Download" xml:space="preserve">
<value>Theme Downloaded Successfully. Click Install To Complete Installation.</value> <value>Theme Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data> </data>
<data name="Error.Theme.Download" xml:space="preserve"> <data name="Error.Theme.Download" xml:space="preserve">
<value>Error Downloading Theme</value> <value>Error Downloading Theme</value>
</data> </data>
<data name="Theme.HelpText" xml:space="preserve"> <data name="Theme.HelpText" xml:space="preserve">
<value>Upload one or more theme packages. Once they are uploaded click Install to complete the installation.</value> <value>Upload one or more theme packages.</value>
</data> </data>
<data name="Search.NoResults" xml:space="preserve"> <data name="Search.NoResults" xml:space="preserve">
<value>No Themes Match The Criteria Provided Or Package Service Is Disabled</value> <value>No Themes Match The Criteria Provided Or Package Service Is Disabled</value>

View File

@ -168,4 +168,13 @@
<data name="PackageName.Text" xml:space="preserve"> <data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value> <value>Package Name:</value>
</data> </data>
<data name="Information.Heading" xml:space="preserve">
<value>Information</value>
</data>
<data name="IsEnabled.HelpText" xml:space="preserve">
<value>Is theme enabled for this site?</value>
</data>
<data name="IsEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
</root> </root>

View File

@ -144,4 +144,7 @@
<data name="ViewTheme.Text" xml:space="preserve"> <data name="ViewTheme.Text" xml:space="preserve">
<value>View</value> <value>View</value>
</data> </data>
<data name="Enabled" xml:space="preserve">
<value>Enabled?</value>
</data>
</root> </root>

View File

@ -340,6 +340,48 @@
<value>Visitor Management</value> <value>Visitor Management</value>
</data> </data>
<data name="Oqtane.Marketplace" xml:space="preserve"> <data name="Oqtane.Marketplace" xml:space="preserve">
<value>Please note that the third party extensions displayed above have been registered in the &lt;a href="https://www.oqtane.net" target="_new"&gt;Oqtane Marketplace&lt;/a&gt; which enables them to be seamlessly downloaded and installed into the framework.</value> <value>Please note that third party extensions are registered in the &lt;a href="https://www.oqtane.net" target="_new"&gt;Oqtane Marketplace&lt;/a&gt; which enables them to be seamlessly downloaded and installed into the framework.</value>
</data>
<data name="Home" xml:space="preserve">
<value>Home</value>
</data>
<data name="Close" xml:space="preserve">
<value>Close</value>
</data>
<data name="OK" xml:space="preserve">
<value>OK</value>
</data>
<data name="Apply" xml:space="preserve">
<value>Apply</value>
</data>
<data name="Select" xml:space="preserve">
<value>Select</value>
</data>
<data name="Next" xml:space="preserve">
<value>Next</value>
</data>
<data name="Previous" xml:space="preserve">
<value>Previous</value>
</data>
<data name="Submit" xml:space="preserve">
<value>Submit</value>
</data>
<data name="Refresh" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="Back" xml:space="preserve">
<value>Back</value>
</data>
<data name="Return" xml:space="preserve">
<value>Return</value>
</data>
<data name="New" xml:space="preserve">
<value>New</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Confirm" xml:space="preserve">
<value>Confirm</value>
</data> </data>
</root> </root>

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -29,9 +30,9 @@ namespace Oqtane.Services
public async Task<List<File>> GetFilesAsync(int siteId, string folderPath) public async Task<List<File>> GetFilesAsync(int siteId, string folderPath)
{ {
if (!(folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar))) if (!(string.IsNullOrEmpty(folderPath) || folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar)))
{ {
folderPath = Utilities.PathCombine(folderPath, System.IO.Path.DirectorySeparatorChar.ToString()); folderPath = Utilities.UrlCombine(folderPath) + "/";
} }
var path = WebUtility.UrlEncode(folderPath); var path = WebUtility.UrlEncode(folderPath);

View File

@ -33,13 +33,6 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task UpdateModuleDefinitionAsync(ModuleDefinition moduleDefinition); Task UpdateModuleDefinitionAsync(ModuleDefinition moduleDefinition);
/// <summary>
/// Installs all module definitions located in //TODO: 2dm where?
/// </summary>
/// <returns></returns>
Task InstallModuleDefinitionsAsync();
/// <summary> /// <summary>
/// Deletes a module definition /// Deletes a module definition
/// </summary> /// </summary>

View File

@ -23,14 +23,6 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<Page> GetPageAsync(int pageId); Task<Page> GetPageAsync(int pageId);
/// <summary>
/// Returns a specific page personalized for the given user
/// </summary>
/// <param name="pageId"></param>
/// <param name="userId"></param>
/// <returns></returns>
Task<Page> GetPageAsync(int pageId, int userId);
/// <summary> /// <summary>
/// Returns a specific page by its defined path /// Returns a specific page by its defined path
/// </summary> /// </summary>

View File

@ -16,6 +16,22 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<List<Theme>> GetThemesAsync(); Task<List<Theme>> GetThemesAsync();
/// <summary>
/// Returns a specific thenme
/// </summary>
/// <param name="themeId"></param>
/// <param name="siteId"></param>
/// <returns></returns>
Task<Theme> GetThemeAsync(int themeId, int siteId);
/// <summary>
/// Returns a theme <see cref="ThemeControl"/>s containing a specific theme control type
/// </summary>
/// <param name="themes"></param>
/// <param name="themeControlType"></param>
/// <returns></returns>
Theme GetTheme(List<Theme> themes, string themeControlType);
/// <summary> /// <summary>
/// Returns a list of <see cref="ThemeControl"/>s from the given themes /// Returns a list of <see cref="ThemeControl"/>s from the given themes
/// </summary> /// </summary>
@ -24,26 +40,27 @@ namespace Oqtane.Services
List<ThemeControl> GetThemeControls(List<Theme> themes); List<ThemeControl> GetThemeControls(List<Theme> themes);
/// <summary> /// <summary>
/// Returns a list of layouts (<see cref="ThemeControl"/>) from the given themes with a matching theme name /// Returns a list of <see cref="ThemeControl"/>s for a theme containing a specific theme control type
/// </summary> /// </summary>
/// <param name="themes"></param> /// <param name="themes"></param>
/// <param name="themeName"></param> /// <param name="themeControlType"></param>
/// <returns></returns> /// <returns></returns>
List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName); List<ThemeControl> GetThemeControls(List<Theme> themes, string themeControlType);
/// <summary> /// <summary>
/// Returns a list of containers (<see cref="ThemeControl"/>) from the given themes with a matching theme name /// Returns a list of containers (<see cref="ThemeControl"/>) for a theme containing a specific theme control type
/// </summary> /// </summary>
/// <param name="themes"></param> /// <param name="themes"></param>
/// <param name="themeName"></param> /// <param name="themeControlType"></param>
/// <returns></returns> /// <returns></returns>
List<ThemeControl> GetContainerControls(List<Theme> themes, string themeName); List<ThemeControl> GetContainerControls(List<Theme> themes, string themeControlType);
/// <summary> /// <summary>
/// Installs all themes located in //TODO: 2dm where? /// Updates a existing theem
/// </summary> /// </summary>
/// <param name="theme"></param>
/// <returns></returns> /// <returns></returns>
Task InstallThemesAsync(); Task UpdateThemeAsync(Theme theme);
/// <summary> /// <summary>
/// Deletes a theme /// Deletes a theme
@ -64,5 +81,14 @@ namespace Oqtane.Services
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<List<Template>> GetThemeTemplatesAsync(); Task<List<Template>> GetThemeTemplatesAsync();
/// <summary>
/// Returns a list of layouts (<see cref="ThemeControl"/>) from the given themes with a matching theme name
/// </summary>
/// <param name="themes"></param>
/// <param name="themeName"></param>
/// <returns></returns>
List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName);
} }
} }

View File

@ -34,11 +34,6 @@ namespace Oqtane.Services
await PutJsonAsync($"{Apiurl}/{moduleDefinition.ModuleDefinitionId}", moduleDefinition); await PutJsonAsync($"{Apiurl}/{moduleDefinition.ModuleDefinitionId}", moduleDefinition);
} }
public async Task InstallModuleDefinitionsAsync()
{
await GetJsonAsync<List<string>>($"{Apiurl}/install");
}
public async Task DeleteModuleDefinitionAsync(int moduleDefinitionId, int siteId) public async Task DeleteModuleDefinitionAsync(int moduleDefinitionId, int siteId)
{ {
await DeleteAsync($"{Apiurl}/{moduleDefinitionId}?siteid={siteId}"); await DeleteAsync($"{Apiurl}/{moduleDefinitionId}?siteid={siteId}");

View File

@ -18,11 +18,7 @@ namespace Oqtane.Services
public async Task<List<Module>> GetModulesAsync(int siteId) public async Task<List<Module>> GetModulesAsync(int siteId)
{ {
List<Module> modules = await GetJsonAsync<List<Module>>($"{Apiurl}?siteid={siteId}"); return await GetJsonAsync<List<Module>>($"{Apiurl}?siteid={siteId}");
modules = modules
.OrderBy(item => item.Order)
.ToList();
return modules;
} }
public async Task<Module> GetModuleAsync(int moduleId) public async Task<Module> GetModuleAsync(int moduleId)

View File

@ -25,11 +25,6 @@ namespace Oqtane.Services
return await GetJsonAsync<Page>($"{Apiurl}/{pageId}"); return await GetJsonAsync<Page>($"{Apiurl}/{pageId}");
} }
public async Task<Page> GetPageAsync(int pageId, int userId)
{
return await GetJsonAsync<Page>($"{Apiurl}/{pageId}?userid={userId}");
}
public async Task<Page> GetPageAsync(string path, int siteId) public async Task<Page> GetPageAsync(string path, int siteId)
{ {
try try

View File

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Services namespace Oqtane.Services
{ {
@ -20,27 +21,35 @@ namespace Oqtane.Services
List<Theme> themes = await GetJsonAsync<List<Theme>>(ApiUrl); List<Theme> themes = await GetJsonAsync<List<Theme>>(ApiUrl);
return themes.OrderBy(item => item.Name).ToList(); return themes.OrderBy(item => item.Name).ToList();
} }
public async Task<Theme> GetThemeAsync(int themeId, int siteId)
{
return await GetJsonAsync<Theme>($"{ApiUrl}/{themeId}?siteid={siteId}");
}
public Theme GetTheme(List<Theme> themes, string themeControlType)
{
return themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeControlType));
}
public List<ThemeControl> GetThemeControls(List<Theme> themes) public List<ThemeControl> GetThemeControls(List<Theme> themes)
{ {
return themes.SelectMany(item => item.Themes).ToList(); return themes.SelectMany(item => item.Themes).OrderBy(item => item.Name).ToList();
} }
//[Obsolete("This method is deprecated.", false)] public List<ThemeControl> GetThemeControls(List<Theme> themes, string themeControlType)
public List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName)
{ {
return null; return GetTheme(themes, themeControlType)?.Themes.OrderBy(item => item.Name).ToList();
} }
public List<ThemeControl> GetContainerControls(List<Theme> themes, string themeName)
public List<ThemeControl> GetContainerControls(List<Theme> themes, string themeControlType)
{ {
return themes.Where(item => Utilities.GetTypeName(themeName).StartsWith(Utilities.GetTypeName(item.ThemeName))) return GetTheme(themes, themeControlType)?.Containers.OrderBy(item => item.Name).ToList();
.SelectMany(item => item.Containers).ToList();
} }
public async Task InstallThemesAsync() public async Task UpdateThemeAsync(Theme theme)
{ {
await GetJsonAsync<List<string>>($"{ApiUrl}/install"); await PutJsonAsync($"{ApiUrl}/{theme.ThemeId}", theme);
} }
public async Task DeleteThemeAsync(string themeName) public async Task DeleteThemeAsync(string themeName)
@ -58,5 +67,11 @@ namespace Oqtane.Services
List<Template> templates = await GetJsonAsync<List<Template>>($"{ApiUrl}/templates"); List<Template> templates = await GetJsonAsync<List<Template>>($"{ApiUrl}/templates");
return templates; return templates;
} }
//[Obsolete("This method is deprecated.", false)]
public List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName)
{
return null;
}
} }
} }

View File

@ -31,9 +31,9 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
// obtained from https://cdnjs.com/libraries // obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css", Integrity = "sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q==", CrossOrigin = "anonymous" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css", Integrity = "sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", Integrity = "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", CrossOrigin = "anonymous" } new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", Integrity = "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", CrossOrigin = "anonymous" }
}; };
} }

View File

@ -2,12 +2,9 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ContainerBase @inherits ContainerBase
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@inject SiteState SiteState
<span class="app-moduletitle"> <span class="app-moduletitle">
<a id="@ModuleState.PageModuleId.ToString()"> @((MarkupString)title)
@((MarkupString)title)
</a>
</span> </span>
@code { @code {

View File

@ -1,3 +1,4 @@
@using System.Net
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@ -55,7 +56,7 @@
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />
} }
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) @if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
@ -64,7 +65,10 @@
</div> </div>
<div class="row d-flex mb-2"> <div class="row d-flex mb-2">
<div class="col d-flex justify-content-between"> <div class="col d-flex justify-content-between">
<button type="button" class="btn btn-secondary col me-1" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button> @if (PageState.Page.UserId == null)
{
<button type="button" class="btn btn-secondary col me-1" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button>
}
<button type="button" class="btn btn-secondary col" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button> <button type="button" class="btn btn-secondary col" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button>
<button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button> <button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
</div> </div>
@ -144,7 +148,7 @@
} }
@foreach (var moduledefinition in _moduleDefinitions) @foreach (var moduledefinition in _moduleDefinitions)
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.PermissionList)) if (moduledefinition.IsEnabled && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.PermissionList))
{ {
if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString())) if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString()))
{ {
@ -223,270 +227,274 @@
} }
@code{ @code{
private bool _canViewAdminDashboard = false; private bool _canViewAdminDashboard = false;
private bool _showEditMode = false; private bool _showEditMode = false;
private bool _deleteConfirmation = false; private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>(); private List<string> _categories = new List<string>();
private List<ModuleDefinition> _allModuleDefinitions; private List<ModuleDefinition> _allModuleDefinitions;
private List<ModuleDefinition> _moduleDefinitions; private List<ModuleDefinition> _moduleDefinitions;
private List<Page> _pages = new List<Page>(); private List<Page> _pages = new List<Page>();
private List<Module> _modules = new List<Module>(); private List<Module> _modules = new List<Module>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private string _category = "Common"; private string _category = "Common";
protected string PageId { get; private set; } = "-"; protected string PageId { get; private set; } = "-";
protected string ModuleId { get; private set; } = "-"; protected string ModuleId { get; private set; } = "-";
protected string ModuleType { get; private set; } = "new"; protected string ModuleType { get; private set; } = "new";
protected string ModuleDefinitionName { get; private set; } = "-"; protected string ModuleDefinitionName { get; private set; } = "-";
protected string Category protected string Category
{ {
get => _category; get => _category;
private set private set
{ {
if (_category != value) if (_category != value)
{ {
_category = value; _category = value;
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList(); _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
ModuleDefinitionName = "-"; ModuleDefinitionName = "-";
Message = ""; Message = "";
StateHasChanged(); StateHasChanged();
_ = UpdateSettingsAsync(); _ = UpdateSettingsAsync();
} }
} }
} }
protected string Pane protected string Pane
{ {
get => _pane; get => _pane;
private set private set
{ {
if (_pane != value) if (_pane != value)
{ {
_pane = value; _pane = value;
_ = UpdateSettingsAsync(); _ = UpdateSettingsAsync();
} }
} }
} }
protected string Title { get; private set; } = ""; protected string Title { get; private set; } = "";
protected string ContainerType { get; private set; } = ""; protected string ContainerType { get; private set; } = "";
protected string Visibility { get; private set; } = "view"; protected string Visibility { get; private set; } = "view";
protected string Message { get; private set; } = ""; protected string Message { get; private set; } = "";
[Parameter] [Parameter]
public string ButtonClass { get; set; } = "btn-outline-secondary"; public string ButtonClass { get; set; } = "btn-outline-secondary";
[Parameter] [Parameter]
public string ContainerClass { get; set; } = "offcanvas offcanvas-end"; public string ContainerClass { get; set; } = "offcanvas offcanvas-end";
[Parameter] [Parameter]
public string HeaderClass { get; set; } = "offcanvas-header"; public string HeaderClass { get; set; } = "offcanvas-header";
[Parameter] [Parameter]
public string BodyClass { get; set; } = "offcanvas-body overflow-auto"; public string BodyClass { get; set; } = "offcanvas-body overflow-auto";
[Parameter] [Parameter]
public bool ShowLanguageSwitcher { get; set; } = true; public bool ShowLanguageSwitcher { get; set; } = true;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
_canViewAdminDashboard = CanViewAdminDashboard(); _canViewAdminDashboard = CanViewAdminDashboard();
_showEditMode = false; _showEditMode = false;
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
_showEditMode = true; _showEditMode = true;
_pages?.Clear(); _pages?.Clear();
foreach (Page p in PageState.Pages) foreach (Page p in PageState.Pages)
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
_pages.Add(p); _pages.Add(p);
} }
} }
await LoadSettingsAsync(); await LoadSettingsAsync();
var themes = await ThemeService.GetThemesAsync(); var themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType); _containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType);
ContainerType = PageState.Site.DefaultContainerType; ContainerType = PageState.Site.DefaultContainerType;
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList(); _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList(); _categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
} }
else else
{ {
foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId)) foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, module.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, module.PermissionList))
{ {
_showEditMode = true; _showEditMode = true;
break; break;
} }
} }
} }
} }
private bool CanViewAdminDashboard() private bool CanViewAdminDashboard()
{ {
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin"); var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null) if (admin != null)
{ {
foreach (var page in PageState.Pages.Where(item => item.ParentId == admin?.PageId)) foreach (var page in PageState.Pages.Where(item => item.ParentId == admin?.PageId))
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{ {
return true; return true;
} }
} }
} }
return false; return false;
} }
private void CategoryChanged(ChangeEventArgs e) private void CategoryChanged(ChangeEventArgs e)
{ {
Category = (string)e.Value; Category = (string)e.Value;
} }
private void ModuleChanged(ChangeEventArgs e) private void ModuleChanged(ChangeEventArgs e)
{ {
ModuleDefinitionName = (string)e.Value; ModuleDefinitionName = (string)e.Value;
if (ModuleDefinitionName != "-") if (ModuleDefinitionName != "-")
{ {
var moduleDefinition = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName); var moduleDefinition = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName);
Message = "<div class=\"alert alert-info mt-2 text-center\" role=\"alert\">" + moduleDefinition.Description + "</div>"; Message = "<div class=\"alert alert-info mt-2 text-center\" role=\"alert\">" + moduleDefinition.Description + "</div>";
} }
else else
{ {
Message = ""; Message = "";
} }
StateHasChanged(); StateHasChanged();
} }
private void PageChanged(ChangeEventArgs e) private void PageChanged(ChangeEventArgs e)
{ {
PageId = (string)e.Value; PageId = (string)e.Value;
if (PageId != "-") if (PageId != "-")
{ {
_modules = PageState.Modules _modules = PageState.Modules
.Where(module => module.PageId == int.Parse(PageId) && .Where(module => module.PageId == int.Parse(PageId) &&
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList)) UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
.ToList(); .ToList();
} }
ModuleId = "-"; ModuleId = "-";
StateHasChanged(); StateHasChanged();
} }
private async Task AddModule() private async Task AddModule()
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
if ((ModuleType == "new" && ModuleDefinitionName != "-") || (ModuleType != "new" && ModuleId != "-")) if ((ModuleType == "new" && ModuleDefinitionName != "-") || (ModuleType != "new" && ModuleId != "-"))
{ {
if (ModuleType == "new") if (ModuleType == "new")
{ {
Module module = new Module(); Module module = new Module();
module.SiteId = PageState.Site.SiteId; module.SiteId = PageState.Site.SiteId;
module.PageId = PageState.Page.PageId; module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = ModuleDefinitionName; module.ModuleDefinitionName = ModuleDefinitionName;
module.AllPages = false; module.AllPages = false;
var permissions = new List<Permission>(); var permissions = new List<Permission>();
if (Visibility == "view") if (Visibility == "view")
{ {
// set module view permissions to page view permissions // set module view permissions to page view permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View); permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View);
} }
else else
{ {
// set module view permissions to page edit permissions // set module view permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit); permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit);
} }
// set module edit permissions to page edit permissions // set module edit permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit); permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit);
module.PermissionList = permissions; module.PermissionList = permissions;
module = await ModuleService.AddModuleAsync(module); module = await ModuleService.AddModuleAsync(module);
ModuleId = module.ModuleId.ToString(); ModuleId = module.ModuleId.ToString();
} }
var pageModule = new PageModule var pageModule = new PageModule
{ {
PageId = PageState.Page.PageId, PageId = PageState.Page.PageId,
ModuleId = int.Parse(ModuleId), ModuleId = int.Parse(ModuleId),
Title = Title Title = Title
}; };
if (pageModule.Title == "") if (pageModule.Title == "")
{ {
if (ModuleType == "new") if (ModuleType == "new")
{ {
pageModule.Title = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName)?.Name; pageModule.Title = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName)?.Name;
} }
else else
{ {
pageModule.Title = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(ModuleId))?.Title; pageModule.Title = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(ModuleId))?.Title;
} }
} }
pageModule.Pane = Pane; pageModule.Pane = Pane;
pageModule.Order = int.MaxValue; pageModule.Order = int.MaxValue;
pageModule.ContainerType = ContainerType; pageModule.ContainerType = ContainerType;
if (pageModule.ContainerType == PageState.Site.DefaultContainerType) if (pageModule.ContainerType == PageState.Site.DefaultContainerType)
{ {
pageModule.ContainerType = ""; pageModule.ContainerType = "";
} }
await PageModuleService.AddPageModuleAsync(pageModule); await PageModuleService.AddPageModuleAsync(pageModule);
await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane);
Message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>"; Message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
Title = ""; Title = "";
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else else
{ {
Message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>"; Message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>";
} }
} }
else else
{ {
Message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>"; Message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>";
} }
} }
private List<Permission> SetPermissions(List<Permission> permissions, int siteId, string modulePermission, string pagePermission) private List<Permission> SetPermissions(List<Permission> permissions, int siteId, string modulePermission, string pagePermission)
{ {
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission)) foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))
{ {
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized }); permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
} }
return permissions; return permissions;
} }
private async Task ToggleEditMode(bool EditMode) private async Task ToggleEditMode(bool EditMode)
{ {
if (_showEditMode) if (_showEditMode)
{ {
if (EditMode) if (EditMode)
{ {
PageState.EditMode = false; PageState.EditMode = false;
} }
else else
{ {
PageState.EditMode = true; PageState.EditMode = true;
} }
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false"))); // preserve other querystring parameters
if (PageState.QueryString.ContainsKey("edit")) PageState.QueryString.Remove("edit");
PageState.QueryString.Add("edit", PageState.EditMode.ToString().ToLower());
var url = PageState.Route.AbsolutePath + Utilities.CreateQueryString(PageState.QueryString);
NavigationManager.NavigateTo(url);
} }
else else
{ {
if (PageState.Page.IsPersonalizable && PageState.User != null) if (PageState.Page.IsPersonalizable && PageState.User != null)
{ {
await PageService.AddPageAsync(PageState.Page.PageId, PageState.User.UserId); var page = await PageService.AddPageAsync(PageState.Page.PageId, PageState.User.UserId);
PageState.EditMode = true; PageState.EditMode = true;
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false"))); NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false")));
} }
} }
} }
@ -501,7 +509,7 @@
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule); module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule);
if (module != null) if (module != null)
{ {
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, module.ModuleId, "Index", "")); NavigationManager.NavigateTo(EditUrl("admin", module.ModuleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
} }
break; break;
case "Add": case "Add":
@ -515,10 +523,10 @@
switch (location) switch (location)
{ {
case "Add": case "Add":
url = EditUrl(PageState.Page.Path, module.ModuleId, location, "cp=" + PageState.Page.PageId); url = EditUrl("admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}");
break; break;
case "Edit": case "Edit":
url = EditUrl(PageState.Page.Path, module.ModuleId, location, "id=" + PageState.Page.PageId.ToString() + "&cp=" + PageState.Page.PageId); url = EditUrl("admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}");
break; break;
} }
} }

View File

@ -19,7 +19,6 @@ namespace Oqtane.Themes.Controls
[Inject] public IUserService UserService { get; set; } [Inject] public IUserService UserService { get; set; }
[Inject] public IJSRuntime jsRuntime { get; set; } [Inject] public IJSRuntime jsRuntime { get; set; }
[Inject] public IServiceProvider ServiceProvider { get; set; } [Inject] public IServiceProvider ServiceProvider { get; set; }
[Inject] public SiteState SiteState { get; set; }
[Inject] public ILogService LoggingService { get; set; } [Inject] public ILogService LoggingService { get; set; }
protected void LoginUser() protected void LoginUser()

View File

@ -10,7 +10,7 @@
</button> </button>
</span> </span>
<div class="app-menu navbar-expand-md"> <div class="app-menu navbar-expand-md">
<div class="collapse navbar-collapse" id="Menu"> <div class="collapse navbar-collapse navbar-nav-scroll" id="Menu">
<MenuItemsHorizontal ParentPage="null" Pages="MenuPages" /> <MenuItemsHorizontal ParentPage="null" Pages="MenuPages" />
</div> </div>
</div> </div>

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Themes.OqtaneTheme namespace Oqtane.Themes.OqtaneTheme
{ {
@ -11,7 +13,13 @@ namespace Oqtane.Themes.OqtaneTheme
Name = "Oqtane Theme", Name = "Oqtane Theme",
Version = "1.0.0", Version = "1.0.0",
ThemeSettingsType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client", ThemeSettingsType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client",
ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client" ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client",
Resources = new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.0/cyborg/bootstrap.min.css", Integrity = "sha512-jwIqEv8o/kTBMJVtbNCBrDqhBojl0YSUam+EFpLjVOC86Ci6t4ZciTnIkelFNOik+dEQVymKGcQLiaJZNAfWRg==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", Integrity = "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", CrossOrigin = "anonymous" }
}
}; };
} }
} }

View File

@ -110,14 +110,6 @@
public override string Panes => PaneNames.Default + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width,Footer"; public override string Panes => PaneNames.Default + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width,Footer";
public override List<Resource> Resources => new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.2.0/cyborg/bootstrap.min.css", Integrity = "sha512-d6pZJl/sNcj0GFkp4kTjXtPE14deuUsOqFQtxkj0KyBJQl+4e0qsEyuIDcNqrYuGoauAW3sWyDCQp49mhF4Syw==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", Integrity = "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", CrossOrigin = "anonymous" }
};
private bool _login = true; private bool _login = true;
private bool _register = true; private bool _register = true;
private bool _footer = false; private bool _footer = false;

View File

@ -52,17 +52,23 @@
</div> </div>
@code { @code {
private string resourceType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client"; // for localization private int pageId = -1;
private string _scope = "page"; private string resourceType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client"; // for localization
private string _login = "-"; private string _scope = "page";
private string _register = "-"; private string _login = "-";
private string _footer = "-"; private string _register = "-";
private string _footer = "-";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try if (PageState.QueryString.ContainsKey("id"))
{ {
await LoadSettings(); pageId = int.Parse(PageState.QueryString["id"]);
}
try
{
await LoadSettings();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -82,7 +88,8 @@
} }
else else
{ {
var settings = SettingService.MergeSettings(PageState.Site.Settings, PageState.Page.Settings); var settings = await SettingService.GetPageSettingsAsync(pageId);
settings = SettingService.MergeSettings(PageState.Site.Settings, settings);
_login = SettingService.GetSetting(settings, GetType().Namespace + ":Login", "-"); _login = SettingService.GetSetting(settings, GetType().Namespace + ":Login", "-");
_register = SettingService.GetSetting(settings, GetType().Namespace + ":Register", "-"); _register = SettingService.GetSetting(settings, GetType().Namespace + ":Register", "-");
_footer = SettingService.GetSetting(settings, GetType().Namespace + ":Footer", "-"); _footer = SettingService.GetSetting(settings, GetType().Namespace + ":Footer", "-");
@ -128,7 +135,7 @@
} }
else else
{ {
var settings = await SettingService.GetPageSettingsAsync(PageState.Page.PageId); var settings = await SettingService.GetPageSettingsAsync(pageId);
if (_login != "-") if (_login != "-")
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
@ -141,7 +148,7 @@
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer);
} }
await SettingService.UpdatePageSettingsAsync(settings, PageState.Page.PageId); await SettingService.UpdatePageSettingsAsync(settings, pageId);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -6,6 +6,7 @@ using Oqtane.UI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Themes namespace Oqtane.Themes
@ -15,10 +16,13 @@ namespace Oqtane.Themes
[Inject] [Inject]
protected IJSRuntime JSRuntime { get; set; } protected IJSRuntime JSRuntime { get; set; }
// optional interface properties [Inject]
protected SiteState SiteState { get; set; }
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
// optional interface properties
public virtual string Name { get; set; } public virtual string Name { get; set; }
public virtual string Thumbnail { get; set; } public virtual string Thumbnail { get; set; }
public virtual string Panes { get; set; } public virtual string Panes { get; set; }
@ -30,17 +34,33 @@ namespace Oqtane.Themes
{ {
if (firstRender) if (firstRender)
{ {
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script)) List<Resource> resources = null;
var type = GetType();
if (type.BaseType == typeof(ThemeBase))
{
if (PageState.Page.Resources != null)
{
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList();
}
}
else // themecontrolbase, containerbase
{
if (Resources != null)
{
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
}
}
if (resources != null && resources.Any())
{ {
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
var scripts = new List<object>(); var scripts = new List<object>();
var inline = 0; var inline = 0;
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) foreach (Resource resource in resources)
{ {
if (!string.IsNullOrEmpty(resource.Url)) if (!string.IsNullOrEmpty(resource.Url))
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + 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 }); scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
} }
else else
{ {
@ -139,6 +159,33 @@ namespace Oqtane.Themes
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate); return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate);
} }
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");
}
[Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)] [Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)]
public string ContentUrl(int fileid) public string ContentUrl(int fileid)
{ {

View File

@ -4,7 +4,8 @@
@if (_visible) @if (_visible)
{ {
<CascadingValue Value="@ModuleState"> <a id="@ModuleState.PageModuleId.ToString()"></a>
<CascadingValue Value="@ModuleState">
@if (_useadminborder) @if (_useadminborder)
{ {
<div class="app-pane-admin-border"> <div class="app-pane-admin-border">
@ -16,6 +17,7 @@
@DynamicComponent @DynamicComponent
} }
</CascadingValue> </CascadingValue>
} }
@code { @code {
@ -38,7 +40,7 @@
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
string container = ModuleState.ContainerType; string container = ModuleState.ContainerType;
if (PageState.ModuleId != -1 && ModuleState.UseAdminContainer) if (PageState.ModuleId != -1 && PageState.Route.Action != "" && ModuleState.UseAdminContainer)
{ {
container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer; container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer;
} }

View File

@ -96,7 +96,7 @@ namespace Oqtane.UI
{ {
_jsRuntime.InvokeVoidAsync( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeLinks", "Oqtane.Interop.includeLinks",
(object) links); (object)links);
return Task.CompletedTask; return Task.CompletedTask;
} }
catch catch
@ -107,12 +107,17 @@ namespace Oqtane.UI
// external scripts need to specify src, inline scripts need to specify id and content // external scripts need to specify src, inline scripts need to specify id and content
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string content, string location) public Task IncludeScript(string id, string src, string integrity, string crossorigin, string content, string location)
{
return IncludeScript(id, src, integrity, crossorigin, "", content, location);
}
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location)
{ {
try try
{ {
_jsRuntime.InvokeVoidAsync( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeScript", "Oqtane.Interop.includeScript",
id, src, integrity, crossorigin, content, location); id, src, integrity, crossorigin, type, content, location);
return Task.CompletedTask; return Task.CompletedTask;
} }
catch catch

View File

@ -13,6 +13,7 @@ namespace Oqtane.UI
public Page Page { get; set; } public Page Page { get; set; }
public User User { get; set; } public User User { get; set; }
public Uri Uri { get; set; } public Uri Uri { get; set; }
public Route Route { get; set; }
public Dictionary<string, string> QueryString { get; set; } public Dictionary<string, string> QueryString { get; set; }
public string UrlParameters { get; set; } public string UrlParameters { get; set; }
public int ModuleId { get; set; } public int ModuleId { get; set; }
@ -23,6 +24,7 @@ namespace Oqtane.UI
public int VisitorId { get; set; } public int VisitorId { get; set; }
public string RemoteIPAddress { get; set; } public string RemoteIPAddress { get; set; }
public string ReturnUrl { get; set; } public string ReturnUrl { get; set; }
public bool IsInternalNavigation { get; set; }
public List<Page> Pages public List<Page> Pages
{ {

View File

@ -17,57 +17,59 @@ else
} }
@code { @code {
private bool _useadminborder = false; private bool _useadminborder = false;
private string _panetitle = ""; private string _panetitle = "";
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
[Parameter] [Parameter]
public string Name { get; set; } public string Name { get; set; }
RenderFragment DynamicComponent { get; set; } RenderFragment DynamicComponent { get; set; }
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList) && PageState.Action == Constants.DefaultAction) if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList) && PageState.Action == Constants.DefaultAction)
{ {
_useadminborder = true; _useadminborder = true;
_panetitle = "<div class=\"app-pane-admin-title\">" + Name + " Pane</div>"; _panetitle = "<div class=\"app-pane-admin-title\">" + Name + " Pane</div>";
} }
else else
{ {
_useadminborder = false; _useadminborder = false;
_panetitle = ""; _panetitle = "";
} }
DynamicComponent = builder => DynamicComponent = builder =>
{ {
if (PageState.ModuleId != -1 && PageState.Action != Constants.DefaultAction) foreach (Module module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
{ {
// action route needs to inject module control into specific pane var pane = module.Pane;
string pane = ""; if (module.ModuleId == PageState.ModuleId && PageState.Action != Constants.DefaultAction)
if (PageState.Page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1) {
{ if (PageState.Page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
pane = PaneNames.Default; {
} pane = PaneNames.Default;
else }
{ else
pane = PaneNames.Admin; {
pane = PaneNames.Admin;
}
}
} // pane matches current pane
if (Name.ToLower() == pane.ToLower()) if (Name.ToLower() == pane.ToLower())
{ {
Module module = PageState.Modules.FirstOrDefault(item => item.ModuleId == PageState.ModuleId); if (module.ModuleId == PageState.ModuleId && PageState.Action != Constants.DefaultAction)
if (module != null) {
{ var moduleType = Type.GetType(module.ModuleType);
var moduleType = Type.GetType(module.ModuleType); if (moduleType != null)
if (moduleType != null) {
{ bool authorized = false;
bool authorized = false; if (Constants.DefaultModuleActions.Contains(PageState.Action))
if (Constants.DefaultModuleActions.Contains(PageState.Action)) {
{ authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList);
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList);
} }
else else
{ {
@ -96,35 +98,16 @@ else
CreateComponent(builder, module); CreateComponent(builder, module);
} }
} }
else
{
// module type does not exist
}
} }
} else
}
else
{
if (PageState.ModuleId != -1)
{
Module module = PageState.Modules.FirstOrDefault(item => item.ModuleId == PageState.ModuleId);
if (module != null && module.Pane.ToLower() == Name.ToLower())
{ {
// check if user is authorized to view module if (PageState.ModuleId == -1 || PageState.ModuleId == module.ModuleId)
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
{ {
CreateComponent(builder, module); // check if user is authorized to view module
} if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
} {
} CreateComponent(builder, module);
else }
{
foreach (Module module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId && item.Pane.ToLower() == Name.ToLower()).OrderBy(x => x.Order).ToArray())
{
// check if user is authorized to view module
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
{
CreateComponent(builder, module);
} }
} }
} }

View File

@ -22,207 +22,243 @@
@DynamicComponent @DynamicComponent
@code { @code {
private string _absoluteUri; private string _absoluteUri;
private bool _navigationInterceptionEnabled; private bool _isInternalNavigation = false;
private PageState _pagestate; private bool _navigationInterceptionEnabled;
private string _error = ""; private PageState _pagestate;
private string _error = "";
[Parameter] [Parameter]
public string Runtime { get; set; } public string Runtime { get; set; }
[Parameter] [Parameter]
public string RenderMode { get; set; } public string RenderMode { get; set; }
[Parameter] [Parameter]
public int VisitorId { get; set; } public int VisitorId { get; set; }
[CascadingParameter] [CascadingParameter]
PageState PageState { get; set; } PageState PageState { get; set; }
[Parameter] [Parameter]
public Action<PageState> OnStateChange { get; set; } public Action<PageState> OnStateChange { get; set; }
private RenderFragment DynamicComponent { get; set; } private RenderFragment DynamicComponent { get; set; }
protected override void OnInitialized() protected override void OnInitialized()
{ {
_absoluteUri = NavigationManager.Uri; _absoluteUri = NavigationManager.Uri;
NavigationManager.LocationChanged += LocationChanged; NavigationManager.LocationChanged += LocationChanged;
DynamicComponent = builder => DynamicComponent = builder =>
{ {
if (PageState != null) if (PageState != null)
{ {
builder.OpenComponent(0, Type.GetType(Constants.PageComponent)); builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
builder.CloseComponent(); builder.CloseComponent();
} }
}; };
} }
public void Dispose() public void Dispose()
{ {
NavigationManager.LocationChanged -= LocationChanged; NavigationManager.LocationChanged -= LocationChanged;
} }
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
if (PageState == null) if (PageState == null)
{ {
await Refresh(); await Refresh();
} }
} }
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] private async void LocationChanged(object sender, LocationChangedEventArgs args)
private async Task Refresh() {
{ _absoluteUri = args.Location;
Site site; _isInternalNavigation = true;
Page page; await Refresh();
User user = null; }
var editmode = false;
var refresh = false;
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
_error = "";
Route route = new Route(_absoluteUri, SiteState.Alias.Path); Task IHandleAfterRender.OnAfterRenderAsync()
int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1; {
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction; if (!_navigationInterceptionEnabled)
var querystring = ParseQueryString(route.Query); {
var returnurl = ""; _navigationInterceptionEnabled = true;
if (querystring.ContainsKey("returnurl")) return NavigationInterception.EnableNavigationInterceptionAsync();
{ }
returnurl = WebUtility.UrlDecode(querystring["returnurl"]); return Task.CompletedTask;
} }
// reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path))) private async Task Refresh()
{ {
if (querystring.ContainsKey("reload") && querystring["reload"] == "post") Site site;
{ Page page;
// post back so that the cookies are set correctly - required on any change to the principal User user = null;
var interop = new Interop(JSRuntime); var editmode = false;
var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) }; var refresh = false;
string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/"); var lastsyncdate = DateTime.UtcNow.AddHours(-1);
await interop.SubmitForm(url, fields); var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
return; _error = "";
}
else
{
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
return;
}
}
// the refresh parameter is used to refresh the client-side PageState Route route = new Route(_absoluteUri, SiteState.Alias.Path);
if (querystring.ContainsKey("refresh")) int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1;
{ var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
refresh = true; var querystring = Utilities.ParseQueryString(route.Query);
} var returnurl = "";
if (querystring.ContainsKey("returnurl"))
{
returnurl = WebUtility.UrlDecode(querystring["returnurl"]);
}
if (PageState != null) // reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias
{ if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
editmode = PageState.EditMode; {
lastsyncdate = PageState.LastSyncDate; if (querystring.ContainsKey("reload") && querystring["reload"] == "post")
} {
if (PageState?.Page.Path != route.PagePath) // post back so that the cookies are set correctly - required on any change to the principal
{ var interop = new Interop(JSRuntime);
editmode = false; // reset edit mode when navigating to different page var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) };
} string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/");
await interop.SubmitForm(url, fields);
return;
}
else
{
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
return;
}
}
// get user // the refresh parameter is used to refresh the client-side PageState
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId) if (querystring.ContainsKey("refresh"))
{ {
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); refresh = true;
if (authState.User.Identity.IsAuthenticated) }
{
user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId);
if (user != null)
{
user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
}
}
}
else
{
user = PageState.User;
}
// process any sync events if (PageState != null)
var sync = await SyncService.GetSyncAsync(lastsyncdate); {
lastsyncdate = sync.SyncDate; editmode = PageState.EditMode;
if (sync.SyncEvents.Any()) lastsyncdate = PageState.LastSyncDate;
{ }
// reload client application if server was restarted or site runtime/rendermode was modified if (PageState?.Page.Path != route.PagePath)
if (PageState != null && sync.SyncEvents.Exists(item => (item.Action == SyncEventActions.Reload))) {
{ editmode = false; // reset edit mode when navigating to different page
NavigationManager.NavigateTo(_absoluteUri, true); }
return;
}
// when site information has changed the PageState needs to be refreshed
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{
refresh = true;
}
// when user information has changed the PageState needs to be refreshed as the list of pages/modules may have changed
if (user != null && sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
{
refresh = true;
}
}
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId) // get user
{ if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId); {
refresh = true; var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
} if (authState.User.Identity.IsAuthenticated)
else {
{ user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId);
site = PageState.Site; if (user != null)
} {
user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
}
}
}
else
{
user = PageState.User;
}
if (site != null) // process any sync events
{ var sync = await SyncService.GetSyncAsync(lastsyncdate);
if (PageState == null || refresh || PageState.Page.Path != route.PagePath) lastsyncdate = sync.SyncDate;
{ if (sync.SyncEvents.Any())
page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); {
} // reload client application if server was restarted or site runtime/rendermode was modified
else if (PageState != null && sync.SyncEvents.Exists(item => (item.Action == SyncEventActions.Reload)))
{ {
page = PageState.Page; NavigationManager.NavigateTo(_absoluteUri, true);
} return;
}
// when site information has changed the PageState needs to be refreshed
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{
refresh = true;
}
// when user information has changed the PageState needs to be refreshed as the list of pages/modules may have changed
if (user != null && sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
{
refresh = true;
}
}
if (page == null && route.PagePath == "") // naked path refers to site home page if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
{ {
if (site.HomePageId != null) site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId);
{ refresh = true;
page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId); }
} else
if (page == null) {
{ site = PageState.Site;
// fallback to use the first page in the collection }
page = site.Pages.FirstOrDefault();
}
}
if (page != null) if (site != null)
{ {
// check if user is authorized to view page if (PageState == null || refresh || PageState.Page.Path != route.PagePath)
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList)) {
{ page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
// load additional metadata for current page }
page = await ProcessPage(page, site, user); else
{
page = PageState.Page;
}
// load additional metadata for modules if (page == null && route.PagePath == "") // naked path refers to site home page
(page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType); {
if (site.HomePageId != null)
{
page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId);
}
if (page == null)
{
// fallback to use the first page in the collection
page = site.Pages.FirstOrDefault();
}
}
if (page == null)
{
// look for personalized page
page = await PageService.GetPageAsync(route.PagePath, site.SiteId);
}
else
{
if (user != null && page.IsPersonalizable)
{
var personalized = await PageService.GetPageAsync(route.PagePath + "/" + user.Username, site.SiteId);
if (personalized != null)
{
// redirect to the personalized page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, personalized.Path, ""), false);
}
}
}
// populate page state (which acts as a client-side cache for subsequent requests) if (page != null)
_pagestate = new PageState {
// check if user is authorized to view page
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList))
{
// load additional metadata for current page
page = ProcessPage(page, site, user, SiteState.Alias);
// load additional metadata for modules
(page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
// populate page state (which acts as a client-side cache for subsequent requests)
_pagestate = new PageState
{ {
Alias = SiteState.Alias, Alias = SiteState.Alias,
Site = site, Site = site,
Page = page, Page = page,
User = user, User = user,
Uri = new Uri(_absoluteUri, UriKind.Absolute), Uri = new Uri(_absoluteUri, UriKind.Absolute),
Route = route,
QueryString = querystring, QueryString = querystring,
UrlParameters = route.UrlParameters, UrlParameters = route.UrlParameters,
ModuleId = moduleid, ModuleId = moduleid,
@ -232,300 +268,270 @@
Runtime = runtime, Runtime = runtime,
VisitorId = VisitorId, VisitorId = VisitorId,
RemoteIPAddress = SiteState.RemoteIPAddress, RemoteIPAddress = SiteState.RemoteIPAddress,
ReturnUrl = returnurl ReturnUrl = returnurl,
IsInternalNavigation = _isInternalNavigation
}; };
OnStateChange?.Invoke(_pagestate); OnStateChange?.Invoke(_pagestate);
await ScrollToFragment(_pagestate.Uri); await ScrollToFragment(_pagestate.Uri);
} }
} }
else // page not found else // page not found
{ {
// look for url mapping // look for url mapping
var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath); var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
{ {
var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl; var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl;
NavigationManager.NavigateTo(url, false); NavigationManager.NavigateTo(url, false);
} }
else // not mapped else // not mapped
{ {
if (user == null) if (user == null)
{ {
// redirect to login page if user not logged in as they may need to be authenticated // redirect to login page if user not logged in as they may need to be authenticated
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)));
} }
else else
{ {
if (route.PagePath != "404") if (route.PagePath != "404")
{ {
// redirect to 404 page // redirect to 404 page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", "")); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", ""));
} }
else else
{ {
// redirect to home page as a fallback // redirect to home page as a fallback
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
} }
} }
} }
} }
} }
else else
{ {
// site does not exist // site does not exist
} }
} }
private async void LocationChanged(object sender, LocationChangedEventArgs args) private Page ProcessPage(Page page, Site site, User user, Alias alias)
{ {
_absoluteUri = args.Location; try
await Refresh(); {
} page.Panes = new List<string>();
page.Resources = new List<Resource>();
Task IHandleAfterRender.OnAfterRenderAsync() // validate theme
{ if (string.IsNullOrEmpty(page.ThemeType))
if (!_navigationInterceptionEnabled) {
{ page.ThemeType = site.DefaultThemeType;
_navigationInterceptionEnabled = true; }
return NavigationInterception.EnableNavigationInterceptionAsync(); var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
} Type themetype = Type.GetType(page.ThemeType);
return Task.CompletedTask; if (themetype == null || theme == null)
} {
// fallback to default Oqtane theme
page.ThemeType = Constants.DefaultTheme;
themetype = Type.GetType(Constants.DefaultTheme);
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
}
private Dictionary<string, string> ParseQueryString(string query) string panes = "";
{ if (themetype != null && theme != null)
Dictionary<string, string> querystring = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // case insensistive keys {
if (!string.IsNullOrEmpty(query)) // get resources for theme (ITheme)
{ page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
if (query.StartsWith("?"))
{
query = query.Substring(1); // ignore "?"
}
foreach (string kvp in query.Split('&', StringSplitOptions.RemoveEmptyEntries))
{
if (kvp != "")
{
if (kvp.Contains("="))
{
string[] pair = kvp.Split('=');
if (!querystring.ContainsKey(pair[0]))
{
querystring.Add(pair[0], pair[1]);
}
}
else
{
if (!querystring.ContainsKey(kvp))
{
querystring.Add(kvp, "true"); // default parameter when no value is provided
}
}
}
}
}
return querystring;
}
private async Task<Page> ProcessPage(Page page, Site site, User user) var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
{ if (themeobject != null)
try {
{ if (!string.IsNullOrEmpty(themeobject.Panes))
if (page.IsPersonalizable && user != null) {
{ panes = themeobject.Panes;
// load the personalized page }
page = await PageService.GetPageAsync(page.PageId, user.UserId); // get resources for theme control
} page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace);
}
}
if (string.IsNullOrEmpty(page.ThemeType)) if (!string.IsNullOrEmpty(panes))
{ {
page.ThemeType = site.DefaultThemeType; page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
} if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin))
{
_error = "The Current Theme Does Not Contain A Default Or Admin Pane";
}
}
else
{
page.Panes.Add(PaneNames.Admin);
_error = "The Current Theme Does Not Contain Any Panes";
}
}
catch
{
// error loading theme or layout
}
page.Panes = new List<string>(); return page;
page.Resources = new List<Resource>(); }
string panes = ""; private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype, Alias alias)
Type themetype = Type.GetType(page.ThemeType); {
if (themetype == null) var paneindex = new Dictionary<string, int>();
{ foreach (Module module in modules)
// fallback {
page.ThemeType = Constants.DefaultTheme; // initialize module control properties
themetype = Type.GetType(Constants.DefaultTheme); module.SecurityAccessLevel = SecurityAccessLevel.Host;
} module.ControlTitle = "";
if (themetype != null) module.Actions = "";
{ module.UseAdminContainer = false;
var themeobject = Activator.CreateInstance(themetype) as IThemeControl; module.PaneModuleIndex = -1;
if (themeobject != null) module.PaneModuleCount = 0;
{
if (!string.IsNullOrEmpty(themeobject.Panes))
{
panes = themeobject.Panes;
}
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page);
}
}
if (!string.IsNullOrEmpty(panes))
{
page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin))
{
_error = "The Current Theme Does Not Contain A Default Or Admin Pane";
}
}
else
{
page.Panes.Add(PaneNames.Admin);
_error = "The Current Theme Does Not Contain Any Panes";
}
}
catch
{
// error loading theme or layout
}
return page; if ((module.PageId == page.PageId || module.ModuleId == moduleid))
} {
var typename = Constants.ErrorModule;
private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype) if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
{ {
var paneindex = new Dictionary<string, int>(); typename = module.ModuleDefinition.ControlTypeTemplate;
foreach (Module module in modules)
{
// initialize module control properties
module.SecurityAccessLevel = SecurityAccessLevel.Host;
module.ControlTitle = "";
module.Actions = "";
module.UseAdminContainer = false;
module.PaneModuleIndex = -1;
module.PaneModuleCount = 0;
if ((module.PageId == page.PageId || module.ModuleId == moduleid)) // handle default action
{ if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
var typename = Constants.ErrorModule; {
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime))) action = module.ModuleDefinition.DefaultAction;
{ }
typename = module.ModuleDefinition.ControlTypeTemplate;
// handle default action // check if the module defines custom action routes
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) if (module.ModuleDefinition.ControlTypeRoutes != "")
{ {
action = module.ModuleDefinition.DefaultAction; foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries))
} {
if (route.StartsWith(action + "="))
{
typename = route.Replace(action + "=", "");
}
}
}
// check if the module defines custom action routes // get module resources
if (module.ModuleDefinition.ControlTypeRoutes != "") page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
{ }
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries))
{
if (route.StartsWith(action + "="))
{
typename = route.Replace(action + "=", "");
}
}
}
}
// ensure component exists and implements IModuleControl // ensure component exists and implements IModuleControl
module.ModuleType = ""; module.ModuleType = "";
if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase)) if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase))
{ {
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action); typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action);
} }
else else
{ {
typename = typename.Replace(Constants.ActionToken, action); typename = typename.Replace(Constants.ActionToken, action);
} }
Type moduletype = Type.GetType(typename, false, true); // case insensitive Type moduletype = Type.GetType(typename, false, true); // case insensitive
if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl))) if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
{ {
module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name
} }
// get additional metadata from IModuleControl interface // get additional metadata from IModuleControl interface
if (moduletype != null && module.ModuleType != "") if (moduletype != null && module.ModuleType != "")
{ {
// retrieve module component resources // retrieve module component resources
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module); page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
if (action.ToLower() == "settings" && module.ModuleDefinition != null) if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{ {
// settings components are embedded within a framework settings module // settings components are embedded within a framework settings module
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true); moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
if (moduletype != null) if (moduletype != null)
{ {
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module); page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
} }
} }
// additional metadata needed for admin components // additional metadata needed for admin components
if (module.ModuleId == moduleid && action != "") if (module.ModuleId == moduleid && action != "")
{ {
module.SecurityAccessLevel = moduleobject.SecurityAccessLevel; module.SecurityAccessLevel = moduleobject.SecurityAccessLevel;
module.ControlTitle = moduleobject.Title; module.ControlTitle = moduleobject.Title;
module.Actions = moduleobject.Actions; module.Actions = moduleobject.Actions;
module.UseAdminContainer = moduleobject.UseAdminContainer; module.UseAdminContainer = moduleobject.UseAdminContainer;
} }
} }
// validate that module's pane exists in current page // validate that module's pane exists in current page
if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1) if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
{ {
// fallback to default pane if it exists // fallback to default pane if it exists
if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1) if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
{ {
module.Pane = PaneNames.Default; module.Pane = PaneNames.Default;
} }
else // otherwise admin pane (legacy) else // otherwise admin pane (legacy)
{ {
module.Pane = PaneNames.Admin; module.Pane = PaneNames.Admin;
} }
} }
// calculate module position within pane // calculate module position within pane
if (paneindex.ContainsKey(module.Pane.ToLower())) if (paneindex.ContainsKey(module.Pane.ToLower()))
{ {
paneindex[module.Pane.ToLower()] += 1; paneindex[module.Pane.ToLower()] += 1;
} }
else else
{ {
paneindex.Add(module.Pane.ToLower(), 0); paneindex.Add(module.Pane.ToLower(), 0);
} }
module.PaneModuleIndex = paneindex[module.Pane.ToLower()]; module.PaneModuleIndex = paneindex[module.Pane.ToLower()];
// container fallback // container fallback
if (string.IsNullOrEmpty(module.ContainerType)) if (string.IsNullOrEmpty(module.ContainerType))
{ {
module.ContainerType = defaultcontainertype; module.ContainerType = defaultcontainertype;
} }
} }
} }
foreach (Module module in modules.Where(item => item.PageId == page.PageId)) foreach (Module module in modules.Where(item => item.PageId == page.PageId))
{ {
if (paneindex.ContainsKey(module.Pane.ToLower())) if (paneindex.ContainsKey(module.Pane.ToLower()))
{ {
module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1; module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1;
} }
} }
return (page, modules); return (page, modules);
} }
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level) private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name)
{ {
if (resources != null) if (resources != null)
{ {
foreach (var resource in resources) foreach (var resource in resources)
{ {
// ensure resource does not exist already if (resource.Level != ResourceLevel.Site)
if (pageresources.Find(item => item.Url == resource.Url) == null) {
{ if (resource.Url.StartsWith("~"))
resource.Level = level; {
pageresources.Add(resource); resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
} }
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
{
resource.Url = alias.BaseUrl + resource.Url;
}
// ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
{
resource.Level = level;
resource.Namespace = name;
pageresources.Add(resource);
}
}
} }
} }
return pageresources; return pageresources;

View File

@ -1,59 +1,186 @@
@namespace Oqtane.UI @namespace Oqtane.UI
@inject IJSRuntime JsRuntime @inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject SiteState SiteState
@DynamicComponent @DynamicComponent
@code { @code {
[CascadingParameter] PageState PageState { get; set; } [CascadingParameter] PageState PageState { get; set; }
RenderFragment DynamicComponent { get; set; } RenderFragment DynamicComponent { get; set; }
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
// handle page redirection // handle page redirection
if (!string.IsNullOrEmpty(PageState.Page.Url)) if (!string.IsNullOrEmpty(PageState.Page.Url))
{
NavigationManager.NavigateTo(PageState.Page.Url);
return;
}
DynamicComponent = builder =>
{
var themeType = Type.GetType(PageState.Page.ThemeType);
builder.OpenComponent(0, themeType);
builder.CloseComponent();
};
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
var interop = new Interop(JsRuntime);
// manage stylesheets for this page
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{
var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower();
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix });
}
if (links.Any())
{ {
await interop.IncludeLinks(links.ToArray()); NavigationManager.NavigateTo(PageState.Page.Url);
return;
} }
await interop.RemoveElementsById("app-stylesheet-page-", "", "app-stylesheet-page-" + batch + "-00");
await interop.RemoveElementsById("app-stylesheet-module-", "", "app-stylesheet-module-" + batch + "-00");
// set page title // set page title
if (!string.IsNullOrEmpty(PageState.Page.Title)) if (!string.IsNullOrEmpty(PageState.Page.Title))
{ {
await interop.UpdateTitle(PageState.Page.Title); SiteState.Properties.PageTitle = PageState.Page.Title;
} }
else else
{ {
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name); SiteState.Properties.PageTitle = PageState.Site.Name + " - " + PageState.Page.Name;
} }
}
// set page head content
var headcontent = "";
// favicon
var favicon = "favicon.ico";
var favicontype = "x-icon";
if (PageState.Site.FaviconFileId != null)
{
favicon = Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value);
favicontype = favicon.Substring(favicon.LastIndexOf(".") + 1);
}
headcontent += $"<link id=\"app-favicon\" rel=\"shortcut icon\" type=\"image/{favicontype}\" href=\"{favicon}\" />\n";
// head content
AddHeadContent(headcontent, PageState.Site.HeadContent);
if (!string.IsNullOrEmpty(PageState.Site.HeadContent))
{
headcontent = AddHeadContent(headcontent, PageState.Site.HeadContent);
}
if (!string.IsNullOrEmpty(PageState.Page.HeadContent))
{
headcontent = AddHeadContent(headcontent, PageState.Page.HeadContent);
}
SiteState.Properties.HeadContent = headcontent;
DynamicComponent = builder =>
{
var themeType = Type.GetType(PageState.Page.ThemeType);
builder.OpenComponent(0, themeType);
builder.CloseComponent();
};
}
private string AddHeadContent(string headcontent, string content)
{
if (!string.IsNullOrEmpty(content))
{
// format head content, remove scripts, and filter duplicate elements
var elements = (">" + content.Replace("\n", "") + "<").Split("><");
foreach (var element in elements)
{
if (!string.IsNullOrEmpty(element) && !element.Contains("script"))
{
if (!headcontent.Contains("<" + element + ">"))
{
headcontent += "<" + element + ">" + "\n";
}
}
}
}
return headcontent;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
{
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
{
await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head);
}
if (!string.IsNullOrEmpty(PageState.Page.BodyContent) && PageState.Page.BodyContent.Contains("<script"))
{
await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body);
}
}
// style sheets
if (PageState.Page.Resources != null && PageState.Page.Resources.Exists(item => item.ResourceType == ResourceType.Stylesheet))
{
var interop = new Interop(JSRuntime);
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{
var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower();
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix });
}
if (links.Any())
{
await interop.IncludeLinks(links.ToArray());
}
await interop.RemoveElementsById("app-stylesheet-page-", "", "app-stylesheet-page-" + batch + "-00");
await interop.RemoveElementsById("app-stylesheet-module-", "", "app-stylesheet-module-" + batch + "-00");
}
}
private async Task InjectScripts(string content, ResourceLocation location)
{
// inject scripts into page dynamically
var interop = new Interop(JSRuntime);
var scripts = new List<object>();
var count = 0;
var index = content.IndexOf("<script");
while (index >= 0)
{
var script = content.Substring(index, content.IndexOf("</script>", index) + 9 - index);
// get script attributes
var attributes = script.Substring(0, script.IndexOf(">")).Replace("\"", "").Split(" ");
string id = "";
string src = "";
string integrity = "";
string crossorigin = "";
string type = "";
foreach (var attribute in attributes)
{
if (attribute.Contains("="))
{
var value = attribute.Split("=");
switch (value[0])
{
case "id":
id = value[1];
break;
case "src":
src = value[1];
break;
case "integrity":
integrity = value[1];
break;
case "crossorigin":
crossorigin = value[1];
break;
case "type":
type = value[1];
break;
}
}
}
// inject script
if (!string.IsNullOrEmpty(src))
{
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location });
}
else
{
// inline script must have an id attribute
if (id == "")
{
count += 1;
id = $"page{PageState.Page.PageId}-script{count}";
}
index = script.IndexOf(">") + 1;
await interop.IncludeScript(id, "", "", "", "", script.Substring(index, script.IndexOf("</script>") - index), location.ToString().ToLower());
}
index = content.IndexOf("<script", index + 1);
}
if (scripts.Any())
{
await interop.IncludeScripts(scripts.ToArray());
}
}
} }

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>3.4.1</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -29,7 +29,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MySql.EntityFrameworkCore" Version="6.0.0" /> <PackageReference Include="MySql.EntityFrameworkCore" Version="7.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.MySQL</id> <id>Oqtane.Database.MySQL</id>
<version>3.4.1</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane MySQL Provider</title> <title>Oqtane MySQL Provider</title>
@ -12,15 +12,15 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="bin\net6.0\Oqtane.Database.MySQL.dll" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.MySQL.dll" target="lib\net7.0" />
<file src="bin\net6.0\Oqtane.Database.MySQL.pdb" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.MySQL.pdb" target="lib\net7.0" />
<file src="bin\net6.0\Mysql.EntityFrameworkCore.dll" target="lib\net6.0" /> <file src="bin\net7.0\Mysql.EntityFrameworkCore.dll" target="lib\net7.0" />
<file src="bin\net6.0\Mysql.Data.dll" target="lib\net6.0" /> <file src="bin\net7.0\Mysql.Data.dll" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>3.4.1</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -29,9 +29,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" /> <PackageReference Include="EFCore.NamingConventions" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.PostgreSQL</id> <id>Oqtane.Database.PostgreSQL</id>
<version>3.4.1</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane PostgreSQL Provider</title> <title>Oqtane PostgreSQL Provider</title>
@ -12,16 +12,16 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="bin\net6.0\Oqtane.Database.PostgreSQL.dll" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.PostgreSQL.dll" target="lib\net7.0" />
<file src="bin\net6.0\Oqtane.Database.PostgreSQL.pdb" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.PostgreSQL.pdb" target="lib\net7.0" />
<file src="bin\net6.0\EFCore.NamingConventions.dll" target="lib\net6.0" /> <file src="bin\net7.0\EFCore.NamingConventions.dll" target="lib\net7.0" />
<file src="bin\net6.0\Npgsql.EntityFrameworkCore.PostgreSQL.dll" target="lib\net6.0" /> <file src="bin\net7.0\Npgsql.EntityFrameworkCore.PostgreSQL.dll" target="lib\net7.0" />
<file src="bin\net6.0\Npgsql.dll" target="lib\net6.0" /> <file src="bin\net7.0\Npgsql.dll" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>3.4.1</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -29,7 +29,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.SqlServer</id> <id>Oqtane.Database.SqlServer</id>
<version>3.4.1</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQL Server Provider</title> <title>Oqtane SQL Server Provider</title>
@ -12,14 +12,14 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="bin\net6.0\Oqtane.Database.SqlServer.dll" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.SqlServer.dll" target="lib\net7.0" />
<file src="bin\net6.0\Oqtane.Database.SqlServer.pdb" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.SqlServer.pdb" target="lib\net7.0" />
<file src="bin\net6.0\Microsoft.EntityFrameworkCore.SqlServer.dll" target="lib\net6.0" /> <file src="bin\net7.0\Microsoft.EntityFrameworkCore.SqlServer.dll" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>3.4.1</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -29,7 +29,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.Sqlite</id> <id>Oqtane.Database.Sqlite</id>
<version>3.4.1</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQLite Provider</title> <title>Oqtane SQLite Provider</title>
@ -12,14 +12,14 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="bin\net6.0\Oqtane.Database.Sqlite.dll" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.Sqlite.dll" target="lib\net7.0" />
<file src="bin\net6.0\Oqtane.Database.Sqlite.pdb" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.Sqlite.pdb" target="lib\net7.0" />
<file src="bin\net6.0\Microsoft.EntityFrameworkCore.Sqlite.dll" target="lib\net6.0" /> <file src="bin\net7.0\Microsoft.EntityFrameworkCore.Sqlite.dll" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

6
Oqtane.Maui/Head.razor Normal file
View File

@ -0,0 +1,6 @@
<DynamicComponent Type="@ComponentType"></DynamicComponent>
@code {
Type ComponentType = Type.GetType("Oqtane.Head, Oqtane.Client");
}

View File

@ -7,6 +7,7 @@
<BlazorWebView HostPage="wwwroot/index.html"> <BlazorWebView HostPage="wwwroot/index.html">
<BlazorWebView.RootComponents> <BlazorWebView.RootComponents>
<RootComponent Selector="head::after" ComponentType="{x:Type local:Head}" />
<RootComponent Selector="#app" ComponentType="{x:Type local:Main}" /> <RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
</BlazorWebView.RootComponents> </BlazorWebView.RootComponents>
</BlazorWebView> </BlazorWebView>

View File

@ -12,8 +12,8 @@ namespace Oqtane.Maui;
public static class MauiProgram public static class MauiProgram
{ {
// the API service url // the API service url
static string apiurl = "https://www.dnfprojects.com"; // for testing //static string apiurl = "https://www.dnfprojects.com"; // for testing
//static string apiurl = "http://localhost:44357"; // for local development (Oqtane.Server must be already running for MAUI client to connect) static string apiurl = "http://localhost:44357"; // for local development (Oqtane.Server must be already running for MAUI client to connect)
public static MauiApp CreateMauiApp() public static MauiApp CreateMauiApp()
{ {
@ -212,23 +212,37 @@ public static class MauiProgram
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services) private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
{ {
// dynamically register module scoped services // dynamically register module scoped services
var implementationTypes = assembly.GetInterfaces<IService>(); try
foreach (var implementationType in implementationTypes)
{ {
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}")); if (implementationType.AssemblyQualifiedName != null)
services.AddScoped(serviceType ?? implementationType, implementationType); {
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) private static void RegisterClientStartups(Assembly assembly, IServiceCollection services)
{ {
var startUps = assembly.GetInstances<IClientStartup>(); try
foreach (var startup in startUps)
{ {
startup.ConfigureServices(services); var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(services);
}
}
catch
{
// could not interrogate assembly - likely missing dependencies
} }
} }
} }

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Razor"> <Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks> <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net7.0-windows10.0.19041.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>3.4.1</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -14,7 +14,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace> <RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid> <ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>3.4.1</ApplicationDisplayVersion> <ApplicationDisplayVersion>4.0.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
@ -65,14 +65,21 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.8" /> <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.8" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" /> <PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
<PackageReference Include="Oqtane.Client" Version="3.4.1" /> </ItemGroup>
<PackageReference Include="Oqtane.Shared" Version="3.4.1" />
<ItemGroup>
<Reference Include="Oqtane.Client">
<HintPath>..\Oqtane.Server\bin\Debug\net7.0\Oqtane.Client.dll</HintPath>
</Reference>
<Reference Include="Oqtane.Shared">
<HintPath>..\Oqtane.Server\bin\Debug\net7.0\Oqtane.Shared.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,15 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories). Deployment of the asset to your application
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
These files will be deployed with you package and will be accessible using Essentials:
async Task LoadMauiAsset()
{
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
using var reader = new StreamReader(stream);
var contents = reader.ReadToEnd();
}

View File

@ -9,8 +9,8 @@
<script src="js/app.js"></script> <script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script> <script src="js/loadjs.min.js"></script>
<link rel="stylesheet" href="css/app.css" /> <link rel="stylesheet" href="css/app.css" />
<link id="app-stylesheet-page" rel="stylesheet" href="css/empty.css" disabled /> <link id="app-stylesheet-page" />
<link id="app-stylesheet-module" rel="stylesheet" href="css/empty.css" disabled /> <link id="app-stylesheet-module" />
</head> </head>
<body> <body>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Client</id> <id>Oqtane.Client</id>
<version>3.4.1</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,13 +12,13 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Client\bin\Release\net6.0\Oqtane.Client.dll" target="lib\net6.0" /> <file src="..\Oqtane.Client\bin\Release\net7.0\Oqtane.Client.dll" target="lib\net7.0" />
<file src="..\Oqtane.Client\bin\Release\net6.0\Oqtane.Client.pdb" target="lib\net6.0" /> <file src="..\Oqtane.Client\bin\Release\net7.0\Oqtane.Client.pdb" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

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

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Server</id> <id>Oqtane.Server</id>
<version>3.4.1</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,13 +12,13 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Server\bin\Release\net6.0\Oqtane.Server.dll" target="lib\net6.0" /> <file src="..\Oqtane.Server\bin\Release\net7.0\Oqtane.Server.dll" target="lib\net7.0" />
<file src="..\Oqtane.Server\bin\Release\net6.0\Oqtane.Server.pdb" target="lib\net6.0" /> <file src="..\Oqtane.Server\bin\Release\net7.0\Oqtane.Server.pdb" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Shared</id> <id>Oqtane.Shared</id>
<version>3.4.1</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,13 +12,13 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Shared\bin\Release\net6.0\Oqtane.Shared.dll" target="lib\net6.0" /> <file src="..\Oqtane.Shared\bin\Release\net7.0\Oqtane.Shared.dll" target="lib\net7.0" />
<file src="..\Oqtane.Shared\bin\Release\net6.0\Oqtane.Shared.pdb" target="lib\net6.0" /> <file src="..\Oqtane.Shared\bin\Release\net7.0\Oqtane.Shared.pdb" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Updater</id> <id>Oqtane.Updater</id>
<version>3.4.1</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,12 +12,12 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Updater\bin\Release\net6.0\publish\*.*" target="lib\net6.0" /> <file src="..\Oqtane.Updater\bin\Release\net7.0\publish\*.*" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

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

View File

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

View File

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

View File

@ -33,8 +33,10 @@ namespace Oqtane.Controllers
private readonly IHttpContextAccessor _accessor; private readonly IHttpContextAccessor _accessor;
private readonly IAliasRepository _aliases; private readonly IAliasRepository _aliases;
private readonly ILogger<InstallationController> _filelogger; private readonly ILogger<InstallationController> _filelogger;
private readonly ITenantManager _tenantManager;
private readonly ServerStateManager _serverState;
public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger<InstallationController> filelogger) public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger<InstallationController> filelogger, ITenantManager tenantManager, ServerStateManager serverState)
{ {
_configManager = configManager; _configManager = configManager;
_installationManager = installationManager; _installationManager = installationManager;
@ -44,6 +46,8 @@ namespace Oqtane.Controllers
_accessor = accessor; _accessor = accessor;
_aliases = aliases; _aliases = aliases;
_filelogger = filelogger; _filelogger = filelogger;
_tenantManager = tenantManager;
_serverState = serverState;
} }
// POST api/<controller> // POST api/<controller>
@ -115,82 +119,56 @@ namespace Oqtane.Controllers
private List<ClientAssembly> GetAssemblyList() private List<ClientAssembly> GetAssemblyList()
{ {
return _cache.GetOrCreate("assemblieslist", entry => int siteId = _tenantManager.GetAlias().SiteId;
return _cache.GetOrCreate($"assemblieslist:{siteId}", entry =>
{ {
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var assemblyList = new List<ClientAssembly>(); var assemblyList = new List<ClientAssembly>();
// get list of assemblies which should be downloaded to client // testmode setting is used for validating that the API is downloading the appropriate assemblies to the client
var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); bool hashfilename = true;
var list = assemblies.Select(a => a.GetName().Name).ToList(); if (_configManager.GetSetting($"{SettingKeys.TestModeKey}", "false") == "true")
// populate assemblies
for (int i = 0; i < list.Count; i++)
{ {
assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, list[i] + ".dll"))); hashfilename = false;
}
// get site assemblies which should be downloaded to client
var assemblies = _serverState.GetServerState(siteId).Assemblies;
// populate assembly list
foreach (var assembly in assemblies)
{
if (assembly != Constants.ClientId)
{
var filepath = Path.Combine(binFolder, assembly) + ".dll";
if (System.IO.File.Exists(filepath))
{
assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, assembly + ".dll"), hashfilename));
}
}
} }
// insert satellite assemblies at beginning of list // insert satellite assemblies at beginning of list
foreach (var culture in _localizationManager.GetInstalledCultures()) foreach (var culture in _localizationManager.GetInstalledCultures())
{ {
var assembliesFolderPath = Path.Combine(binFolder, culture); if (culture != Constants.DefaultCulture)
if (culture == Constants.DefaultCulture)
{ {
continue; var assembliesFolderPath = Path.Combine(binFolder, culture);
} if (Directory.Exists(assembliesFolderPath))
if (Directory.Exists(assembliesFolderPath))
{
foreach (var resourceFile in Directory.EnumerateFiles(assembliesFolderPath))
{ {
assemblyList.Insert(0, new ClientAssembly(resourceFile)); foreach (var assembly in assemblies)
}
}
else
{
_filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist"));
}
}
// insert module and theme dependencies at beginning of list
foreach (var assembly in assemblies)
{
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))))
{
var instance = Activator.CreateInstance(type) as IModule;
foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse())
{
var filepath = Path.Combine(binFolder, name + ".dll");
if (System.IO.File.Exists(filepath))
{ {
if (!assemblyList.Exists(item => item.FilePath == filepath)) var filepath = Path.Combine(assembliesFolderPath, assembly) + ".resources.dll";
if (System.IO.File.Exists(filepath))
{ {
assemblyList.Insert(0, new ClientAssembly(filepath)); assemblyList.Insert(0, new ClientAssembly(Path.Combine(assembliesFolderPath, assembly + ".resources.dll"), hashfilename));
} }
} }
else
{
_filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist"));
}
} }
} else
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme))))
{
var instance = Activator.CreateInstance(type) as ITheme;
foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse())
{ {
var filepath = Path.Combine(binFolder, name + ".dll"); _filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist"));
if (System.IO.File.Exists(filepath))
{
if (!assemblyList.Exists(item => item.FilePath == filepath))
{
assemblyList.Insert(0, new ClientAssembly(filepath));
}
}
else
{
_filelogger.LogError(Utilities.LogMessage(this, $"Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist"));
}
} }
} }
} }
@ -233,21 +211,24 @@ namespace Oqtane.Controllers
{ {
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{ {
if (System.IO.File.Exists(assembly.FilePath)) if (Path.GetFileNameWithoutExtension(assembly.FilePath) != Constants.ClientId)
{ {
using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read)) if (System.IO.File.Exists(assembly.FilePath))
using (var entrystream = archive.CreateEntry(assembly.HashedName).Open())
{ {
filestream.CopyTo(entrystream); using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read))
using (var entrystream = archive.CreateEntry(assembly.HashedName).Open())
{
filestream.CopyTo(entrystream);
}
} }
} var pdb = assembly.FilePath.Replace(".dll", ".pdb");
var pdb = assembly.FilePath.Replace(".dll", ".pdb"); if (System.IO.File.Exists(pdb))
if (System.IO.File.Exists(pdb))
{
using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read))
using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open())
{ {
filestream.CopyTo(entrystream); using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read))
using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open())
{
filestream.CopyTo(entrystream);
}
} }
} }
} }
@ -285,11 +266,18 @@ namespace Oqtane.Controllers
public struct ClientAssembly public struct ClientAssembly
{ {
public ClientAssembly(string filepath) public ClientAssembly(string filepath, bool hashfilename)
{ {
FilePath = filepath; FilePath = filepath;
DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath); DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath);
HashedName = GetDeterministicHashCode(filepath).ToString("X8") + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath); if (hashfilename)
{
HashedName = GetDeterministicHashCode(filepath).ToString("X8") + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
}
else
{
HashedName = Path.GetFileNameWithoutExtension(filepath) + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
}
} }
public string FilePath { get; private set; } public string FilePath { get; private set; }

View File

@ -9,6 +9,7 @@ using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using System.Net; using System.Net;
using System.Security.Policy;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -75,7 +76,7 @@ namespace Oqtane.Controllers
module.Order = pagemodule.Order; module.Order = pagemodule.Order;
module.ContainerType = pagemodule.ContainerType; module.ContainerType = pagemodule.ContainerType;
module.ModuleDefinition = FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName)); module.ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName));
module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId) module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.PermissionList)) .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.PermissionList))
@ -83,6 +84,7 @@ namespace Oqtane.Controllers
modules.Add(module); modules.Add(module);
} }
modules = modules.OrderBy(item => item.PageId).ThenBy(item => item.Pane).ThenBy(item => item.Order).ToList();
} }
} }
else else
@ -95,29 +97,6 @@ namespace Oqtane.Controllers
return modules; return modules;
} }
private ModuleDefinition FilterModuleDefinition(ModuleDefinition moduleDefinition)
{
if (moduleDefinition != null)
{
moduleDefinition.Description = "";
moduleDefinition.Categories = "";
moduleDefinition.Version = "";
moduleDefinition.Owner = "";
moduleDefinition.Url = "";
moduleDefinition.Contact = "";
moduleDefinition.License = "";
moduleDefinition.Dependencies = "";
moduleDefinition.PermissionNames = "";
moduleDefinition.ServerManagerType = "";
moduleDefinition.ReleaseVersions = "";
moduleDefinition.PackageName = "";
moduleDefinition.AssemblyName = "";
moduleDefinition.PermissionList = null;
moduleDefinition.Template = "";
}
return moduleDefinition;
}
// GET api/<controller>/5 // GET api/<controller>/5
[HttpGet("{id}")] [HttpGet("{id}")]
public Module Get(int id) public Module Get(int id)
@ -126,7 +105,7 @@ namespace Oqtane.Controllers
if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User,PermissionNames.View, module.PermissionList)) if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User,PermissionNames.View, module.PermissionList))
{ {
List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(module.SiteId).ToList(); List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(module.SiteId).ToList();
module.ModuleDefinition = FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName)); module.ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName));
module.Settings = _settings.GetSettings(EntityNames.Module, id) module.Settings = _settings.GetSettings(EntityNames.Module, id)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, module.PermissionList)) .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, module.PermissionList))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);

View File

@ -123,14 +123,14 @@ namespace Oqtane.Controllers
if (moduleDefinition.Template.ToLower().Contains("internal")) if (moduleDefinition.Template.ToLower().Contains("internal"))
{ {
rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString()); rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString());
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + ", Oqtane.Client"; moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ", Oqtane.Client";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, Oqtane.Server"; moduleDefinition.ServerManagerType = moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, Oqtane.Server";
} }
else else
{ {
rootPath = Utilities.PathCombine(rootFolder.Parent.FullName, moduleDefinition.Owner + "." + moduleDefinition.Name, Path.DirectorySeparatorChar.ToString()); rootPath = Utilities.PathCombine(rootFolder.Parent.FullName, moduleDefinition.Owner + ".Module." + moduleDefinition.Name, Path.DirectorySeparatorChar.ToString());
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + ", " + moduleDefinition.Owner + "." + moduleDefinition.Name + ".Client.Oqtane"; moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ", " + moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ".Client.Oqtane";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + "." + moduleDefinition.Name + ".Server.Oqtane"; moduleDefinition.ServerManagerType = moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ".Server.Oqtane";
} }
ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition); ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition);
@ -255,10 +255,10 @@ namespace Oqtane.Controllers
_modules.DeleteModule(moduleToRemove.ModuleId); _modules.DeleteModule(moduleToRemove.ModuleId);
} }
// remove module definition // remove module definition
_moduleDefinitions.DeleteModuleDefinition(id); _moduleDefinitions.DeleteModuleDefinition(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, moduledefinition.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
} }
else else
@ -268,14 +268,6 @@ namespace Oqtane.Controllers
} }
} }
[HttpGet("install")]
[Authorize(Roles = RoleNames.Host)]
public void InstallModules()
{
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Modules Installed");
_installationManager.InstallPackages();
}
// GET: api/<controller>/templates // GET: api/<controller>/templates
[HttpGet("templates")] [HttpGet("templates")]
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
@ -339,9 +331,9 @@ namespace Oqtane.Controllers
if (moduleDefinition.Version == "local") if (moduleDefinition.Version == "local")
{ {
text = text.Replace("[FrameworkVersion]", Constants.Version); text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>"); text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[ServerReference]", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Server.dll</HintPath></Reference>"); text = text.Replace("[ServerReference]", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Server.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>"); text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Shared.dll</HintPath></Reference>");
} }
else else
{ {

View File

@ -10,6 +10,8 @@ using Oqtane.Enums;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Modules.Admin.Users;
using System.IO;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -73,20 +75,12 @@ namespace Oqtane.Controllers
return pages; return pages;
} }
// GET api/<controller>/5?userid=x // GET api/<controller>/5
[HttpGet("{id}")] [HttpGet("{id}")]
public Page Get(int id, string userid) public Page Get(int id)
{ {
Page page = null; var page = _pages.GetPage(id);
if (string.IsNullOrEmpty(userid)) if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, page.PermissionList))
{
page = _pages.GetPage(id);
}
else
{
page = _pages.GetPage(id, int.Parse(userid));
}
if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User,PermissionNames.View, page.PermissionList))
{ {
page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId) page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, page.PermissionList)) .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, page.PermissionList))
@ -95,7 +89,7 @@ namespace Oqtane.Controllers
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {PageId} {UserId}", id, userid); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {PageId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null; return null;
} }
@ -140,7 +134,7 @@ namespace Oqtane.Controllers
}; };
} }
if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions)) if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, permissions))
{ {
page = _pages.AddPage(page); page = _pages.AddPage(page);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create);
@ -180,29 +174,30 @@ namespace Oqtane.Controllers
{ {
Page page = null; Page page = null;
Page parent = _pages.GetPage(id); Page parent = _pages.GetPage(id);
if (parent != null && parent.SiteId == _alias.SiteId && parent.IsPersonalizable && _userPermissions.GetUser(User).UserId == int.Parse(userid)) User user = _userPermissions.GetUser(User);
if (parent != null && parent.SiteId == _alias.SiteId && parent.IsPersonalizable && user.UserId == int.Parse(userid))
{ {
page = new Page(); page = new Page();
page.SiteId = parent.SiteId; page.SiteId = parent.SiteId;
page.Name = parent.Name;
page.Title = parent.Title;
page.Path = parent.Path;
page.ParentId = parent.PageId; page.ParentId = parent.PageId;
page.Name = user.Username;
page.Path = parent.Path + "/" + page.Name;
page.Title = parent.Name + " - " + page.Name;
page.Order = 0; page.Order = 0;
page.IsNavigation = false; page.IsNavigation = false;
page.Url = ""; page.Url = "";
page.ThemeType = parent.ThemeType; page.ThemeType = parent.ThemeType;
page.DefaultContainerType = parent.DefaultContainerType; page.DefaultContainerType = parent.DefaultContainerType;
page.Icon = parent.Icon; page.Icon = parent.Icon;
page.PermissionList = new List<Permission> { page.PermissionList = new List<Permission>()
{
new Permission(PermissionNames.View, int.Parse(userid), true), new Permission(PermissionNames.View, int.Parse(userid), true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, int.Parse(userid), true) new Permission(PermissionNames.Edit, int.Parse(userid), true)
}; };
page.IsPersonalizable = false; page.IsPersonalizable = false;
page.UserId = int.Parse(userid); page.UserId = int.Parse(userid);
page = _pages.AddPage(page); page = _pages.AddPage(page);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
// copy modules // copy modules
List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId).ToList(); List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId).ToList();
@ -213,8 +208,10 @@ namespace Oqtane.Controllers
module.PageId = page.PageId; module.PageId = page.PageId;
module.ModuleDefinitionName = pm.Module.ModuleDefinitionName; module.ModuleDefinitionName = pm.Module.ModuleDefinitionName;
module.AllPages = false; module.AllPages = false;
module.PermissionList = new List<Permission> { module.PermissionList = new List<Permission>()
{
new Permission(PermissionNames.View, int.Parse(userid), true), new Permission(PermissionNames.View, int.Parse(userid), true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, int.Parse(userid), true) new Permission(PermissionNames.Edit, int.Parse(userid), true)
}; };
module = _modules.AddModule(module); module = _modules.AddModule(module);
@ -235,6 +232,9 @@ namespace Oqtane.Controllers
_pageModules.AddPageModule(pagemodule); _pageModules.AddPageModule(pagemodule);
} }
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
} }
else else
{ {
@ -284,7 +284,7 @@ namespace Oqtane.Controllers
{ {
var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList(); var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList();
// permissions added // permissions added
foreach(Permission permission in added) foreach (Permission permission in added)
{ {
if (!modulePermissions.Any(item => item.PermissionName == permission.PermissionName if (!modulePermissions.Any(item => item.PermissionName == permission.PermissionName
&& item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized)) && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized))
@ -345,7 +345,7 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Registered)] [Authorize(Roles = RoleNames.Registered)]
public void Put(int siteid, int pageid, int? parentid) public void Put(int siteid, int pageid, int? parentid)
{ {
if (siteid == _alias.SiteId && siteid == _alias.SiteId && _pages.GetPage(pageid, false) != null && _userPermissions.IsAuthorized(User, siteid, EntityNames.Page, pageid, PermissionNames.Edit)) if (siteid == _alias.SiteId && _pages.GetPage(pageid, false) != null && _userPermissions.IsAuthorized(User, siteid, EntityNames.Page, pageid, PermissionNames.Edit))
{ {
int order = 1; int order = 1;
List<Page> pages = _pages.GetPages(siteid).ToList(); List<Page> pages = _pages.GetPages(siteid).ToList();

View File

@ -200,6 +200,8 @@ namespace Oqtane.Controllers
case EntityNames.Tenant: case EntityNames.Tenant:
case EntityNames.ModuleDefinition: case EntityNames.ModuleDefinition:
case EntityNames.Host: case EntityNames.Host:
case EntityNames.Job:
case EntityNames.Theme:
if (permissionName == PermissionNames.Edit) if (permissionName == PermissionNames.Edit)
{ {
authorized = User.IsInRole(RoleNames.Host); authorized = User.IsInRole(RoleNames.Host);
@ -262,6 +264,8 @@ namespace Oqtane.Controllers
case EntityNames.Tenant: case EntityNames.Tenant:
case EntityNames.ModuleDefinition: case EntityNames.ModuleDefinition:
case EntityNames.Host: case EntityNames.Host:
case EntityNames.Job:
case EntityNames.Theme:
filter = !User.IsInRole(RoleNames.Host); filter = !User.IsInRole(RoleNames.Host);
break; break;
case EntityNames.Site: case EntityNames.Site:

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