Compare commits

..

501 Commits

Author SHA1 Message Date
1872a1a77c Merge pull request #3483 from oqtane/master
Merge pull request #3482 from oqtane/dev
2023-11-16 15:00:22 -05:00
cf57bad7fa Merge pull request #3482 from oqtane/dev
8.0.0 Release
2023-11-16 15:00:04 -05:00
7e956894ee Merge pull request #3477 from sbwalker/dev
fix warning in MySQL provider, update Database provider packages to official .NET 8 release, add Oqtane.Licensing
2023-11-15 20:49:33 -05:00
f78046d4c1 fix warning in MySQL provider, update Database provider packages to official .NET 8 release, add Oqtane.Licensing 2023-11-15 20:49:07 -05:00
beb09cbf4a Merge pull request #3476 from sbwalker/dev
add SiteState.IsPrerendering property back to avoid breaking change. New implementation relies on .NET 8 HttpContext CascadingParameter
2023-11-15 15:01:04 -05:00
6b2a5d7777 add SiteState.IsPrerendering property back to avoid breaking change. New implementation relies on .NET 8 HttpContext CascadingParameter 2023-11-15 15:00:34 -05:00
4b0d368222 Merge pull request #3475 from sbwalker/dev
migration to official .NET 8 release
2023-11-14 14:48:35 -05:00
5ab2f6ea3a migration to official .NET 8 release 2023-11-14 14:48:12 -05:00
f845bf5b25 Merge pull request #3472 from sbwalker/dev
fix #3454 - do not remove Oqtane.lLicensing assemblies during package uninstall
2023-11-13 13:16:56 -05:00
bb10d64d58 fix #3454 - do not remove Oqtane.lLicensing assemblies during package uninstall 2023-11-13 13:16:35 -05:00
62472b97e6 Merge pull request #3469 from sbwalker/dev
update email address in AspNetUsers table when user email is modified
2023-11-10 16:13:37 -05:00
10b89ff00b update email address in AspNetUsers table when user email is modified 2023-11-10 16:13:24 -05:00
2d8339343f Merge pull request #3468 from sbwalker/dev
removed photo from Edit User as files cannot be written to other users folders
2023-11-10 15:59:02 -05:00
9ddd88b5d0 removed photo from Edit User as files cannot be written to other users folders 2023-11-10 15:58:43 -05:00
614ec645c6 Merge pull request #3467 from sbwalker/dev
use appropriate bootstrap classes
2023-11-10 15:40:32 -05:00
893ffc48dc use appropriate bootstrap classes 2023-11-10 15:40:20 -05:00
7bddb70f11 Merge pull request #3466 from sbwalker/dev
add new user settings to RESX file
2023-11-10 15:19:13 -05:00
9d4a38e560 add new user settings to RESX file 2023-11-10 15:18:59 -05:00
9d20b31585 Merge pull request #3465 from sbwalker/dev
fix #3399 - set defaults for all dropdowns in Add Site
2023-11-10 15:05:51 -05:00
fb8838375f fix #3399 - set defaults for all dropdowns in Add Site 2023-11-10 15:05:39 -05:00
dead4e3f85 Merge pull request #3464 from sbwalker/dev
fix #3420 - auto create user folder for Host user if it does not exist for site
2023-11-10 13:49:31 -05:00
9c833a8a95 fix #3420 - auto create user folder for Host user if it does not exist for site 2023-11-10 13:49:11 -05:00
0b90794bca Merge pull request #3463 from sbwalker/dev
move visitor tracking after url mapping and 404 handling
2023-11-10 13:11:30 -05:00
fd89757814 move visitor tracking after url mapping and 404 handling 2023-11-10 13:00:57 -05:00
5eedc312c8 Merge pull request #3462 from PfaffIC/dev-loginauthcookie
Added login cookie expiration time functionality.
2023-11-10 07:48:47 -05:00
36c1cc5e0c Added functinality to always remember user login.
Added functinality to always remember user login. Provides the option to force login cookie expiration and dont fallback on the session timespan that is used as default when users dont choose the option 'Remember me'.
2023-11-09 16:29:09 +01:00
0b4cdea9dd Added functinality to declare custom login cookie expiration time.
Added login cookie expiration time. Added setting in user settings to declare custom cookie expiration time. Cookie expiration time overwrites default expiration time of 14 days (if not session timespan is used).
2023-11-09 16:15:53 +01:00
12172168f6 Merge pull request #3460 from sbwalker/dev
improve upload time estimate calculations and limit polling attempts
2023-11-07 15:14:13 -05:00
c79aa49252 improve upload time estimate calculations and limit polling attempts 2023-11-07 15:13:33 -05:00
b869308bf4 Merge pull request #3459 from sbwalker/dev
fix #3438 - remove IsPrerendering property as it is not used, and remove reference to HttpContext
2023-11-07 13:52:30 -05:00
7a748bd142 fix #3438 - remove IsPrerendering property as it is not used, and remove reference to HttpContext 2023-11-07 13:49:54 -05:00
d11669e613 Merge pull request #3457 from oqtane/net8
Merging net8 branch into dev in preparation for Oqtane 5.0 release next week
2023-11-06 15:04:53 -05:00
b5108b938c Merge pull request #3447 from thabaum/net8-Admin-Profile-Edit-Validation
Admin\Profiles\Edit.razor: Form Validation For Rows, Length and Order - Adds Rows Resource Data - Issues #3426,  #3446
2023-10-30 09:12:27 -04:00
049d510536 Updates Order/Rows/Length min/max values 2023-10-26 14:01:27 -07:00
113f7b18a6 Replaces maxLength with max for input type number 2023-10-25 09:01:41 -07:00
65b248c3cf min="1" for "Rows" 2023-10-25 06:18:00 -07:00
1698996ac3 Adds "Rows" Resource Data 2023-10-24 16:12:19 -07:00
2ae47f2375 Adds type="number" Form Input Validation 2023-10-24 16:10:12 -07:00
6733d12e81 Merge pull request #3445 from sbwalker/net8
added Verify Existing Users? option to User Management - External Login Setting
2023-10-24 14:31:59 -04:00
8b5109e32f added Verify Existing Users? option to User Management - External Login Setting 2023-10-24 14:28:14 -04:00
7b651d56b1 Merge pull request #3444 from sbwalker/net8
if site login is disabled redirect Login to external identity provider
2023-10-24 13:49:09 -04:00
23b9b8aaf6 if site login is disabled redirect Login to external identity provider 2023-10-24 13:46:25 -04:00
8d9fb43828 Merge pull request #3443 from sbwalker/net8
fixes to notifications UI in user profile
2023-10-24 08:41:20 -04:00
0d1be72fdb fixes to notifications UI in user profile 2023-10-24 08:40:26 -04:00
ce102a2af6 Merge pull request #3436 from sbwalker/net8
complete fix for #3427 in User Profile component
2023-10-23 12:17:21 -04:00
dda6071bbc complete fix for #3427 in User Profile component 2023-10-23 12:16:52 -04:00
0c4809bb16 Merge pull request #3435 from sbwalker/net8
Fix spelling mistake "notififications"
2023-10-23 11:36:37 -04:00
4b87c5e80e Fix spelling mistake "notififications" 2023-10-23 11:36:03 -04:00
b8fe9b9074 Merge pull request #3425 from thabaum/net8-UserProfile-Index-Fix
UserProfile/Index.razor: Notifications Formatting and No Notifications Message - Fixes #3413, #3415
2023-10-23 11:33:31 -04:00
bc8b18cea8 Merge pull request #3434 from sbwalker/net8
fix #3427 - profile validation for User Management (Add/Edit)
2023-10-23 11:25:40 -04:00
20fdd3e891 fix #3427 - profile validation for User Management (Add/Edit) 2023-10-23 11:24:22 -04:00
ff9147487f Merge pull request #3424 from thabaum/net8-UserProfile-View-Fix
UserProfile/View.razor: Sets Input Classes to col-sm-9, Disables Reply When Notification From is System or Blank (empty)
2023-10-23 11:22:15 -04:00
af494f3b5e Merge pull request #3423 from thabaum/net8-Models-Profile-Fix
Oqtane.Server\Repository\SiteRepository.cs: Sets Rows Default Value Equal To 1.
2023-10-23 11:20:58 -04:00
f68d26317d Removes Biography 2023-10-21 15:12:15 -07:00
5b881f7c77 Disable Reply Button When From is System or Emtpy 2023-10-21 13:11:02 -07:00
fe5efff255 Adds Biography and Rows Profile Settings to Create Site 2023-10-21 10:36:09 -07:00
46d18a642c Rows property set to no default 2023-10-21 10:32:01 -07:00
c00012f28b class= 2023-10-20 21:57:42 -07:00
d0050f7d59 Updates Localization of No Notifications Messages 2023-10-20 21:39:19 -07:00
f3a4881261 No notification messages. 2023-10-20 21:38:06 -07:00
1728909964 Adds Localization To No Notification Messages 2023-10-20 21:34:43 -07:00
f6e7460b24 Fix Formatting + Add No Notifictions Message 2023-10-20 21:31:23 -07:00
351829888f Fix Columns Format 2023-10-20 21:25:27 -07:00
e520c30ade Rows Property Default Equals 1 2023-10-20 21:18:36 -07:00
99267ac2f0 Merge pull request #3409 from thabaum/thabaum-NET8-Development-Module-Theme-Templates
development module theme templates
2023-10-18 19:37:47 -04:00
6f1f7ef7fc Revert back to previous bootstrap version 2023-10-18 16:20:36 -07:00
d647e947e2 Removes unnecessary space 2023-10-18 15:52:22 -07:00
adef45c30f Removes unnecessary space 2023-10-18 15:51:58 -07:00
8b80859023 .NET 8 Update 2023-10-18 15:45:47 -07:00
907fff89c8 .NET 8 Update 2023-10-18 15:43:55 -07:00
ca532ffdaf .NET 8 Update 2023-10-18 15:43:13 -07:00
64ee842dc4 .NET 8 Update 2023-10-18 15:42:36 -07:00
9c0754a88d .NET 8 Update 2023-10-18 15:40:51 -07:00
3dac9e3dae .NET 8.0 Update 2023-10-18 15:38:51 -07:00
6963e667aa Update to version 5.0.0 2023-10-18 15:37:38 -07:00
af292b291b Updates to Bootstrap 5.3.2 2023-10-18 15:33:34 -07:00
918f75704e .NET 8 Update 2023-10-18 15:26:42 -07:00
6e258e5d0c .NET 8 Update 2023-10-18 15:23:06 -07:00
a8ea4ec085 .NET 8 Update 2023-10-18 15:22:22 -07:00
2f894af028 .NET 8 Update 2023-10-18 15:21:53 -07:00
dd400e1214 Version 5 2023-10-18 15:20:56 -07:00
f8a183bfdf .NET 8 Update 2023-10-18 15:06:44 -07:00
2404193b61 .NET 8 Ready 2023-10-18 15:04:07 -07:00
3ef11f5b26 Merge pull request #3408 from sbwalker/net8
fix compiler warning ASP0019: Use IHeaderDictionary.Append or the indexer to append or set headers.
2023-10-18 17:14:04 -04:00
30419cec12 fix compiler warning ASP0019: Use IHeaderDictionary.Append or the indexer to append or set headers. 2023-10-18 17:09:19 -04:00
162841feb8 Merge pull request #3405 from thabaum/net8
Removes unnecessary added space from upgrade to .NET 8
2023-10-18 16:18:32 -04:00
f910f63f8d Remove space 2023-10-18 13:14:52 -07:00
860cb75f8f Merge pull request #3404 from thabaum/net8
Remove default connection data
2023-10-18 16:14:27 -04:00
293be93b93 Remove default connection data 2023-10-18 12:55:39 -07:00
976e77b31e Merge pull request #3402 from sbwalker/net8
.NET 8 initial migration
2023-10-18 15:25:05 -04:00
9f28ee2982 .NET 8 initial migration 2023-10-18 15:22:53 -04:00
e6c1e48b86 Merge pull request #3396 from sbwalker/net8
test commit
2023-10-18 11:51:03 -04:00
2c2880199d test commit 2023-10-18 11:50:32 -04:00
c7f8737eeb Merge pull request #3392 from sbwalker/dev
resolve null reference exception in FileManager when ShowFiles = false
2023-10-17 15:12:06 -04:00
f3aea3108d resolve null reference exception in FileManager when ShowFiles = false 2023-10-17 15:11:51 -04:00
e4f8ad0f05 Update README.md 2023-10-16 15:05:12 -04:00
8b80f72313 Merge pull request #3386 from oqtane/master
4.0.6 Release
2023-10-16 15:00:27 -04:00
06ebce31ca Merge pull request #3385 from oqtane/dev
4.0.6 Release
2023-10-16 14:59:59 -04:00
59db8ba997 Merge pull request #3383 from sbwalker/dev
4.0.6 data provider packages
2023-10-16 09:08:17 -04:00
df37d4aa7f 4.0.6 data provider packages 2023-10-16 09:08:03 -04:00
fd9935c800 Merge pull request #3382 from sbwalker/dev
prepare for 4.0.6 release
2023-10-16 08:43:42 -04:00
c057430488 prepare for 4.0.6 release 2023-10-16 08:43:31 -04:00
767fa78e22 Merge pull request #3380 from sbwalker/dev
resolve issue in making Pager search work with sorting
2023-10-16 08:03:42 -04:00
a0e289dcd6 resolve issue in making Pager search work with sorting 2023-10-16 08:03:31 -04:00
c62d147254 Merge pull request #3375 from leigh-pointer/AllowPunctuation
Module Creator allow punctuation in Description field
2023-10-13 08:46:15 -04:00
f739bee353 Merge pull request #3376 from leigh-pointer/BootswatchThemeIssue
Fix for #3374 Backward compatability
2023-10-13 08:46:03 -04:00
db4dfb69fb Merge pull request #3377 from sbwalker/dev
improve Pager search to support properties on child objects
2023-10-13 08:39:23 -04:00
9729a5ef16 improve Pager search to support properties on child objects 2023-10-13 08:39:02 -04:00
6f12818352 Fix for #3374 Backward compatability
Fixes the issue in the Language selector.
2023-10-13 14:06:21 +02:00
75fc318da0 Module Creator all punctuation in description
Modified the Regex to all punctuation in the description.
2023-10-13 11:50:00 +02:00
acf71cc2c2 Merge pull request #3370 from leigh-pointer/LanguageAlignment
Language Selector Alignment fix  #3368
2023-10-12 11:10:47 -04:00
42efe10473 Merge pull request #3372 from sbwalker/dev
optimize Pager search to remove redundant AllowSearch parameter and avoid consuming memory when not in use
2023-10-12 11:10:35 -04:00
b77e72880b optimize Pager search to remove redundant AllowSearch parameter and avoid consuming memory when not in use 2023-10-12 11:10:14 -04:00
297c1524b4 Language Selector Alignment fix #3368
Themes fixed also
2023-10-11 15:48:05 +02:00
6140743769 Merge pull request #3352 from Rodien/dev
Introduce a dropdown menu for authorization response types
2023-10-11 09:08:48 -04:00
d5ca700828 Merge pull request #3366 from sbwalker/dev
change name of RESX key and value to reflect purpose
2023-10-10 10:33:39 -04:00
a7e1fe76c3 change name of RESX key and value to reflect purpose 2023-10-10 10:33:11 -04:00
f6c55279d1 Merge pull request #3364 from leigh-pointer/PagerSearch
Pager Search Localization
2023-10-10 10:25:47 -04:00
826f4835bb Merge branch 'dev' into PagerSearch 2023-10-10 10:25:37 -04:00
119a28def1 Pager Search Localization
Localization is now referencing the Shared Resources
2023-10-10 14:33:16 +02:00
c1ffb8bc33 Merge pull request #3365 from sbwalker/dev
use SharedLocalizer in Pager
2023-10-10 08:07:21 -04:00
60cc9d14af use SharedLocalizer in Pager 2023-10-10 08:07:07 -04:00
7cf4f8fdaa Pager Search Localization
Localized the new Search Buttons
Added a Localized Placeholder that will display what columns are being searched.
2023-10-10 11:28:57 +02:00
1b84e83061 Merge pull request #3363 from sbwalker/dev
add Search capability to Pager and include in management UIs
2023-10-09 16:59:54 -04:00
b0d2ee8760 add Search capability to Pager and include in management UIs 2023-10-09 16:59:36 -04:00
2022432d35 Merge pull request #3362 from sbwalker/dev
Add Rows option to Profile Management. Improve Profile validation feedback. Fix Add User so that profile Options are supported.
2023-10-09 14:27:24 -04:00
c0ed335d84 Add Rows option to Profile Management. Improve Profile validation feedback. Fix Add User so that profile Options are supported. 2023-10-09 14:26:56 -04:00
4964562866 Merge pull request #3361 from sbwalker/dev
include Pane in Module Settings
2023-10-09 13:19:00 -04:00
f78dc443ad include Pane in Module Settings 2023-10-09 13:18:43 -04:00
56b47db778 Merge pull request #3360 from sbwalker/dev
allow module to be inserted at top of pane
2023-10-09 12:58:15 -04:00
2d4bf17b28 allow module to be inserted at top of pane 2023-10-09 12:58:01 -04:00
3b1819c68d Merge pull request #3359 from sbwalker/dev
optimize UrlCombine method
2023-10-09 12:00:51 -04:00
575bbdb53b optimize UrlCombine method 2023-10-09 12:00:34 -04:00
2fa7482028 Introduce a dropdown menu in the 'External Login' settings area for authentication flow response types. 2023-10-03 09:00:53 +02:00
e5062317e2 Merge pull request #3347 from sbwalker/dev
do not show uploaded file name or delete button when multiple uploads are enabled
2023-09-29 11:49:05 -04:00
543f4fa3c2 do not show uploaded file name or delete button when multiple uploads are enabled 2023-09-29 11:48:45 -04:00
49cdf69815 Merge pull request #3344 from Mostafa-Moafi/dev
Fixed issue #3282
2023-09-28 15:36:00 -04:00
04196a1a19 Merge pull request #3346 from sbwalker/dev
display uploaded file name in FileManager when ShowFiles = false
2023-09-28 15:35:26 -04:00
b8622b8708 display uploaded file name in FileManager when ShowFiles = false 2023-09-28 15:35:13 -04:00
a2feca0ba3 Fixed Bug #3282 2023-09-28 19:23:31 +03:30
ae6c6a75b2 Merge pull request #3342 from sbwalker/dev
style page names in Admin Dashboard so that they are always visible in Bootstrap themes
2023-09-28 07:51:05 -04:00
bd987e9531 style page names in Admin Dashboard so that they are always visible in Bootstrap themes 2023-09-27 16:26:59 -04:00
24d69fd820 Merge pull request #3341 from sbwalker/dev
include CopyLocalLockFileAssemblies property in Client project in Default Module Template
2023-09-27 16:11:46 -04:00
9e2fa8d7f1 include CopyLocalLockFileAssemblies property in Client project in Default Module Template 2023-09-27 16:11:29 -04:00
74ba2f7283 Merge pull request #3340 from sbwalker/dev
add hyperlinks to product logos
2023-09-27 16:09:30 -04:00
7603b5c63f add hyperlinks to product logos 2023-09-27 16:09:19 -04:00
03566afe66 Merge pull request #3339 from sbwalker/dev
fix UrlCombine so that it handles leading and trailing path delimiters and returns proper paths
2023-09-27 16:07:32 -04:00
3c2e314e2d fix UrlCombine so that it handles leading and trailing path delimiters and returns proper paths 2023-09-27 16:07:13 -04:00
1fcf08b5cd Update README.md 2023-09-26 14:06:18 -04:00
c13db89ed4 Merge pull request #3330 from sbwalker/dev
4.0.5 data provider packages
2023-09-26 14:03:19 -04:00
e68ae9e8a6 4.0.5 data provider packages 2023-09-26 14:03:06 -04:00
fb252d6db3 Merge pull request #3329 from oqtane/master
4.0.5 release
2023-09-26 13:59:55 -04:00
df959353d6 Merge pull request #3328 from oqtane/dev
4.0.5 release
2023-09-26 13:59:29 -04:00
fbc443483d Merge pull request #3327 from sbwalker/dev
prepare for 4.0.5
2023-09-26 11:35:05 -04:00
00f1dbc3dd prepare for 4.0.5 2023-09-26 11:34:52 -04:00
c6021ff012 Merge pull request #3326 from ijaz-saeed/dev
Empty _themetype check to avoid page crash
2023-09-26 10:23:02 -04:00
7a8cfcee35 Empty _themetype check to avoid page crash
without this check, admin dashboard -> Page Management -> Edit page
is crashing
2023-09-26 18:23:29 +05:00
c15586a1c4 Update README.md 2023-09-25 16:00:23 -04:00
be1d124e78 Merge pull request #3322 from oqtane/master
Merge pull request #3321 from oqtane/dev
2023-09-25 15:55:54 -04:00
6fabf84d05 Merge pull request #3321 from oqtane/dev
4.0.4 release
2023-09-25 15:55:37 -04:00
3299275da2 Merge pull request #3320 from sbwalker/dev
resolve cookie configuration
2023-09-25 15:10:50 -04:00
5539243bf3 resolve cookie configuration 2023-09-25 15:10:36 -04:00
c3d330f500 Merge pull request #3319 from sbwalker/dev
refactor logic to redirect to external login provider when local site login is disabled
2023-09-25 13:53:26 -04:00
8c9e886136 refactor logic to redirect to external login provider when local site login is disabled 2023-09-25 13:53:05 -04:00
cfde9944f8 Merge pull request #3318 from sbwalker/dev
include Logout link in Control Panel for scenarios where a theme does not include a Login/Logout component
2023-09-25 12:42:59 -04:00
b35d778abe include Logout link in Control Panel for scenarios where a theme does not include a Login/Logout component 2023-09-25 12:42:39 -04:00
1e9ee81df3 Merge pull request #3317 from sbwalker/dev
updated database provider packages for 4.0.4
2023-09-25 10:01:59 -04:00
545d5730b6 database provider packages for 4.0.4 2023-09-25 09:59:07 -04:00
28e645f9ca Merge branch 'dev' of https://github.com/sbwalker/oqtane.framework into dev 2023-09-25 09:54:38 -04:00
5b5c8a4beb database provider packages for 4.0.4 2023-09-25 09:54:27 -04:00
6178be974a Merge pull request #3310 from leigh-pointer/LangSwitchPadding
upated LanguageSwitcher with end padding
2023-09-25 07:42:24 -04:00
7771c3159b upated LanguageSwitcher with end padding
When the LanguageSwitcher is dsiplayed the wsa no end padding on the button so it appeared flush with the following button.  Adding "pe-1" to the class corrects the issue
2023-09-24 18:49:50 +02:00
fc1e9c5d71 Merge pull request #3308 from sbwalker/dev
revert remaining logic which forced page themes to be a member of a site theme (4.0.0)
2023-09-23 11:47:08 -04:00
359de5b85e revert remaining logic which forced page themes to be a member of a site theme 2023-09-23 11:46:35 -04:00
68dc4904cf Merge pull request #3307 from sbwalker/dev
improve user import API
2023-09-23 11:37:43 -04:00
057fd02e26 improve user import API 2023-09-23 11:37:29 -04:00
5c86ef6682 Merge pull request #3303 from leigh-pointer/Retention-resx
Fix for  Site 'SMTP retention' is not in resource file (Issue #3301) and #3304 Users 'Import Users' resource
2023-09-23 09:12:33 -04:00
7c14bd799f Merge pull request #3305 from sbwalker/dev
improvements based on user import testing
2023-09-23 09:04:35 -04:00
edac046fcd improvements based on user import testing 2023-09-23 09:04:18 -04:00
8b23b386f7 ImportUsers Added to Resx and Razor resource key 2023-09-23 10:56:17 +02:00
e9bed59032 Fix for Site 'SMTP retention' is not in resource file (Issue #3301)
Fix for  Site 'SMTP retention' is not in resource file (Issue #3301)
2023-09-23 10:42:39 +02:00
54077be32c Merge pull request #3299 from sbwalker/dev
fix reference to DisplayName
2023-09-22 15:03:57 -04:00
30ad442dd1 fix reference to DisplayName 2023-09-22 15:03:45 -04:00
b0c677d5a7 Merge pull request #3298 from sbwalker/dev
removing href link from DisplayName as Email is now included
2023-09-22 14:44:36 -04:00
abe1b93e3c removing href link from DisplayName as Email is now included 2023-09-22 14:44:25 -04:00
fd699cbb73 Merge pull request #3293 from W6HBR/dev
Add Email column to User Manager screen
2023-09-22 14:31:56 -04:00
6f93ca11de Merge pull request #3297 from sbwalker/dev
fix issue where module migrations were not being executed on upgrade due to version not being overridden
2023-09-22 14:12:36 -04:00
916663b493 fix issue where module migrations were not being executed on upgrade due to version not being overridden 2023-09-22 14:12:18 -04:00
dbf2cddd87 Update Index.razor to include Email column. 2023-09-21 16:58:48 -07:00
d3c248cf5c Update Index.resx for Email text. 2023-09-21 16:57:01 -07:00
bd93a94bb7 Merge pull request #3292 from thabaum/patch-6
Fix typo in app.css
2023-09-21 19:42:02 -04:00
a46836e2a6 Fix typo in app.css 2023-09-21 16:34:57 -07:00
a1d88b4faa Merge pull request #3290 from sbwalker/dev
add named site option support to factory
2023-09-21 17:17:59 -04:00
29741c0ec8 add named site option support to factory 2023-09-21 17:17:46 -04:00
0b5fb92452 Merge pull request #3289 from sbwalker/dev
add support for named site options
2023-09-21 16:54:04 -04:00
65782e87c1 add support for named site options 2023-09-21 16:53:52 -04:00
6403df3330 Merge pull request #3288 from sbwalker/dev
set DefaultScheme for authentication
2023-09-21 14:45:09 -04:00
c6a8f5305a set DefaultScheme for authentication 2023-09-21 14:44:57 -04:00
d354e63267 Merge pull request #3285 from sbwalker/dev
fix localization in Profile Management
2023-09-21 10:02:36 -04:00
836b174d15 fix localization in Profile Management 2023-09-21 10:02:21 -04:00
4435f25ee6 Merge pull request #3275 from W6HBR/dev
Update to Profiles field list to include Title, Category and ViewOrder
2023-09-21 09:57:55 -04:00
4ab4bf4a3a Merge pull request #3279 from thabaum/patch-5
Updates Child Page Link Color in Theme Creator Template - Theme.css - Fixes #3278
2023-09-21 09:57:30 -04:00
d2f1d3c86c Merge pull request #3283 from sbwalker/dev
user import improvements
2023-09-21 09:37:44 -04:00
98257de005 user import improvements 2023-09-21 09:37:29 -04:00
5ea5c6ff7d Updates Child Page Link Color in Theme.css 2023-09-20 17:12:04 -07:00
441324d354 Update to Profiles field list to include Title, Category and ViewOrder
When adding additional profile fields, this enhancement makes it easier to see details of the profile fields for the purpose of field organization.
2023-09-20 14:59:44 -07:00
44f093b2fa Merge pull request #3266 from W6HBR/W6HBR-patch-1
Add password complexity requirements message to password Reset module.
2023-09-20 15:19:03 -04:00
38a16d1ae2 Merge pull request #3273 from sbwalker/dev
prepare for 4.0.4 release
2023-09-20 14:59:10 -04:00
6b9de40442 prepare for 4.0.4 release 2023-09-20 14:58:54 -04:00
d9c9eb9799 Merge pull request #3258 from leigh-pointer/ViewNotification
Removed the ReadOnly attribute in the Notifications Reply TextArea
2023-09-20 14:32:41 -04:00
e88c8eee0f Merge pull request #3271 from sbwalker/dev
fix #3231 - validate module description
2023-09-20 14:29:21 -04:00
a6ca843ced fix #3231 - validate module description 2023-09-20 14:29:06 -04:00
6967a5cc14 Merge pull request #3270 from sbwalker/dev
if site login is disabled redirect Login to external identity provider
2023-09-20 13:45:34 -04:00
e0ebf70907 if site login is disabled redirect Login to external identity provider 2023-09-20 13:45:19 -04:00
53d0202f3b Merge pull request #3268 from sbwalker/dev
prevent System Update in development environment
2023-09-20 12:34:20 -04:00
8daf38654d prevent System Update in development environment 2023-09-20 12:33:43 -04:00
17337440f2 Merge pull request #3267 from sbwalker/dev
add ability to import users
2023-09-20 10:44:05 -04:00
8e5e79a799 add ability to import users 2023-09-20 10:43:49 -04:00
5c1cf14303 Add password complexity requirements message to password Reset module.
This adds the same functionality that already exists in the UserProfile module.
2023-09-19 18:59:38 -07:00
f2ad796104 Merge pull request #3263 from sbwalker/dev
adjust error message text when adding users with duplicate email addresses
2023-09-19 09:00:11 -04:00
886df69058 adjust error message text when adding users with duplicate email addresses 2023-09-19 08:59:53 -04:00
703aa95e60 Merge pull request #3262 from sbwalker/dev
fix error when Allow User Login is set to false (ElementReference has not been configured correctly)
2023-09-19 08:49:21 -04:00
7919e14d90 fix error when Allow User Login is set to false (ElementReference has not been configured correctly) 2023-09-19 08:49:03 -04:00
c8ef191660 Merge pull request #3261 from sbwalker/dev
remove using statements added by Visual Studio
2023-09-18 09:47:52 -04:00
840b656d1c remove using statements added by Visual Studio 2023-09-18 09:47:38 -04:00
d28516b6b1 Merge pull request #3260 from sbwalker/dev
fix issue with module order in panes caused by transition from Admin to Default pane naming
2023-09-18 09:46:40 -04:00
5797bf549c fix issue with module order in panes caused by transition from Admin to Default pane naming 2023-09-18 09:46:16 -04:00
e1ea721ea9 Removed the ReadOnly attribute
The replay textarea can now be used to enter a reply to the notification.
2023-09-16 18:35:22 +02:00
5ed1284baf Merge pull request #3257 from sbwalker/dev
fix #3255 - behavior when moving pages to other parents
2023-09-15 16:23:28 -04:00
e507023a03 fix #3255 - behavior when moving pages to other parents 2023-09-15 16:23:14 -04:00
2b328b9bb2 Merge pull request #3254 from sbwalker/dev
fix #3253 - login needs to validate User.IsDeleted property
2023-09-13 10:02:27 -04:00
d155e13399 fix #3253 - login needs to validate User.IsDeleted property 2023-09-13 10:02:11 -04:00
6993d4782d Merge pull request #3244 from sbwalker/dev
improve polling in file upload to use actual file sizes to calculate duration
2023-09-08 17:16:28 -04:00
9267efce01 improve polling in file upload to use actual file sizes to calculate duration 2023-09-08 17:16:09 -04:00
8c88cec863 Merge pull request #3242 from sbwalker/dev
retain querystring parameters on url mapping redirect
2023-09-08 12:05:35 -04:00
b4b9976567 retain querystring parameters on url mapping redirect 2023-09-08 12:05:20 -04:00
3cbd820d9c Merge pull request #3240 from sbwalker/dev
fix #3236 - script injection issue
2023-09-08 11:25:40 -04:00
edd77b0222 fix #3236 - script injection issue 2023-09-08 11:25:27 -04:00
c0991df9a2 Merge pull request #3239 from sbwalker/dev
fix #3235 - </script> not being removed from Head Content
2023-09-08 11:09:46 -04:00
26921c899e fix #3235 - </script> not being removed from Head Content 2023-09-08 11:09:07 -04:00
a1e5912d37 Merge pull request #3238 from sbwalker/dev
fix #3229 - changing page path in Edot Page when invoked from Control Panel results in 404 when redirecting
2023-09-08 10:35:52 -04:00
037f1ec887 fix #3229 - changing page path in Edot Page when invoked from Control Panel results in 404 when redirecting 2023-09-08 10:35:22 -04:00
89ab44590b Merge pull request #3224 from sbwalker/dev
capitalize
2023-09-05 11:28:52 -04:00
4a20be8b34 capitalize 2023-09-05 11:28:36 -04:00
32a401ba67 Merge pull request #3223 from sbwalker/dev
display Site Guid in Site Settings
2023-09-05 11:27:30 -04:00
138c01aef8 display Site Guid in Site Settings 2023-09-05 11:27:17 -04:00
3e54bc9c77 Merge pull request #3221 from sbwalker/dev
add ability to validate and download packages
2023-09-02 14:08:34 -04:00
9966fc4651 add ability to validate and download packages 2023-09-02 14:08:21 -04:00
c9ed5ec98f Merge pull request #3220 from sbwalker/dev
improve help text for paclage name
2023-09-01 14:04:38 -04:00
1f4ae5dbfb improve help text for paclage name 2023-09-01 14:04:27 -04:00
d0d3cc8faa Merge pull request #3219 from sbwalker/dev
fix paths in Edit Page / Modules tab / Edit option
2023-09-01 13:57:26 -04:00
7513ae28f0 fix paths in Edit Page / Modules tab / Edit option 2023-09-01 13:57:14 -04:00
c151c6f55a Merge pull request #3218 from sbwalker/dev
include User Settings when calling UserService
2023-09-01 13:16:51 -04:00
d491aeeba6 include User Settings when calling UserService 2023-09-01 13:16:39 -04:00
ab93d0acea Merge pull request #3217 from sbwalker/dev
Fix #3215 - module creator creates incorrect ServerManagerType value
2023-09-01 13:03:01 -04:00
f40f3f934f Fix #3215 - module creator creates incorrect ServerManagerType value 2023-09-01 13:02:44 -04:00
4d57adb393 Merge pull request #3216 from sbwalker/dev
allow an administratot to browse to the SiteMap, handle default module panes and ordering for PageTemplates, allow trial products to be purchased
2023-09-01 12:14:24 -04:00
e91db18677 allow an administratot to browse to the SiteMap, handle default module panes and ordering for PageTemplates, allow trial products to be purchased 2023-09-01 12:14:04 -04:00
9a88d65ff7 Merge pull request #3212 from sbwalker/dev
need to use context rather than filter
2023-08-31 14:18:31 -04:00
211d8c7f19 need to use context rather than filter 2023-08-31 14:18:19 -04:00
763cdca114 Merge pull request #3211 from sbwalker/dev
Marketplace UI consistency
2023-08-31 13:55:08 -04:00
0d56d35646 Marketplace UI consistency 2023-08-31 13:54:56 -04:00
d1e80cb86a Update README.md 2023-08-29 14:01:00 -04:00
2e116489c8 Merge pull request #3208 from oqtane/master
Merge pull request #3207 from oqtane/dev
2023-08-29 13:54:50 -04:00
a94daa1216 Merge pull request #3207 from oqtane/dev
4.0.3 Release
2023-08-29 13:54:31 -04:00
ad57d665cc Merge pull request #3205 from sbwalker/dev
filter deleted pages and modules on the server
2023-08-28 17:15:45 -04:00
db2c42f0f4 filter deleted pages and modules on the server 2023-08-28 17:15:31 -04:00
b713cf86c7 Merge pull request #3204 from sbwalker/dev
fix page template update logic
2023-08-28 16:12:47 -04:00
11d02ccf44 fix page template update logic 2023-08-28 16:12:32 -04:00
54f3c8beac Merge pull request #3203 from sbwalker/dev
documentation for ReleaseVersions property
2023-08-28 15:27:47 -04:00
2ed51bf1f3 documentation for ReleaseVersions property 2023-08-28 15:27:33 -04:00
9f859f024c Merge pull request #3202 from sbwalker/dev
improve solution related to module installation issue where ModuleDefinition Version property was not being populated correctly (#3175)
2023-08-28 15:20:29 -04:00
d47175df8a improve solution related to module installation issue where ModuleDefinition Version property was not being populated correctly (#3175) 2023-08-28 15:20:09 -04:00
d6a80d36b1 Merge pull request #3200 from sbwalker/dev
abstract namespace specification to template.json so that module and theme templates can use their own naming conventions
2023-08-28 09:03:26 -04:00
a857e3f31e abstract namespace specification to template.json so that module and theme templates can use their own naming conventions 2023-08-28 09:03:05 -04:00
918dfb05cd Merge pull request #3199 from sbwalker/dev
prepare for 4.0.3 release
2023-08-28 07:58:49 -04:00
f22e0c4384 prepare for 4.0.3 release 2023-08-28 07:58:33 -04:00
daa08626ae Merge pull request #3198 from sbwalker/dev
support ~ notation for license urls
2023-08-28 07:49:22 -04:00
45592e6acd support ~ notation for license urls 2023-08-28 07:49:06 -04:00
6944eb3392 Merge pull request #3197 from sbwalker/dev
allow module or theme License property to be a Url
2023-08-27 08:57:50 -04:00
85f20aca58 allow module or theme License property to be a Url 2023-08-27 08:57:30 -04:00
ca8d8b3587 Merge pull request #3196 from sbwalker/dev
move password requirement localization back to Client project
2023-08-27 08:43:43 -04:00
74921a0686 move password requirement localization back to Client project 2023-08-27 08:43:23 -04:00
be4296f288 Merge pull request #3195 from sbwalker/dev
include only distinct items which have package names
2023-08-25 17:44:39 -04:00
836de56276 include only distinct items which have package names 2023-08-25 17:44:27 -04:00
ef60ac3836 Merge pull request #3194 from sbwalker/dev
optimize package update queries
2023-08-25 15:42:58 -04:00
bdd1ba05e8 optimize package update queries 2023-08-25 15:42:45 -04:00
f0a22d5517 Merge pull request #3193 from sbwalker/dev
remove Module Creator in favor of using Create Module in Module Management
2023-08-25 13:49:08 -04:00
417c8d7874 remove Module Creator in favor of using Create Module in Module Management 2023-08-25 13:48:51 -04:00
5a111cdb2a Merge pull request #3192 from sbwalker/dev
fix #3174 - display accurate password complexity requirements (this is now implemented in registration, user profiles, and user management - add/edit)
2023-08-25 13:31:22 -04:00
ef2f779f71 fix #3174 - display accurate password complexity requirements (this is now implemented in registration, user profiles, and user management - add/edit) 2023-08-25 13:31:02 -04:00
f0fd0db7bb Merge pull request #3191 from sbwalker/dev
simplified method names
2023-08-25 12:30:01 -04:00
b4ab45d2e7 simplified method names 2023-08-25 12:29:46 -04:00
73c4bcee30 Merge pull request #3185 from leigh-pointer/SchedularTimeControls
Fix for Schedular Allows incorrect Time format #3184
2023-08-25 10:54:13 -04:00
d4daf098e1 Merge pull request #3190 from sbwalker/dev
resolve #3189 - make path a querystring parameter
2023-08-25 09:49:22 -04:00
95de1fff69 resolve #3189 - make path a querystring parameter 2023-08-25 09:49:07 -04:00
96480b4382 Override and 2 new functions 2023-08-25 12:09:16 +02:00
14ae553557 Merge pull request #3188 from sbwalker/dev
remove unecessary NotMapped attribute
2023-08-24 16:42:30 -04:00
4de809e275 remove unecessary NotMapped attribute 2023-08-24 16:41:58 -04:00
a383382529 Merge pull request #3187 from sbwalker/dev
include PackageRegistryUrl in System Info
2023-08-24 16:39:51 -04:00
d2b3061ed9 include PackageRegistryUrl in System Info 2023-08-24 16:39:38 -04:00
073b10929a Fix for Schedular Allows incorrect Time format #3184
Modified code to accept correct time types.
2023-08-24 18:37:33 +02:00
6a2cfcab34 Merge pull request #3183 from leigh-pointer/IconResources
Update IconRescources
2023-08-24 11:38:09 -04:00
00c85ae7d3 Update IconResources.resx 2023-08-24 17:03:24 +02:00
d1b1e88389 Update IconRescources 2023-08-24 15:46:24 +02:00
5679ff5df9 Merge pull request #3182 from sbwalker/dev
add refresh button to module and theme installation page
2023-08-24 09:33:46 -04:00
3f28b39da0 add refresh button to module and theme installation page 2023-08-24 09:33:32 -04:00
ee21536742 Merge pull request #3181 from sbwalker/dev
reverse InputList Dictionary usage
2023-08-24 08:50:45 -04:00
6fafeedeb9 reverse InputList Dictionary usage 2023-08-24 08:50:33 -04:00
d86c53858e Merge pull request #3180 from sbwalker/dev
added defensive logic to resolve regression issue caused by #3175
2023-08-24 08:10:06 -04:00
0a22f80942 added defensive logic to resolve regression issue caused by #3175 2023-08-24 08:09:53 -04:00
fb247197e8 Merge pull request #3179 from sbwalker/dev
Allow InputList component to be localizable and support multiple instances on a page. Implement icon localization in Page Add/Edit using IconResources.resx
2023-08-23 16:43:36 -04:00
261ed05fa3 Allow InputList component to be localizable and support multiple instances on a page. Implement icon localization in Page Add/Edit using IconResources.resx 2023-08-23 16:43:14 -04:00
f5ce48a7c2 Merge pull request #3178 from sbwalker/dev
move icon loading reflection logic to server
2023-08-23 15:25:54 -04:00
82d128974c move icon loading reflection logic to server 2023-08-23 15:25:39 -04:00
34ae731959 Merge pull request #3169 from leigh-pointer/InputList
Add InputList control to select values from a dictionary of string
2023-08-23 14:29:38 -04:00
030a92d048 Merge pull request #3171 from vnetonline/fix-3163
[FIX] Added the ability to clear the Message in control panel
2023-08-23 14:29:28 -04:00
a327358b7a Merge pull request #3177 from sbwalker/dev
add support for background color padding during image resizing
2023-08-23 10:05:46 -04:00
9bd078d3e9 add support for background color padding during image resizing 2023-08-23 10:05:27 -04:00
412405e22c Merge pull request #3176 from sbwalker/dev
improve UX for Extend license option
2023-08-23 09:39:04 -04:00
2b20b34a74 improve UX for Extend license option 2023-08-23 09:38:48 -04:00
f269a07463 Merge pull request #3175 from sbwalker/dev
resolve module installation issue where ModuleDefinition Version property was not being populated correctly
2023-08-23 09:24:56 -04:00
d9948e8ee0 resolve module installation issue where ModuleDefinition Version property was not being populated correctly 2023-08-23 09:24:36 -04:00
ddb2fe4b03 Update Edit.razor
Moved Icon to the end of control
2023-08-23 07:49:50 +02:00
471f7184fb Added Icon Preview
Added an icon preview to the icon selecting control
2023-08-23 07:47:35 +02:00
92ea5da358 [FIX] Added the ability to clear the Message in control panel on open or close
This fix is related to issue #3163
2023-08-23 10:13:16 +10:00
09f1d3ca15 Add InputList control to select values from a dictionary of string
The control accepts a dictionary of string that can be searched using an input control.
Pages Add and Edit have implemented the control to render the list of Icons found in the Oqtane Shared namespace.
2023-08-22 09:50:59 +02:00
3729b8eac2 Merge pull request #3161 from rcpacheco/rcp_show_passwd_dbs
Update database installers to show/hide passwd
2023-08-21 11:02:29 -04:00
ca5f345414 Update database installers to show/hide passwd 2023-08-18 13:25:32 -06:00
15cf1e8a53 Merge pull request #3160 from sbwalker/dev
fix #3157 - provide support for asynchronous scheduled jobs
2023-08-18 13:34:52 -04:00
da74b0ece1 fix #3157 - provide support for asynchronous scheduled jobs 2023-08-18 13:34:38 -04:00
f50fe6f22f Merge pull request #3158 from sbwalker/dev
add support for * ImageSizes for folders
2023-08-18 10:55:25 -04:00
542eec2a9e add support for * ImageSizes for folders 2023-08-18 10:55:10 -04:00
4c5460fc9e Merge pull request #3156 from sbwalker/dev
migrate LocalizerFactory logic from SiteRouter to ModuleTitle component
2023-08-17 08:23:30 -04:00
394b8f1ce6 migrate LocalizerFactory logic from SiteRouter to ModuleTitle component 2023-08-17 08:23:17 -04:00
abeeda1a2b Merge pull request #3155 from sbwalker/dev
rollback #3125 and localize module component Title using LocalizerFactory
2023-08-17 07:56:58 -04:00
9e6ea3f486 rollback #3125 and localize module component Title using LocalizerFactory 2023-08-17 07:56:39 -04:00
2777a0946c Merge pull request #3154 from vnetonline/enhance-expando-object
[ENHANCE] - Change to ExpandoObject instead of an Anonymous Object
2023-08-17 07:47:57 -04:00
7f43c2659a Merge pull request #3153 from leigh-pointer/UserSort
Update the TH with link-primary Decoration classes
2023-08-17 07:46:39 -04:00
a4fa11c881 [ENHANCE] - Change to ExpandoObject instead of an Anonymous Object
Anonymous Object are not able to be used across assemblies however ExpandoObject is refer to #3145
2023-08-17 10:45:38 +10:00
8230e51c05 Update the TH with link-primary Decoration classes
This emphasizes that the header is clickable.
2023-08-16 19:09:12 +02:00
6e62d4791c Update the TH with link-primary Decoration classes
This emphasizes that the header is clickable.
2023-08-16 19:06:59 +02:00
ce8abdb8cd Merge pull request #3152 from sbwalker/dev
modify column size to prevent text wrapping
2023-08-16 12:25:53 -04:00
941bb7edaa modify column size to prevent text wrapping 2023-08-16 12:25:39 -04:00
1acbdf8b9e Merge pull request #3150 from alikoli/dev
Fix missing translations part 3
2023-08-15 15:51:58 -04:00
530804c847 Merge pull request #3151 from sbwalker/dev
In FileManager fix the id handling for the progressinfo and progressbar and include OnSelect events on Upload and Delete. Add missing backend implementation for AddFileAsync
2023-08-15 15:51:32 -04:00
b00b426e9c fix the id handling for the progressinfo and progressbar, include OnSelect events on Upload and Delete, add missing backend implementation for AddFileAsync 2023-08-15 15:50:35 -04:00
128f1753c0 Remove resx backups 2023-08-15 16:17:23 +02:00
1922629d27 Merge branch 'oqtane:dev' into dev 2023-08-15 16:12:21 +02:00
283a03a7cc Fix missing translations part 3 2023-08-15 16:09:59 +02:00
cdab26a97b Merge pull request #3146 from HonesDK/dev
Fixed localization in AuditInfo
2023-08-15 08:03:29 -04:00
5d3311c46b Merge pull request #3147 from sbwalker/dev
Notification job must convert \n to <br /> now that IsNodyHtml is set to True
2023-08-14 17:03:43 -04:00
7cbc21671b Notification job must convert \n to <br /> now that IsNodyHtml is set to True 2023-08-14 17:03:21 -04:00
d14d820e25 Merge branch 'dev' of https://github.com/HonesDK/oqtane.framework into dev 2023-08-14 16:54:41 +02:00
b567d02df2 Fixed localization in AuditInfo 2023-08-14 16:52:30 +02:00
dbb58541f2 Revert "Fixed modified by localization, and added DateTimeFormat to localization"
This reverts commit 4a8bcc6f71.
2023-08-14 16:51:21 +02:00
5a42caf4f8 Merge pull request #3143 from sbwalker/dev
add ability to get user based on username or email address
2023-08-13 08:35:24 -04:00
c2acd010ce add ability to get user based on username or email address 2023-08-13 08:35:03 -04:00
4a8bcc6f71 Fixed modified by localization, and added DateTimeFormat to localization 2023-08-13 10:11:29 +02:00
89e4dc7a08 Merge pull request #3141 from sbwalker/dev
change "price" to "from" to reflect multiple options
2023-08-12 10:31:56 -04:00
c344eedb12 change "price" to "from" to reflect multiple options 2023-08-12 10:31:44 -04:00
0aeb1a62b9 Merge pull request #3137 from sbwalker/dev
fix #3134 improve parsing of headcontent to handle space delimiters
2023-08-11 15:53:43 -04:00
316e0f5a68 fix #3134 improve parsing of headcontent to handle space delimiters 2023-08-11 15:53:32 -04:00
6f9314f76f Merge pull request #3133 from alikoli/dev
Fix missing translations part 2
2023-08-11 15:33:21 -04:00
6ef21795ba fix heading 2023-08-10 02:13:31 +02:00
e44855493e fix heading 2023-08-10 02:12:36 +02:00
101a3c8af5 Revert launchSettings 2023-08-10 02:03:46 +02:00
8fbbbce4ec Fix missing translations part 2 2023-08-10 01:52:46 +02:00
1de8bb850f Update README.md 2023-08-09 08:54:45 -04:00
25d09186fe Update README.md 2023-08-09 08:34:35 -04:00
9552b7c6f9 Update README.md 2023-08-09 08:34:13 -04:00
3d692e4198 Merge pull request #3132 from oqtane/master
Merge pull request #3131 from oqtane/dev
2023-08-09 08:22:44 -04:00
215ca9f755 Merge pull request #3131 from oqtane/dev
4.0.2 Release
2023-08-09 08:22:24 -04:00
7dbcb24f3d Merge pull request #3129 from alikoli/fix-missing-translations
Fix missing translations
2023-08-09 08:16:06 -04:00
18b4bd62f7 Update appsettings.json 2023-08-09 04:06:14 +02:00
20f3cf5327 Add empty appsettings 2023-08-09 03:58:34 +02:00
c1d35e8af5 Fix missing translations 2023-08-09 03:06:15 +02:00
e1cd318f61 Merge pull request #3128 from sbwalker/dev
fix installed cultures logic to recognize all satellite resources
2023-08-08 13:22:10 -04:00
4da0952091 fix installed cultures logic to recognize all satellite resources 2023-08-08 13:21:56 -04:00
a60f75f0a9 Merge pull request #3127 from sbwalker/dev
fix refresh logic in router
2023-08-08 13:07:43 -04:00
3814009ad9 fix refresh logic in router 2023-08-08 13:07:29 -04:00
fe7bb00112 Merge pull request #3125 from leigh-pointer/ModuleTitleLoc
Updated Module Title using SetModuleTitle on ModuleBase
2023-08-08 12:23:36 -04:00
2356753dfc Updated Module Title using SetModuleTitle on ModuleBase 2023-08-08 16:37:40 +02:00
bd23ec268b Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-08 15:34:25 +02:00
1d5f23c3c7 Merge pull request #3123 from leigh-pointer/TransKeys
Trans keys
2023-08-08 09:33:43 -04:00
f424bf53b3 Revert Title to "Folder Management"
This is due to the public virtual string Title can not use the Localize instance as it not yet created so error with null reference.
2023-08-08 15:30:39 +02:00
efbdf0006e Merge remote-tracking branch 'oqtane/dev' into TransKeys 2023-08-08 15:22:03 +02:00
625b173ee4 Merge pull request #3122 from leigh-pointer/TransKeys
Updated the Settings Heading
2023-08-08 08:05:21 -04:00
0a8d9bc3d3 Updated the Settings Heading 2023-08-08 14:01:45 +02:00
0d0909a461 Merge pull request #3120 from leigh-pointer/TransKeys
Translation for thread #3094
2023-08-08 07:55:35 -04:00
ebb0a538f8 Merge pull request #3121 from sbwalker/dev
improve sync service to always rely on server dates
2023-08-08 07:51:26 -04:00
337a566617 improve sync service to always rely on server dates 2023-08-08 07:51:07 -04:00
fecee7a12b Translation for thread #3094
Updated the Missing or incorrect keys
2023-08-08 13:30:10 +02:00
282ec99ce8 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-08 12:14:18 +02:00
17a4985ebe Merge pull request #3119 from sbwalker/dev
fix Section component localization
2023-08-07 17:11:36 -04:00
13b17d91a9 fix Section component localizatioon 2023-08-07 17:11:17 -04:00
5782005007 Merge pull request #3118 from sbwalker/dev
improve reload in router to prevent looping
2023-08-07 15:40:00 -04:00
258f2dbe8f improve reload in router to prevent looping 2023-08-07 15:39:44 -04:00
e7b35bd0c2 Merge pull request #3117 from sbwalker/dev
fix #3094 - localization of admin module titles
2023-08-07 12:14:11 -04:00
176bd229e6 fix #3094 - localization of admin module titles 2023-08-07 12:13:53 -04:00
c12f1251b0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-07 18:07:11 +02:00
0710736661 Merge pull request #3116 from sbwalker/dev
improve code documentation
2023-08-07 10:39:40 -04:00
51ebe520f4 improve code documentation 2023-08-07 10:39:24 -04:00
1ef566c824 Merge pull request #3115 from sbwalker/dev
fix issue where user could not be shared across multiple sites
2023-08-07 09:58:41 -04:00
96cf06fe8d fix issue where user could not be shared across multiple sites 2023-08-07 09:58:24 -04:00
38ebfdcd0c Merge pull request #3114 from sbwalker/dev
fix #3108 - raise reload event after user logs out
2023-08-07 09:34:37 -04:00
b5649e2a6f fix #3108 - raise reload event after user logs out 2023-08-07 09:34:20 -04:00
5fc6dadeae Merge pull request #3113 from sbwalker/dev
prepare for 4.0.2 release
2023-08-06 08:34:05 -04:00
22cfec9276 prepare for 4.0.2 release 2023-08-06 08:33:36 -04:00
0a82ec5f0a Update README.md 2023-08-06 08:32:05 -04:00
3bdd1f6c4f Merge pull request #3112 from sbwalker/dev
use new GetJsonAsync method in external module template
2023-08-06 08:19:16 -04:00
1e466dc1fe use new GetJsonAsync method in external module template 2023-08-06 08:17:56 -04:00
3b302d2f86 Merge pull request #3111 from sbwalker/dev
change Help button style
2023-08-06 07:58:50 -04:00
7f108eebf9 change Help button style 2023-08-06 07:58:27 -04:00
9d41fee2fe Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-06 09:39:41 +02:00
bef686f95a Merge pull request #3110 from sbwalker/dev
change defaultvalue parameter to defaultResult to make more intuitive
2023-08-05 16:35:04 -04:00
4bdcb974bd change defaultvalue parameter to defaultResult to make more intuitive 2023-08-05 16:34:41 -04:00
af042bd23c Merge pull request #3109 from sbwalker/dev
introduce new GetJsonAsync method with default value parameter
2023-08-05 09:01:03 -04:00
19e3cef7dd introduce new GetJsonAsync method with default value parameter 2023-08-05 09:00:38 -04:00
085ab4bfb4 Merge pull request #3107 from sbwalker/dev
add transparency support on image resizing
2023-08-04 16:07:50 -04:00
f7bd03d051 add transparency support on image resizing 2023-08-04 16:07:37 -04:00
b48d751717 Merge pull request #3106 from sbwalker/dev
update Module and Theme Install UI to match Marketplace - including logos and support for sorting
2023-08-04 15:17:36 -04:00
92a4a1b210 update Module and Theme Install UI to match Marketplace - including logos and support for sorting 2023-08-04 15:17:17 -04:00
6a57fcc04a Merge pull request #3103 from sbwalker/dev
fix GetFolderByPath to support root folder path
2023-08-03 17:29:26 -04:00
749e11762f fix GetFolderByPath to support root folder path 2023-08-03 17:29:02 -04:00
61817726c3 Merge pull request #3102 from sbwalker/dev
fix issue where meta name="description" tags were being excluded from output
2023-08-03 16:02:49 -04:00
808354e969 fix issue where meta name="description" tags were being excluded from output 2023-08-03 16:02:34 -04:00
d2f594ff4a Merge pull request #3101 from sbwalker/dev
fix #3069 - exclude templates from release packages
2023-08-03 15:27:05 -04:00
fa18467cdd fix #3069 - exclude templates from release packages 2023-08-03 15:26:23 -04:00
4c940d02ef Merge pull request #3100 from sbwalker/dev
add error handling and logging to folder creation logic
2023-08-03 14:48:58 -04:00
04202a6b70 Merge branch 'dev' of https://github.com/sbwalker/oqtane.framework into dev 2023-08-03 14:45:39 -04:00
4483901270 fix #3072 - add error handling and logging to folder creation logic 2023-08-03 14:45:27 -04:00
f02d894697 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-03 20:25:17 +02:00
2bc130856a Merge pull request #3047 from leigh-pointer/FixTemplateNULL
Fixes Module Creator problem  #3041
2023-08-03 14:23:55 -04:00
aa3a4dca65 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-03 18:53:56 +02:00
4f65931f51 Merge pull request #3099 from sbwalker/dev
fix #3065 - redirect user if they are logged in and navigating to Login page
2023-08-03 12:47:02 -04:00
93be61e483 fix #3065 - redirect user if they are logged in and navigating to Login page 2023-08-03 12:46:42 -04:00
cb7fe364bc Merge pull request #3098 from sbwalker/dev
remove unecessary using statement added by Visual Studio
2023-08-03 11:51:38 -04:00
9cdcb4b22c remove unecessary using statement added by Visual Studio 2023-08-03 11:51:26 -04:00
c49072f9b6 Merge pull request #3097 from sbwalker/dev
fix #3082 - handle username claim as "unique_name" with "name" as fallback, improve validation logic and logging
2023-08-03 11:49:24 -04:00
61df26b667 fix #3082 - handle username claim as "unique_name" with "name" as fallback, improve validation logic and logging 2023-08-03 11:48:59 -04:00
25c935f1db Merge pull request #3096 from sbwalker/dev
fix #3093 - user email links include extra "://"
2023-08-03 11:11:31 -04:00
6b982bc0c7 fix #3093 - user email links include extra "://" 2023-08-03 11:11:14 -04:00
caccc803d3 Merge pull request #3095 from sbwalker/dev
improve appsettings.json in Maui client
2023-08-03 10:15:26 -04:00
5be640632b improve appsettings.json in Maui client 2023-08-03 10:15:12 -04:00
40912f0ffd Merge pull request #3092 from sbwalker/dev
fix WebAssembly startup alias handling
2023-08-03 08:25:59 -04:00
d518860a34 fix WebAssembly startup alias handling 2023-08-03 08:25:44 -04:00
185617ed9e Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-03 05:39:32 +02:00
5ba96b627e Merge pull request #3090 from sbwalker/dev
support for appsettings.json in Maui app
2023-08-02 17:23:41 -04:00
edf955f4cd support for appsettings.json in Maui app 2023-08-02 17:23:27 -04:00
8d0b04668e Merge pull request #3089 from sbwalker/dev
add appicon to Maui client project
2023-08-02 17:11:47 -04:00
a72e5e02da add appicon to Maui client project 2023-08-02 17:11:33 -04:00
4740404c2e Merge pull request #3088 from sbwalker/dev
add version to support url
2023-08-02 16:46:11 -04:00
34253916ea add version to support url 2023-08-02 16:45:59 -04:00
1d77ba2694 Merge pull request #3055 from leigh-pointer/AutoCompleteRequired
Extended AutoComplete control to allow the Required attribute
2023-08-02 14:53:14 -04:00
765041c587 Merge pull request #3058 from vnetonline/fix-file-manager-show-folders
[ENHANCE] - #3057 FileManager when ShowFolders = False however a Folder is set by path
2023-08-02 14:52:04 -04:00
8c6bca7666 Merge pull request #3087 from sbwalker/dev
trim dependencies for Themes (related to #3079)
2023-08-02 14:49:06 -04:00
c1b1cef590 trim dependencies for Themes (related to #3079) 2023-08-02 14:48:51 -04:00
507f7804c3 Merge pull request #3079 from vnetonline/fix-3078
[FIX] - #3078 - ModuleInfo Dependency needs to be trimmed so there is no white space (credit @maxmontgmx))
2023-08-02 14:45:23 -04:00
453bacf9bd Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-02 20:13:17 +02:00
25778458c6 Merge pull request #3086 from sbwalker/dev
Fix #3068 - support microsites in .NET MAUI
2023-08-02 13:55:23 -04:00
7a42646bed Fix #3068 - support microsites in .NET MAUI 2023-08-02 13:55:01 -04:00
755b7034d2 Merge pull request #3085 from sbwalker/dev
Fix #3068 - support microsites in .NET MAUI
2023-08-02 13:54:47 -04:00
122fcfd701 Fix #3068 - support microsites in .NET MAUI 2023-08-02 13:53:55 -04:00
2c905eddc2 Merge branch 'fix-file-manager-show-folders' of https://github.com/vnetonline/oqtane.framework into fix-file-manager-show-folders 2023-08-01 21:12:02 +10:00
3e8eb9abb5 [ENHANCE] - #3057 FileManager when ShowFolders = false however a Folder is set by path 2023-08-01 21:11:54 +10:00
d2df3b2d9a ModuleInfo Dependency needs to be trimmed so there is no white space at the end (credit @maxmontgmx)) 2023-08-01 21:02:19 +10:00
2a0c983c2e Handle if Parameter is set dynamically 2023-07-26 11:28:14 +02:00
1319432fde [Fix] - #3057 FileManager when ShowFolders = false however a Folder is set by path 2023-07-24 08:19:06 +10:00
02e2aeb6d1 Extended control to set the Required attribute
The control now allow the required attribute to be set.  This will now give better UX feedback.
2023-07-20 13:56:29 +02:00
5e35edd976 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-20 13:34:27 +02:00
4d63c6266c Merge pull request #3053 from sbwalker/dev
disable ServiceBase logic which does not work with legacy ControllerRoutes.Default routes (modules created prior to 2.1)
2023-07-19 20:07:46 -04:00
48f8d41993 disable ServiceBase logic which does not work with legacy ControllerRoutes.Default routes (modules created prior to 2.1) 2023-07-19 20:07:27 -04:00
0b41ccad83 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 22:39:24 +02:00
f994afbc11 Merge pull request #3051 from sbwalker/dev
fix #3048 - uploading to Packages folder showing unsuccessful message
2023-07-19 16:27:39 -04:00
5dea783677 fix #3048 - uploading to Packages folder showing unsuccessful message 2023-07-19 16:27:26 -04:00
347ef9a1d0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 22:16:18 +02:00
739aa5311c Merge pull request #3050 from sbwalker/dev
fix #3046 - user folders displayed in folder lists
2023-07-19 16:15:04 -04:00
805018286c fix #3046 - user folders displayed in folder lists 2023-07-19 16:14:48 -04:00
45f2a47ba5 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 21:53:10 +02:00
fe503b7bee Merge pull request #3049 from sbwalker/dev
FileManager modification to support ShowFolders = False
2023-07-19 15:45:25 -04:00
6736570ee7 FileManager modification to support ShowFolders = False 2023-07-19 15:45:12 -04:00
88c06eea6e Fixes Module Creator problem #3041
This fix is to handle a returned null from the database.  This issue came to light while using Postgresql
2023-07-19 09:58:36 +02:00
13693ef9e5 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 09:53:29 +02:00
3a54326e73 Update README.md 2023-07-18 12:42:49 -04:00
941c1c8a45 Update README.md 2023-07-18 12:35:27 -04:00
d328058075 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-18 17:40:29 +02:00
9108eb5616 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-18 12:37:53 +02:00
ea45044dc7 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-17 16:48:24 +02:00
6b16808207 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-17 11:01:10 +02:00
4071b285ce Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-15 12:34:10 +02:00
7bc80f0814 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-13 17:21:55 +02:00
6b57202421 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-13 11:40:58 +02:00
ca3edb6687 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 15:22:30 +02:00
7428f87899 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 14:47:53 +02:00
02481560a9 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 08:29:05 +02:00
2d66063763 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 21:19:27 +02:00
9158e24295 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 17:41:35 +02:00
118e177756 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 14:37:16 +02:00
88ac82a013 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-09 21:12:47 +02:00
c025013ca8 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-30 22:04:18 +02:00
229 changed files with 5442 additions and 2665 deletions

View File

@ -2,7 +2,6 @@
@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)
{ {
@ -50,29 +49,21 @@
[Parameter] [Parameter]
public string AuthorizationToken { get; set; } public string AuthorizationToken { get; set; }
[CascadingParameter]
HttpContext HttpContext { 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; }
private IHttpContextAccessor accessor;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
SiteState.RemoteIPAddress = RemoteIPAddress; SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken; SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken; SiteState.AuthorizationToken = AuthorizationToken;
SiteState.IsPrerendering = (HttpContext != null) ? true : false;
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)

View File

@ -0,0 +1,14 @@
namespace Oqtane
{
/// <summary>
/// Dummy class used to collect shared resource strings for this application
/// </summary>
/// <remarks>
/// This class is mostly used with IStringLocalizer and IHtmlLocalizer interfaces.
/// The class must reside at the project root.
/// </remarks>
public class IconResources
{
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Installer.Controls @namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl @implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label> <Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
@ -28,7 +29,10 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label> <Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" /> <div class="input-group">
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div> </div>
</div> </div>
@ -38,6 +42,13 @@
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm"); private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
private string _uid = String.Empty; private string _uid = String.Empty;
private string _pwd = String.Empty; private string _pwd = String.Empty;
private string _passwordType = "password";
private string _togglePassword = string.Empty;
protected override void OnInitialized()
{
_togglePassword = SharedLocalizer["ShowPassword"];
}
public string GetConnectionString() public string GetConnectionString()
{ {
@ -55,4 +66,18 @@
return connectionString; return connectionString;
} }
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
} }

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Installer.Controls @namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl @implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<PostgreSQLConfig> Localizer @inject IStringLocalizer<PostgreSQLConfig> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label> <Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
@ -40,7 +41,10 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label> <Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" /> <div class="input-group">
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div> </div>
</div> </div>
} }
@ -52,6 +56,13 @@
private string _security = "integrated"; private string _security = "integrated";
private string _uid = String.Empty; private string _uid = String.Empty;
private string _pwd = String.Empty; private string _pwd = String.Empty;
private string _passwordType = "password";
private string _togglePassword = string.Empty;
protected override void OnInitialized()
{
_togglePassword = SharedLocalizer["ShowPassword"];
}
public string GetConnectionString() public string GetConnectionString()
{ {
@ -80,4 +91,18 @@
return connectionString; return connectionString;
} }
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
} }

View File

@ -35,7 +35,10 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label> <Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" /> <div class="input-group">
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div> </div>
</div> </div>
} }
@ -67,9 +70,16 @@
private string _security = "integrated"; private string _security = "integrated";
private string _uid = String.Empty; private string _uid = String.Empty;
private string _pwd = String.Empty; private string _pwd = String.Empty;
private string _passwordType = "password";
private string _togglePassword = string.Empty;
private string _encryption = "false"; private string _encryption = "false";
private string _trustservercertificate = "false"; private string _trustservercertificate = "false";
protected override void OnInitialized()
{
_togglePassword = SharedLocalizer["ShowPassword"];
}
public string GetConnectionString() public string GetConnectionString()
{ {
var connectionString = String.Empty; var connectionString = String.Empty;
@ -92,4 +102,18 @@
return connectionString; return connectionString;
} }
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
} }

View File

@ -15,7 +15,7 @@
<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 (.NET 7)</div> <div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 8)</div>
</div> </div>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />

View File

@ -12,11 +12,12 @@
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
string url = NavigateUrl(p.Path); string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center mb-3"> <p class="col-md-2 mx-auto text-center mb-3">
<NavLink class="nav-link text-primary" href="@url" Match="NavLinkMatch.All"> <NavLink class="nav-link text-primary" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name] <h2><span class="@p.Icon" aria-hidden="true"></span></h2>
<p class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</p>
</NavLink> </NavLink>
</div> </p>
} }
} }
</div> </div>

View File

@ -48,7 +48,7 @@
</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="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,x200,200x)" ResourceKey="ImageSizes">Image Sizes: </Label> <Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" /> <input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
</div> </div>
@ -67,6 +67,7 @@
</div> </div>
</div> </div>
</form> </form>
<br /><br />
@if (!_isSystem) @if (!_isSystem)
{ {
@ -79,8 +80,7 @@
@((MarkupString)"&nbsp;") @((MarkupString)"&nbsp;")
<ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" /> <ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
} }
<br /> <br /><br />
<br />
@if (PageState.QueryString.ContainsKey("id")) @if (PageState.QueryString.ContainsKey("id"))
{ {
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo> <AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>

View File

@ -8,27 +8,28 @@
@if (_files != null) @if (_files != null)
{ {
<div class="container"> <div class="row">
<div class="row mb-1 align-items-center"> <div class="col-md mb-1">
<div class="col-sm-2"> <ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
<label class="control-label">@Localizer["Folder"] </label>
</div> </div>
<div class="col-sm-6"> <div class="col-md-8 mb-1">
<div class="input-group">
<span class="input-group-text">@Localizer["Folder"]:</span>
<select class="form-select" @onchange="(e => FolderChanged(e))"> <select class="form-select" @onchange="(e => FolderChanged(e))">
@foreach (Folder folder in _folders) @foreach (Folder folder in _folders)
{ {
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
} }
</select> </select>
</div>
<div class="col-sm-4">
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />&nbsp; <ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />&nbsp;
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />&nbsp; </div>
</div>
<div class="col-md mb-1 text-end">
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" /> <ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
</div> </div>
</div> </div>
</div>
<Pager Items="@_files"> <Pager Items="@_files" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -56,7 +56,7 @@
<input id="starting" type="date" class="form-control" @bind="@_startDate" /> <input id="starting" type="date" class="form-control" @bind="@_startDate" />
</div> </div>
<div class="col"> <div class="col">
<input id="starting" type="text" class="form-control" placeholder="hh:mm" @bind="@_startTime" /> <input id="starting" type="time" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
</div> </div>
</div> </div>
</div> </div>
@ -69,7 +69,7 @@
<input id="ending" type="date" class="form-control" @bind="@_endDate" /> <input id="ending" type="date" class="form-control" @bind="@_endDate" />
</div> </div>
<div class="col"> <div class="col">
<input id="ending" type="text" class="form-control" placeholder="hh:mm" @bind="@_endTime" /> <input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
</div> </div>
</div> </div>
</div> </div>
@ -82,7 +82,7 @@
<input id="next" type="date" class="form-control" @bind="@_nextDate" /> <input id="next" type="date" class="form-control" @bind="@_nextDate" />
</div> </div>
<div class="col"> <div class="col">
<input id="next" type="text" class="form-control" placeholder="hh:mm" @bind="@_nextTime" /> <input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
</div> </div>
</div> </div>
</div> </div>
@ -106,12 +106,12 @@
private string _interval = string.Empty; private string _interval = string.Empty;
private string _frequency = string.Empty; private string _frequency = string.Empty;
private DateTime? _startDate = null; private DateTime? _startDate = null;
private string _startTime = string.Empty; private DateTime? _startTime = null;
private DateTime? _endDate = null; private DateTime? _endDate = null;
private string _endTime = string.Empty; private DateTime? _endTime = null;
private string _retentionHistory = string.Empty; private string _retentionHistory = string.Empty;
private DateTime? _nextDate = null; private DateTime? _nextDate = null;
private string _nextTime = string.Empty; private DateTime? _nextTime = null;
private string createdby; private string createdby;
private DateTime createdon; private DateTime createdon;
private string modifiedby; private string modifiedby;
@ -132,10 +132,13 @@
_isEnabled = job.IsEnabled.ToString(); _isEnabled = job.IsEnabled.ToString();
_interval = job.Interval.ToString(); _interval = job.Interval.ToString();
_frequency = job.Frequency; _frequency = job.Frequency;
(_startDate, _startTime) = Utilities.UtcAsLocalDateAndTime(job.StartDate); _startDate = Utilities.UtcAsLocalDate(job.StartDate);
(_endDate, _endTime) = Utilities.UtcAsLocalDateAndTime(job.EndDate); _startTime = Utilities.UtcAsLocalDateTime(job.StartDate);
_endDate = Utilities.UtcAsLocalDate(job.EndDate);
_endTime = Utilities.UtcAsLocalDateTime(job.EndDate);
_retentionHistory = job.RetentionHistory.ToString(); _retentionHistory = job.RetentionHistory.ToString();
(_nextDate, _nextTime) = Utilities.UtcAsLocalDateAndTime(job.NextExecution); _nextDate = Utilities.UtcAsLocalDate(job.NextExecution);
_nextTime = Utilities.UtcAsLocalDateTime(job.NextExecution);
createdby = job.CreatedBy; createdby = job.CreatedBy;
createdon = job.CreatedOn; createdon = job.CreatedOn;
modifiedby = job.ModifiedBy; modifiedby = job.ModifiedBy;

View File

@ -11,11 +11,11 @@
else else
{ {
<ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" /> <ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" />
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">Refresh</button> <button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">@Localizer["Refresh.Text"]</button>
<br /> <br />
<br /> <br />
<Pager Items="@_jobs"> <Pager Items="@_jobs" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -16,7 +16,7 @@
else else
{ {
<TabStrip> <TabStrip>
<TabPanel Name="Manage" ResourceKey="Manage"> <TabPanel Name="Manage" ResourceKey="Manage" Heading="Manage">
<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">
@ -45,7 +45,7 @@ else
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form> </form>
</TabPanel> </TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host"> <TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host" Heading="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 translations. Once they are uploaded click Install." 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>

View File

@ -14,7 +14,7 @@ else
{ {
<ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" /> <ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" />
<Pager Items="@_languages"> <Pager Items="@_languages" SearchProperties="Name,Code">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
@ -146,7 +146,7 @@ else
{ {
try try
{ {
_package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, version); _package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, version, false);
if (_package != null) if (_package != null)
{ {
StateHasChanged(); StateHasChanged();
@ -168,7 +168,7 @@ else
{ {
try try
{ {
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version);
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(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
_package = null; _package = null;

View File

@ -3,6 +3,7 @@
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@inject ISettingService SettingService
@inject IServiceProvider ServiceProvider @inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -11,9 +12,6 @@
<Authorizing> <Authorizing>
<text>...</text> <text>...</text>
</Authorizing> </Authorizing>
<Authorized>
<div>@Localizer["Info.SignedIn"]</div>
</Authorized>
<NotAuthorized> <NotAuthorized>
@if (!twofactor) @if (!twofactor)
{ {
@ -38,10 +36,13 @@
</div> </div>
</div> </div>
<div class="form-group mt-2"> <div class="form-group mt-2">
@if (!_alwaysremember)
{
<div class="form-check"> <div class="form-check">
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" /> <input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label> <Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
</div> </div>
}
</div> </div>
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button> <button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
@ -80,6 +81,7 @@
private string _passwordtype = "password"; private string _passwordtype = "password";
private string _togglepassword = string.Empty; private string _togglepassword = string.Empty;
private bool _remember = false; private bool _remember = false;
private bool _alwaysremember = false;
private string _code = string.Empty; private string _code = string.Empty;
private string _returnUrl = string.Empty; private string _returnUrl = string.Empty;
@ -95,18 +97,11 @@
{ {
try try
{ {
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
{
_allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]);
}
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))
{
_allowexternallogin = true;
}
if (PageState.QueryString.ContainsKey("returnurl")) if (PageState.QueryString.ContainsKey("returnurl"))
{ {
_returnUrl = PageState.QueryString["returnurl"]; _returnUrl = PageState.QueryString["returnurl"];
@ -160,6 +155,10 @@
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info); AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
} }
} }
if (PageState.Site.Settings.TryGetValue("LoginOptions:AlwaysRemember", out string alwaysRememberStr))
{
_alwaysremember = Convert.ToBoolean(alwaysRememberStr);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -170,10 +169,16 @@
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender && PageState.User == null) if (firstRender && PageState.User == null && _allowsitelogin)
{ {
await username.FocusAsync(); await username.FocusAsync();
} }
// redirect logged in user to specified page
if (PageState.User != null)
{
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
} }
private async Task Login() private async Task Login()
@ -189,6 +194,13 @@
if (!twofactor) if (!twofactor)
{ {
bool alwaysRemember = false;
if (PageState.Site.Settings.TryGetValue("LoginOptions:AlwaysRemember", out string alwaysRememberStr))
{
alwaysRemember = Convert.ToBoolean(alwaysRememberStr);
}
bool remember = alwaysRemember || _remember;
_remember = remember;
user = await UserService.LoginUserAsync(user, hybrid, _remember); user = await UserService.LoginUserAsync(user, hybrid, _remember);
} }
else else
@ -203,8 +215,7 @@
if (hybrid) if (hybrid)
{ {
// hybrid apps utilize an interactive login // hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged(); authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true)); NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
} }
@ -218,7 +229,7 @@
} }
else else
{ {
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired) if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired)
{ {
twofactor = true; twofactor = true;
validated = false; validated = false;

View File

@ -1,203 +0,0 @@
@namespace Oqtane.Modules.Admin.ModuleCreator
@inherits ModuleBase
@using System.Text.RegularExpressions
@inject NavigationManager NavigationManager
@inject IModuleDefinitionService ModuleDefinitionService
@inject IModuleService ModuleService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (string.IsNullOrEmpty(_moduledefinitionname) && _templates != null)
{
<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="owner" HelpText="Enter the name of the organization who is developing this module. It should not contain spaces or punctuation." ResourceKey="OwnerName">Owner Name: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation." ResourceKey="ModuleName">Module Name: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_module" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
<div class="col-sm-9">
<textarea id="description" class="form-control" @bind="@_description" rows="3" ></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="template" HelpText="Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server." ResourceKey="Template">Template: </Label>
<div class="col-sm-9">
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))" required>
<option value="-">&lt;@Localizer["Template.Select"]&gt;</option>
@foreach (Template template in _templates)
{
<option value="@template.Name">@template.Title</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<div class="col-sm-9">
<select id="reference" class="form-select" @bind="@_reference" required>
@foreach (string version in _versions)
{
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
{
<option value="@(version)">@(version)</option>
}
}
<option value="local">@SharedLocalizer["LocalVersion"]</option>
</select>
</div>
</div>
@if (!string.IsNullOrEmpty(_location))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_location" readonly />
</div>
</div>
}
</div>
</form>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["Module.Create"]</button>
}
else
{
<button type="button" class="btn btn-success" @onclick="ActivateModule">@Localizer["Module.Activate"]</button>
}
@code {
private ElementReference form;
private bool validated = false;
private string _moduledefinitionname = string.Empty;
private string _owner = string.Empty;
private string _module = string.Empty;
private string _description = string.Empty;
private List<Template> _templates;
private string _template = "-";
private string[] _versions;
private string _reference = "local";
private string _minversion = "2.0.0";
private string _location = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override void OnInitialized()
{
_moduledefinitionname = SettingService.GetSetting(ModuleState.Settings, "ModuleDefinitionName", "");
if (string.IsNullOrEmpty(_moduledefinitionname))
{
AddModuleMessage(Localizer["Info.Module.Creator"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Info.Module.Activate"], MessageType.Info);
}
}
protected override async Task OnParametersSetAsync()
{
try
{
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Module Creator");
}
}
private async Task CreateModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
{
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Creating Module");
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task ActivateModule()
{
try
{
if (!string.IsNullOrEmpty(_moduledefinitionname))
{
Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
module.ModuleDefinitionName = _moduledefinitionname;
await ModuleService.UpdateModuleAsync(module);
NavigationManager.NavigateTo(NavigateUrl(), true);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Activating Module");
}
}
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private void TemplateChanged(ChangeEventArgs e)
{
_template = (string)e.Value;
_minversion = "2.0.0";
if (_template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
}
GetLocation();
}
private void GetLocation()
{
_location = string.Empty;
if (_owner != "" && _module != "" && _template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + ".Module." + _module;
}
StateHasChanged();
}
}

View File

@ -1,17 +0,0 @@
using Oqtane.Documentation;
using Oqtane.Models;
namespace Oqtane.Modules.Admin.ModuleCreator
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Module Creator",
Description = "Enables software developers to quickly create modules by automating many of the initial module creation tasks",
Version = "1.0.0",
Categories = "Developer"
};
}
}

View File

@ -8,67 +8,121 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip> <TabStrip>
<TabPanel Name="Download" ResourceKey="Download"> <TabPanel Name="Download" ResourceKey="Download" Heading="Download">
<div class="row justify-content-center mb-3"> <div class="row justify-content-center mb-3">
<div class="col-sm-6"> <div class="text-center">
<div class="form-check form-check-inline">
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
</div>
<div class="form-check form-check-inline">
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group"> <div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))"> <span class="input-group-text">@Localizer["Product"]</span>
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" /> <input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button> <button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button> <button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<button type="button" class="btn btn-primary ms-2" @onclick="Refresh"><span class="@Icons.Reload" aria-hidden="true"></span></button>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-3">
@if (_packages != null) <div class="col">
@if (_initialized)
{ {
if (_packages.Count > 0) <br />
<div class="row mb-3">
<div class="col-sm-4">
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
</div>
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-4">
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
@if (_price == "free")
{ {
<Pager Items="@_packages"> <option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
} }
</td> <option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
<td style="width: 1px; vertical-align: middle;"> @if (_price == "paid")
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{ {
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a> <option value="price">@SharedLocalizer["Search.Price"]</option>
}
</select>
</div>
</div>
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6" CurrentPage="@_page.ToString()" OnPageChange="OnPageChange">
<Row>
<div class="m-2 p-2 d-flex justify-content-center">
<div class="container-fluid px-0">
<div class="row g-0 mb-2">
<div class="col-4">
<a href="@context.ProductUrl" target="_blank">
@if (context.LogoUrl != null)
{
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
} }
else else
{
<img src="/package.png" class="img-fluid" alt="@context.Name" />
}
</a>
</div>
<div class="col-8 text-end">
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<br /><small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
}
@if (context.Price == null)
{
<br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
}
else
{
<br /><small>@SharedLocalizer["From"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
@((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
}
</div>
</div>
<div class="row g-0">
<div class="col">
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<br />
@if (!string.IsNullOrEmpty(context.PackageUrl))
{ {
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button> <button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
} }
</td> @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
</div>
</div>
</div>
</Row> </Row>
</Pager> </Pager>
} }
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div> </div>
} </div>
}
<br /> <br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" /> <ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel> </TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload"> <TabPanel Name="Upload" ResourceKey="Upload" Heading="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>
@ -116,8 +170,11 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {
private bool _initialized = false;
private int _page = 1;
private List<Package> _packages; private List<Package> _packages;
private string _price = "free"; private string _price = "free";
private string _sort = "popularity";
private string _search = ""; private string _search = "";
private string _productname = ""; private string _productname = "";
private string _packageid = ""; private string _packageid = "";
@ -131,6 +188,7 @@
try try
{ {
await LoadModuleDefinitions(); await LoadModuleDefinitions();
_initialized = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -141,8 +199,10 @@
private async Task LoadModuleDefinitions() private async Task LoadModuleDefinitions()
{ {
ShowProgressIndicator();
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, "", _sort);
if (_packages != null) if (_packages != null)
{ {
@ -154,46 +214,44 @@
} }
} }
} }
HideProgressIndicator();
} }
private async void PriceChanged(ChangeEventArgs e) private async void PriceChanged(string price)
{ {
try _price = price;
{ _sort = "popularity";
_price = (string)e.Value;
_search = "";
await LoadModuleDefinitions(); await LoadModuleDefinitions();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
private async Task Search() private async Task Search()
{
try
{ {
await LoadModuleDefinitions(); await LoadModuleDefinitions();
} }
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
private async Task Reset() private async Task Reset()
{ {
try _page = 1;
{
_search = ""; _search = "";
await LoadModuleDefinitions(); await LoadModuleDefinitions();
} }
catch (Exception ex)
private async Task Refresh()
{ {
await logger.LogError(ex, "Error On Reset"); await LoadModuleDefinitions();
} }
private void OnPageChange(int page)
{
_page = page;
}
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadModuleDefinitions();
} }
private void HideModal() private void HideModal()
@ -207,7 +265,7 @@
{ {
try try
{ {
var package = await PackageService.GetPackageAsync(packageid, version); var package = await PackageService.GetPackageAsync(packageid, version, false);
if (package != null) if (package != null)
{ {
_productname = package.Name; _productname = package.Name;
@ -236,7 +294,7 @@
{ {
try try
{ {
await PackageService.DownloadPackageAsync(_packageid, _packageversion, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(_packageid, _packageversion);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion); await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
_productname = ""; _productname = "";

View File

@ -88,9 +88,12 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override void OnInitialized() protected override void OnInitialized()
{
if (!NavigationManager.BaseUri.Contains("localhost:"))
{ {
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info); AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
} }
}
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
@ -115,12 +118,20 @@
{ {
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-") if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
{ {
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference }; if (IsValidXML(_description))
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
GetLocation(); GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
} }
else else
{
AddModuleMessage(Localizer["Message.Require.ValidDescription"], MessageType.Warning);
}
}
else
{ {
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning); AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
} }
@ -142,6 +153,12 @@
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
} }
private bool IsValidXML(string description)
{
// must contain letters, digits, or spaces
return Regex.IsMatch(description, "^[A-Za-z0-9 .,!?]+$");
}
private void TemplateChanged(ChangeEventArgs e) private void TemplateChanged(ChangeEventArgs e)
{ {
_template = (string)e.Value; _template = (string)e.Value;
@ -160,8 +177,14 @@
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; if (!string.IsNullOrEmpty(template.Namespace))
{
_location = template.Location + template.Namespace.Replace("[Owner]", _owner).Replace("[Module]", _module);
}
else
{
_location = template.Location + _owner + ".Module." + _module;
}
} }
StateHasChanged(); StateHasChanged();
} }

View File

@ -12,7 +12,7 @@
@if (_initialized) @if (_initialized)
{ {
<TabStrip> <TabStrip>
<TabPanel Name="Definition" ResourceKey="Definition"> <TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
<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">
@ -59,9 +59,26 @@
</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="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label> <Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification." ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@if (!string.IsNullOrEmpty(_packagename))
{
<div class="input-group">
<input id="packagename" class="form-control" @bind="@_packagename" disabled /> <input id="packagename" class="form-control" @bind="@_packagename" disabled />
@if (string.IsNullOrEmpty(_packageurl))
{
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
}
else
{
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
}
</div>
}
else
{
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
}
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -85,13 +102,14 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label> <Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
{
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Modules/" + Utilities.GetTypeName(_moduledefinitionname))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
}
else
{
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea> <textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div> }
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtimes" HelpText="The Blazor runtimes which this module supports" ResourceKey="Runtimes">Runtimes: </Label>
<div class="col-sm-9">
<input id="runtimes" class="form-control" @bind="@_runtimes" disabled />
</div> </div>
</div> </div>
</div> </div>
@ -103,7 +121,7 @@
<br /> <br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo> <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</TabPanel> </TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions"> <TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
@ -113,7 +131,7 @@
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel> </TabPanel>
<TabPanel Name="Translations" ResourceKey="Translations"> <TabPanel Name="Translations" ResourceKey="Translations" Heading="Translations">
@if (_languages != null && _languages.Count > 0) @if (_languages != null && _languages.Count > 0)
{ {
<Pager Items="@_languages"> <Pager Items="@_languages">
@ -190,7 +208,7 @@
</p> </p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button> <button type="button" class="btn btn-success" @onclick="DownloadTranslation">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div> </div>
</div> </div>
@ -212,11 +230,11 @@
private string _moduledefinitionname = ""; private string _moduledefinitionname = "";
private string _version; private string _version;
private string _packagename = ""; private string _packagename = "";
private string _packageurl = "";
private string _owner = ""; private string _owner = "";
private string _url = ""; private string _url = "";
private string _contact = ""; private string _contact = "";
private string _license = ""; private string _license = "";
private string _runtimes = "";
private List<Permission> _permissions = null; private List<Permission> _permissions = null;
private string _createdby; private string _createdby;
private DateTime _createdon; private DateTime _createdon;
@ -252,7 +270,6 @@
_url = moduleDefinition.Url; _url = moduleDefinition.Url;
_contact = moduleDefinition.Contact; _contact = moduleDefinition.Contact;
_license = moduleDefinition.License; _license = moduleDefinition.License;
_runtimes = moduleDefinition.Runtimes;
_permissions = moduleDefinition.PermissionList; _permissions = moduleDefinition.PermissionList;
_createdby = moduleDefinition.CreatedBy; _createdby = moduleDefinition.CreatedBy;
_createdon = moduleDefinition.CreatedOn; _createdon = moduleDefinition.CreatedOn;
@ -365,7 +382,7 @@
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version; var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
try try
{ {
_package = await PackageService.GetPackageAsync(packagename, version); _package = await PackageService.GetPackageAsync(packagename, version, false);
if (_package != null) if (_package != null)
{ {
StateHasChanged(); StateHasChanged();
@ -383,11 +400,11 @@
} }
} }
private async Task DownloadPackage() private async Task DownloadTranslation()
{ {
try try
{ {
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version);
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(string.Format(Localizer["Success.Translation.Download"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Translation.Download"], NavigateUrl("admin/system")), MessageType.Success);
_package = null; _package = null;
@ -399,4 +416,28 @@
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error); AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
} }
} }
private async Task ValidatePackage()
{
try
{
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
{
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
}
else
{
_packageurl = package.PackageUrl;
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
}
}
} }

View File

@ -37,7 +37,7 @@ else
</div> </div>
</div> </div>
<Pager Items="@_moduleDefinitions.Where(item => item.Categories.Contains(_category))"> <Pager Items="@_moduleDefinitions">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
@ -80,7 +80,7 @@ else
} }
</td> </td>
<td> <td>
@((MarkupString)SupportLink(context.PackageName)) @((MarkupString)SupportLink(context.PackageName, context.Version))
</td> </td>
<td> <td>
@((MarkupString)PurchaseLink(context.PackageName)) @((MarkupString)PurchaseLink(context.PackageName))
@ -99,6 +99,7 @@ else
} }
@code { @code {
private List<ModuleDefinition> _allModuleDefinitions;
private List<ModuleDefinition> _moduleDefinitions; private List<ModuleDefinition> _moduleDefinitions;
private List<Package> _packages; private List<Package> _packages;
private List<string> _categories = new List<string>(); private List<string> _categories = new List<string>();
@ -110,9 +111,9 @@ else
{ {
try try
{ {
_moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module"); _categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
_categories = _moduleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList(); await LoadModuleDefinitions();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -124,6 +125,12 @@ else
} }
} }
private async Task LoadModuleDefinitions()
{
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
_packages = await PackageService.GetPackageUpdatesAsync("module");
}
private string PurchaseLink(string packagename) private string PurchaseLink(string packagename)
{ {
string link = ""; string link = "";
@ -134,10 +141,13 @@ else
{ {
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date) if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{ {
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span><br />"; if (string.IsNullOrEmpty(package.PaymentUrl))
if (!string.IsNullOrEmpty(package.PaymentUrl))
{ {
link += "&nbsp;&nbsp;<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + SharedLocalizer["Extend"] + "</a>"; link = "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
}
else
{
link = "<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</a>";
} }
} }
} }
@ -145,7 +155,7 @@ else
return link; return link;
} }
private string SupportLink(string packagename) private string SupportLink(string packagename, string version)
{ {
string link = ""; string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null) if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -153,7 +163,7 @@ else
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl)) if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{ {
link += "<a class=\"btn btn-success\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>"; link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
} }
} }
return link; return link;
@ -176,7 +186,7 @@ else
{ {
try try
{ {
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(packagename, version);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version); await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
} }
@ -202,9 +212,9 @@ else
} }
} }
private void CategoryChanged(ChangeEventArgs e) private async Task CategoryChanged(ChangeEventArgs e)
{ {
_category = (string)e.Value; _category = (string)e.Value;
StateHasChanged(); await LoadModuleDefinitions();
} }
} }

View File

@ -22,7 +22,6 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Export Content"; public override string Title => "Export Content";
private async Task ExportModule() private async Task ExportModule()
{ {
try try

View File

@ -26,6 +26,17 @@
<input id="title" type="text" class="form-control" @bind="@_title" required /> <input id="title" type="text" class="form-control" @bind="@_title" required />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pane" HelpText="The pane where the module will be displayed" ResourceKey="Pane">Pane: </Label>
<div class="col-sm-9">
<select class="form-select" @bind="@_pane">
@foreach (string pane in PageState.Page.Panes)
{
<option value="@pane">@pane Pane</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label> <Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -112,6 +123,7 @@
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private string _module; private string _module;
private string _title; private string _title;
private string _pane;
private string _containerType; private string _containerType;
private string _allPages = "false"; private string _allPages = "false";
private string _permissionNames = ""; private string _permissionNames = "";
@ -134,6 +146,7 @@
{ {
_module = ModuleState.ModuleDefinition.Name; _module = ModuleState.ModuleDefinition.Name;
_title = ModuleState.Title; _title = ModuleState.Title;
_pane = ModuleState.Pane;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType; _containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString(); _allPages = ModuleState.AllPages.ToString();
@ -206,6 +219,7 @@
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId); pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title; pagemodule.Title = _title;
pagemodule.Pane = _pane;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty; pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType) if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{ {

View File

@ -3,6 +3,7 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IPageService PageService @inject IPageService PageService
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject ISystemService SystemService
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -10,7 +11,7 @@
{ {
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh"> <TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" ResourceKey="Settings" Heading="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>
@ -39,9 +40,9 @@
<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> <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"> <div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required> <select id="insert" class="form-select" @bind="@_insert" required>
<option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0) @if (_children != null && _children.Count > 0)
{ {
<option value="<<">@Localizer["AtBeginning"]</option>
<option value="<">@Localizer["Before"]</option> <option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option> <option value=">">@Localizer["After"]</option>
} }
@ -111,8 +112,11 @@
</div> </div>
<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="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"> <div class="col-sm-8">
<input id="icon" class="form-control" @bind="@_icon" /> <InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
</div>
<div class="col-sm-1">
<i class="@_icon"></i>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -126,7 +130,7 @@
</div> </div>
</div> </div>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance"> <Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
<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>
@ -158,7 +162,7 @@
</div> </div>
</div> </div>
</Section> </Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent"> <Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
<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="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </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>
@ -175,7 +179,7 @@
</div> </div>
</Section> </Section>
</TabPanel> </TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions"> <TabPanel Name="Permissions" ResourceKey="Permissions" Heading=@Localizer["Permissions.Heading"]>
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
@ -184,7 +188,7 @@
</TabPanel> </TabPanel>
@if (_themeSettingsType != null) @if (_themeSettingsType != null)
{ {
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings"> <TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
@ThemeSettingsComponent @ThemeSettingsComponent
</TabPanel> </TabPanel>
} }
@ -227,6 +231,8 @@
private RenderFragment ThemeSettingsComponent { get; set; } private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false; private bool _refresh = false;
protected Page _parent = null; protected Page _parent = null;
protected Dictionary<string, string> _icons;
private string _iconresources = "";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -241,6 +247,8 @@
_parentid = _parent.PageId.ToString(); _parentid = _parent.PageId.ToString();
} }
} }
_icons = await SystemService.GetIconsAsync();
_iconresources = typeof(IconResources).FullName;
// if admin or user has edit access to parent page // 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))) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
@ -249,7 +257,14 @@
_themes = ThemeService.GetThemeControls(PageState.Site.Themes); _themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = PageState.Site.DefaultContainerType; _containertype = PageState.Site.DefaultContainerType;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList(); _children = new List<Page>();
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
}
ThemeSettings(); ThemeSettings();
_initialized = true; _initialized = true;
} }
@ -272,26 +287,13 @@
{ {
_parentid = (string)e.Value; _parentid = (string)e.Value;
_children = new List<Page>(); _children = new List<Page>();
if (_parentid == "-1") foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
_children.Add(p); _children.Add(p);
} }
} }
}
else
{
foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid)))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
}
}
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -482,4 +484,8 @@
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
} }
private void IconChanged(string NewIcon)
{
_icon = NewIcon;
}
} }

View File

@ -5,6 +5,7 @@
@inject IPageService PageService @inject IPageService PageService
@inject IPageModuleService PageModuleService @inject IPageModuleService PageModuleService
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject ISystemService SystemService
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -14,7 +15,7 @@
@if (_page.UserId == null) @if (_page.UserId == null)
{ {
<TabStrip Refresh="@_refresh"> <TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]> <TabPanel Name="Settings" ResourceKey="Settings" Heading="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>
@ -47,13 +48,16 @@
{ {
<option value="=">&lt;@Localizer["ThisLocation.Keep"]&gt;</option> <option value="=">&lt;@Localizer["ThisLocation.Keep"]&gt;</option>
} }
<option value="<<">@Localizer["ToBeginning"]</option>
@if (_children != null && _children.Count > 0) @if (_children != null && _children.Count > 0)
{ {
<option value="<<">@Localizer["ToBeginning"]</option>
<option value="<">@Localizer["Before"]</option> <option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option> <option value=">">@Localizer["After"]</option>
} }
@if (_parentid != _currentparentid)
{
<option value=">>">@Localizer["ToEnd"]</option> <option value=">>">@Localizer["ToEnd"]</option>
}
</select> </select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">")) @if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{ {
@ -123,8 +127,11 @@
</div> </div>
<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="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"> <div class="col-sm-8">
<input id="icon" class="form-control" @bind="@_icon" maxlength="50" /> <InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
</div>
<div class="col-sm-1">
<i class="@_icon"></i>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -137,7 +144,7 @@
</div> </div>
</div> </div>
</div> </div>
<Section Name="Appearance" ResourceKey="Appearance"> <Section Name="Appearance" ResourceKey="Appearance" Heading="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>
@ -189,7 +196,7 @@
<br /> <br />
<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>
</TabPanel> </TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions"> <TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" PermissionList="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Page" PermissionList="@_permissions" @ref="_permissionGrid" />
@ -205,7 +212,7 @@
<th>@Localizer["ModuleDefinition"]</th> <th>@Localizer["ModuleDefinition"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Settings" Text="Edit" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" ResourceKey="ModuleSettings" /></td> <td><ActionLink Action="Settings" Text="Edit" Path="@_actualpath" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" ResourceKey="ModuleSettings" /></td>
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td> <td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td>@context.Title</td> <td>@context.Title</td>
<td>@context.ModuleDefinition?.Name</td> <td>@context.ModuleDefinition?.Name</td>
@ -224,7 +231,7 @@
else else
{ {
<TabStrip Refresh="@_refresh"> <TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]> <TabPanel Name="Settings" ResourceKey="Settings" Heading="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="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>
@ -288,6 +295,7 @@
private int _childid = -1; private int _childid = -1;
private string _isnavigation; private string _isnavigation;
private string _isclickable; private string _isclickable;
private string _actualpath;
private string _path; private string _path;
private string _url; private string _url;
private string _ispersonalizable; private string _ispersonalizable;
@ -312,14 +320,17 @@
private bool _refresh = false; private bool _refresh = false;
protected Page _page = null; protected Page _page = null;
protected Page _parent = null; protected Page _parent = null;
protected Dictionary<string, string> _icons;
private string _iconresources = "";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_pageId = Int32.Parse(PageState.QueryString["id"]); _pageId = Int32.Parse(PageState.QueryString["id"]);
_page = await PageService.GetPageAsync(_pageId); _page = await PageService.GetPageAsync(_pageId);
_icons = await SystemService.GetIconsAsync();
_iconresources = Utilities.GetFullTypeName(typeof(IconResources).AssemblyQualifiedName);
if (_page != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _page.PermissionList)) if (_page != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _page.PermissionList))
{ {
@ -333,10 +344,19 @@
_parentid = _page.ParentId.ToString(); _parentid = _page.ParentId.ToString();
_parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId); _parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
} }
_children = new List<Page>();
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
}
_currentparentid = _parentid; _currentparentid = _parentid;
_isnavigation = _page.IsNavigation.ToString(); _isnavigation = _page.IsNavigation.ToString();
_isclickable = _page.IsClickable.ToString(); _isclickable = _page.IsClickable.ToString();
_path = _page.Path; _actualpath = _page.Path;
_path = _actualpath;
if (string.IsNullOrEmpty(_path)) if (string.IsNullOrEmpty(_path))
{ {
_path = "/"; _path = "/";
@ -355,7 +375,7 @@
// appearance // appearance
_title = _page.Title; _title = _page.Title;
_themetype = _page.ThemeType; _themetype = _page.ThemeType;
if (string.IsNullOrEmpty(_themetype) || ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName) if (string.IsNullOrEmpty(_themetype))
{ {
_themetype = PageState.Site.DefaultThemeType; _themetype = PageState.Site.DefaultThemeType;
} }
@ -407,34 +427,14 @@
{ {
_parentid = (string)e.Value; _parentid = (string)e.Value;
_children = new List<Page>(); _children = new List<Page>();
if (_parentid == "-1") foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{ {
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null)) if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
_children.Add(p); _children.Add(p);
} }
} }
} _insert = (_parentid == _currentparentid) ? "=" : ">>";
else
{
foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid)))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
}
}
if (_parentid == _currentparentid)
{
_insert = "=";
}
else
{
_insert = ">>";
}
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -660,4 +660,8 @@
} }
} }
private void IconChanged(string NewIcon)
{
_icon = NewIcon;
}
} }

View File

@ -9,7 +9,7 @@
{ {
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" /> <ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)"> <Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -22,7 +22,7 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label> <Label Class="col-sm-3" For="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required ></textarea> <textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="256" required></textarea>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -34,13 +34,19 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label> <Label Class="col-sm-3" For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="order" class="form-control" @bind="@_vieworder" maxlength="4" required /> <input id="order" class="form-control" @bind="@_vieworder" min="0" max="99" type="number" required />
</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="length" HelpText="The max number of characters this profile item should accept (enter zero for unlimited)" ResourceKey="Length">Length: </Label> <Label Class="col-sm-3" For="length" HelpText="The max number of characters this profile item should accept (enter zero for unlimited)" ResourceKey="Length">Length: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="length" class="form-control" @bind="@_maxlength" maxlength="4" required /> <input id="length" class="form-control" @bind="@_maxlength" min="0" max="524288" type="number" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="rows" HelpText="The number of rows for text entry (one is the default)" ResourceKey="Rows">Rows: </Label>
<div class="col-sm-9">
<input id="rows" class="form-control" @bind="@_rows" min="1" max="10" type="number" required />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -101,6 +107,7 @@
private string _category = string.Empty; private string _category = string.Empty;
private string _vieworder = "0"; private string _vieworder = "0";
private string _maxlength = "0"; private string _maxlength = "0";
private string _rows = "1";
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 _validation = string.Empty;
@ -131,6 +138,7 @@
_category = profile.Category; _category = profile.Category;
_vieworder = profile.ViewOrder.ToString(); _vieworder = profile.ViewOrder.ToString();
_maxlength = profile.MaxLength.ToString(); _maxlength = profile.MaxLength.ToString();
_rows = profile.Rows.ToString();
_defaultvalue = profile.DefaultValue; _defaultvalue = profile.DefaultValue;
_options = profile.Options; _options = profile.Options;
_validation = profile.Validation; _validation = profile.Validation;
@ -175,6 +183,7 @@
profile.Category = _category; profile.Category = _category;
profile.ViewOrder = int.Parse(_vieworder); profile.ViewOrder = int.Parse(_vieworder);
profile.MaxLength = int.Parse(_maxlength); profile.MaxLength = int.Parse(_maxlength);
profile.Rows = int.Parse(_rows);
profile.DefaultValue = _defaultvalue; profile.DefaultValue = _defaultvalue;
profile.Options = _options; profile.Options = _options;
profile.Validation = _validation; profile.Validation = _validation;

View File

@ -12,16 +12,22 @@ else
{ {
<ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" /> <ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" />
<Pager Items="@_profiles"> <Pager Items="@_profiles" SearchProperties="Title,Category">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Title"]</th>
<th>@Localizer["Category"]</th>
<th>@Localizer["Order"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td> <td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Title</td>
<td>@context.Category</td>
<td>@context.ViewOrder</td>
</Row> </Row>
</Pager> </Pager>
} }

View File

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

View File

@ -16,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="@_passwordconstruction" Type="MessageType.Info" /> <ModuleMessage Message="@_passwordrequirements" 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">
@ -69,6 +69,7 @@ else
} }
@code { @code {
private string _passwordrequirements;
private string _username = string.Empty; private string _username = string.Empty;
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
@ -79,44 +80,16 @@ else
private string _email = string.Empty; private string _email = string.Empty;
private string _displayname = string.Empty; private string _displayname = string.Empty;
//Password construction
private string _minimumlength;
private string _uniquecharacters;
private bool _requiredigit;
private bool _requireupper;
private bool _requirelower;
private bool _requirepunctuation;
private string _passwordconstruction;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); _passwordrequirements = await UserService.GetPasswordRequirementsAsync(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() protected override void OnParametersSet()
{ {
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
} }
private async Task Register() private async Task Register()

View File

@ -6,6 +6,7 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<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="username" HelpText="Your username will be populated from the link you received in the password reset notification" ResourceKey="Username">Username: </Label> <Label Class="col-sm-3" For="username" HelpText="Your username will be populated from the link you received in the password reset notification" ResourceKey="Username">Username: </Label>
@ -45,12 +46,14 @@
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 _passwordrequirements;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token")) if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
{ {

View File

@ -12,7 +12,7 @@ else
{ {
<ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" /> <ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" />
<Pager Items="@_roles"> <Pager Items="@_roles" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -26,7 +26,7 @@
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label> <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">
<select id="homepage" class="form-select" @bind="@_homepageid" required> <select id="homepage" class="form-select" @bind="@_homepageid" required>
<option value="-">&lt;@Localizer["Not Specified"]&gt;</option> <option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (Page page in PageState.Pages) @foreach (Page page in PageState.Pages)
{ {
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone)) if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
@ -49,7 +49,16 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing" ResourceKey="SiteMap">Site Map: </Label> <Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing" ResourceKey="SiteMap">Site Map: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="sitemap" class="form-control" @bind="@_sitemap" required disabled /> <div class="input-group">
<input id="sitemap" class="form-control" @bind="@_sitemap" disabled />
<a href="@_sitemap" class="btn btn-secondary" target="_new">@Localizer["Browse"]</a>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="siteguid" HelpText="The Unique Identifier For The Site" ResourceKey="SiteGuid">ID: </Label>
<div class="col-sm-9">
<input id="siteguid" class="form-control" @bind="@_siteguid" required disabled />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -60,7 +69,7 @@
</div> </div>
</div> </div>
<br /> <br />
<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="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label> <Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
@ -257,7 +266,7 @@
<td> <td>
@if (_aliasid == -1) @if (_aliasid == -1)
{ {
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteModule" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" /> <ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
} }
</td> </td>
<td>@context.Name</td> <td>@context.Name</td>
@ -348,6 +357,7 @@
private string _homepageid = "-"; private string _homepageid = "-";
private string _isdeleted; private string _isdeleted;
private string _sitemap = ""; private string _sitemap = "";
private string _siteguid = "";
private string _version = ""; private string _version = "";
private int _logofileid = -1; private int _logofileid = -1;
private FileManager _logofilemanager; private FileManager _logofilemanager;
@ -406,6 +416,7 @@
} }
_isdeleted = site.IsDeleted.ToString(); _isdeleted = site.IsDeleted.ToString();
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml"; _sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml";
_siteguid = site.SiteGuid;
_version = site.Version; _version = site.Version;
// appearance // appearance
@ -610,6 +621,7 @@
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, "SMTPEnabled", _smtpenabled, true); settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true); settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);

View File

@ -37,7 +37,7 @@ else
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label> <Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="defaultTheme" class="form-select" @onchange="(e => ThemeChanged(e))" required> <select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option> <option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes) @foreach (var theme in _themes)
{ {
@ -58,19 +58,6 @@ else
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
<div class="col-sm-9">
<select id="adminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="">&lt;@Localizer["DefaultContainer.Admin"]&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="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label> <Label Class="col-sm-3" For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -105,7 +92,7 @@ else
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label> <Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="tenant" class="form-select" @onchange="(e => TenantChanged(e))" required> <select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))" required>
<option value="-">&lt;@Localizer["Tenant.Select"]&gt;</option> <option value="-">&lt;@Localizer["Tenant.Select"]&gt;</option>
<option value="+">&lt;@Localizer["Tenant.Add"]&gt;</option> <option value="+">&lt;@Localizer["Tenant.Add"]&gt;</option>
@foreach (Tenant tenant in _tenants) @foreach (Tenant tenant in _tenants)
@ -214,7 +201,6 @@ else
private string _urls = string.Empty; private string _urls = string.Empty;
private string _themetype = "-"; private string _themetype = "-";
private string _containertype = "-"; private string _containertype = "-";
private string _admincontainertype = "";
private string _sitetemplatetype = "-"; private string _sitetemplatetype = "-";
private string _runtime = "Server"; private string _runtime = "Server";
private string _prerender = "Prerendered"; private string _prerender = "Prerendered";
@ -224,10 +210,24 @@ else
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_tenants = await TenantService.GetTenantsAsync(); _tenants = await TenantService.GetTenantsAsync();
if (_tenants.Any(item => item.Name == TenantNames.Master))
{
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
}
_urls = PageState.Alias.Name; _urls = PageState.Alias.Name;
_themeList = await ThemeService.GetThemesAsync(); _themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList); _themes = ThemeService.GetThemeControls(_themeList);
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
{
_themetype = Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = _containers.First().TypeName;
}
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync(); _siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate))
{
_sitetemplatetype = Constants.DefaultSiteTemplate;
}
_databases = await DatabaseService.GetDatabasesAsync(); _databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault)) if (_databases.Exists(item => item.IsDefault))
@ -295,7 +295,6 @@ else
_containers = new List<ThemeControl>(); _containers = new List<ThemeControl>();
_containertype = "-"; _containertype = "-";
} }
_admincontainertype = "";
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -399,7 +398,7 @@ else
config.Aliases = _urls; config.Aliases = _urls;
config.DefaultTheme = _themetype; config.DefaultTheme = _themetype;
config.DefaultContainer = _containertype; config.DefaultContainer = _containertype;
config.DefaultAdminContainer = _admincontainertype; config.DefaultAdminContainer = "";
config.SiteTemplate = _sitetemplatetype; config.SiteTemplate = _sitetemplatetype;
config.Runtime = _runtime; config.Runtime = _runtime;
config.RenderMode = _runtime + _prerender; config.RenderMode = _runtime + _prerender;

View File

@ -8,13 +8,13 @@
@if (_sites == null) @if (_sites == null)
{ {
<p><em>Loading...</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
<ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" /> <ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" />
<Pager Items="@_sites"> <Pager Items="@_sites" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -133,12 +133,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="packageservice" HelpText="Specify If The Package Service Is Enabled For Installing Modules, Themes, And Translations" ResourceKey="PackageService">Enable Package Service? </Label> <Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="packageservice" class="form-select" @bind="@_packageservice"> <input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</div> </div>
</div> </div>
</div> </div>
@ -182,7 +179,7 @@
private string _logginglevel = string.Empty; private string _logginglevel = string.Empty;
private string _notificationlevel = string.Empty; private string _notificationlevel = string.Empty;
private string _swagger = string.Empty; private string _swagger = string.Empty;
private string _packageservice = string.Empty; private string _packageregistryurl = string.Empty;
private string _log = string.Empty; private string _log = string.Empty;
@ -213,7 +210,7 @@
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString(); _logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString(); _notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
_swagger = systeminfo["UseSwagger"].ToString(); _swagger = systeminfo["UseSwagger"].ToString();
_packageservice = systeminfo["PackageService"].ToString(); _packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
} }
systeminfo = await SystemService.GetSystemInfoAsync("log"); systeminfo = await SystemService.GetSystemInfoAsync("log");
@ -232,7 +229,7 @@
settings.Add("Logging:LogLevel:Default", _logginglevel); settings.Add("Logging:LogLevel:Default", _logginglevel);
settings.Add("Logging:LogLevel:Notify", _notificationlevel); settings.Add("Logging:LogLevel:Notify", _notificationlevel);
settings.Add("UseSwagger", _swagger); settings.Add("UseSwagger", _swagger);
settings.Add("PackageService", _packageservice); settings.Add("PackageRegistryUrl", _packageregistryurl);
await SystemService.UpdateSystemInfoAsync(settings); await SystemService.UpdateSystemInfoAsync(settings);
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success); AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
} }

View File

@ -10,61 +10,115 @@
<TabStrip> <TabStrip>
<TabPanel Name="Download" ResourceKey="Download"> <TabPanel Name="Download" ResourceKey="Download">
<div class="row justify-content-center mb-3"> <div class="row justify-content-center mb-3">
<div class="col-sm-6"> <div class="text-center">
<div class="form-check form-check-inline">
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
</div>
<div class="form-check form-check-inline">
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group"> <div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))"> <span class="input-group-text">@Localizer["Product"]</span>
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" /> <input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button> <button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button> <button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<button type="button" class="btn btn-primary ms-2" @onclick="Refresh"><span class="@Icons.Reload" aria-hidden="true"></span></button>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-3">
@if (_packages != null) <div class="col">
@if (_initialized)
{ {
if (_packages.Count > 0) <br />
<div class="row mb-3">
<div class="col-sm-4">
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
</div>
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-4">
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
@if (_price == "free")
{ {
<Pager Items="@_packages"> <option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;@SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
} }
</td> <option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
<td style="width: 1px; vertical-align: middle;"> @if (_price == "paid")
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{ {
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a> <option value="price">@SharedLocalizer["Search.Price"]</option>
}
</select>
</div>
</div>
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6">
<Row>
<div class="m-2 p-2 d-flex justify-content-center">
<div class="container-fluid px-0">
<div class="row g-0 mb-2">
<div class="col-4">
<a href="@context.ProductUrl" target="_blank">
@if (context.LogoUrl != null)
{
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
} }
else else
{
<img src="/package.png" class="img-fluid" alt="@context.Name" />
}
</a>
</div>
<div class="col-8 text-end">
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<br /><small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
}
@if (context.Price == null)
{
<br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
}
else
{
<br /><small>@SharedLocalizer["From"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
@((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
}
</div>
</div>
<div class="row g-0">
<div class="col">
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<br />
@if (!string.IsNullOrEmpty(context.PackageUrl))
{ {
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button> <button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
} }
</td> @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
</div>
</div>
</div>
</Row> </Row>
</Pager> </Pager>
} }
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div> </div>
} </div>
}
<br /> <br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" /> <ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel> </TabPanel>
@ -116,8 +170,11 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {
private bool _initialized = false;
private int _page = 1;
private List<Package> _packages; private List<Package> _packages;
private string _price = "free"; private string _price = "free";
private string _sort = "popularity";
private string _search = ""; private string _search = "";
private string _productname = ""; private string _productname = "";
private string _license = ""; private string _license = "";
@ -131,6 +188,7 @@
try try
{ {
await LoadThemes(); await LoadThemes();
_initialized = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -141,8 +199,10 @@
private async Task LoadThemes() private async Task LoadThemes()
{ {
ShowProgressIndicator();
var themes = await ThemeService.GetThemesAsync(); var themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, ""); _packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
if (_packages != null) if (_packages != null)
{ {
@ -154,46 +214,44 @@
} }
} }
} }
HideProgressIndicator();
} }
private async void PriceChanged(ChangeEventArgs e) private async void PriceChanged(string price)
{ {
try _price = price;
{ _sort = "popularity";
_price = (string)e.Value;
_search = "";
await LoadThemes(); await LoadThemes();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
private async Task Search() private async Task Search()
{
try
{ {
await LoadThemes(); await LoadThemes();
} }
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
private async Task Reset() private async Task Reset()
{ {
try _page = 1;
{
_search = ""; _search = "";
await LoadThemes(); await LoadThemes();
} }
catch (Exception ex)
private async Task Refresh()
{ {
await logger.LogError(ex, "Error On Reset"); await LoadThemes();
} }
private void OnPageChange(int page)
{
_page = page;
}
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadThemes();
} }
private void HideModal() private void HideModal()
@ -207,7 +265,7 @@
{ {
try try
{ {
var package = await PackageService.GetPackageAsync(packageid, version); var package = await PackageService.GetPackageAsync(packageid, version, false);
if (package != null) if (package != null)
{ {
_productname = package.Name; _productname = package.Name;
@ -236,7 +294,7 @@
{ {
try try
{ {
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(_packageid, _version);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version); await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
_productname = ""; _productname = "";

View File

@ -79,9 +79,12 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override void OnInitialized() protected override void OnInitialized()
{
if (!NavigationManager.BaseUri.Contains("localhost:"))
{ {
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info); AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
} }
}
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
@ -102,7 +105,8 @@
{ {
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-") if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
{ {
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference }; var template = _templates.FirstOrDefault(item => item.Name == _template);
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
theme = await ThemeService.CreateThemeAsync(theme); theme = await ThemeService.CreateThemeAsync(theme);
GetLocation(); GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
@ -142,8 +146,14 @@
if (_owner != "" && _theme != "" && _template != "-") if (_owner != "" && _theme != "" && _template != "-")
{ {
var template = _templates.FirstOrDefault(item => item.Name == _template); var template = _templates.FirstOrDefault(item => item.Name == _template);
if (!string.IsNullOrEmpty(template.Namespace))
{
_location = template.Location + template.Namespace.Replace("[Owner]", _owner).Replace("[Theme]", _theme);
}
else
{
_location = template.Location + _owner + ".Theme." + _theme; _location = template.Location + _owner + ".Theme." + _theme;
}
} }
StateHasChanged(); StateHasChanged();
} }

View File

@ -2,6 +2,7 @@
@using System.Net @using System.Net
@inherits ModuleBase @inherits ModuleBase
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject IPackageService PackageService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -27,7 +28,7 @@
</div> </div>
</div> </div>
</form> </form>
<Section Name="Information" ResourceKey="Information"> <Section Name="Information" ResourceKey="Information" Heading="Information">
<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="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label> <Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
@ -42,9 +43,26 @@
</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="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label> <Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@if (!string.IsNullOrEmpty(_packagename))
{
<div class="input-group">
<input id="packagename" class="form-control" @bind="@_packagename" disabled /> <input id="packagename" class="form-control" @bind="@_packagename" disabled />
@if (string.IsNullOrEmpty(_packageurl))
{
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
}
else
{
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
}
</div>
}
else
{
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
}
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -68,7 +86,14 @@
<div class="row mb-1 align-items-center"> <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> <Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
{
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
}
else
{
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea> <textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
}
</div> </div>
</div> </div>
</div> </div>
@ -90,7 +115,8 @@
private string _isenabled; private string _isenabled;
private string _name; private string _name;
private string _version; private string _version;
private string _packagename; private string _packagename = "";
private string _packageurl = "";
private string _owner = ""; private string _owner = "";
private string _url = ""; private string _url = "";
private string _contact = ""; private string _contact = "";
@ -159,4 +185,27 @@
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
private async Task ValidatePackage()
{
try
{
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
{
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
}
else
{
_packageurl = package.PackageUrl;
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
}
}
} }

View File

@ -13,7 +13,7 @@
} }
else else
{ {
<ActionLink Action="Add" Text="Install Theme" /> <ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
@((MarkupString)"&nbsp;") @((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" /> <ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" />
@ -29,7 +29,7 @@ else
<th>&nbsp;</th> <th>&nbsp;</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditModule" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
<td> <td>
@if (context.AssemblyName != Constants.ClientId) @if (context.AssemblyName != Constants.ClientId)
{ {
@ -49,7 +49,7 @@ else
} }
</td> </td>
<td> <td>
@((MarkupString)SupportLink(context.PackageName)) @((MarkupString)SupportLink(context.PackageName, context.Version))
</td> </td>
<td> <td>
@((MarkupString)PurchaseLink(context.PackageName)) @((MarkupString)PurchaseLink(context.PackageName))
@ -79,7 +79,7 @@ else
try try
{ {
_themes = await ThemeService.GetThemesAsync(); _themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme"); _packages = await PackageService.GetPackageUpdatesAsync("theme");
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -101,10 +101,13 @@ else
{ {
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date) if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{ {
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span><br />"; if (string.IsNullOrEmpty(package.PaymentUrl))
if (!string.IsNullOrEmpty(package.PaymentUrl))
{ {
link += "&nbsp;&nbsp;<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + SharedLocalizer["Extend"] + "</a>"; link = "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
}
else
{
link = "<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</a>";
} }
} }
} }
@ -112,7 +115,7 @@ else
return link; return link;
} }
private string SupportLink(string packagename) private string SupportLink(string packagename, string version)
{ {
string link = ""; string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null) if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -120,7 +123,7 @@ else
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl)) if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{ {
link += "<a class=\"btn btn-success\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>"; link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
} }
} }
return link; return link;
@ -143,7 +146,7 @@ else
{ {
try try
{ {
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(packagename, version);
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version); await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version);
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
} }

View File

@ -7,6 +7,8 @@
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
<TabStrip> <TabStrip>
<TabPanel Name="Download" ResourceKey="Download"> <TabPanel Name="Download" ResourceKey="Download">
@if (_package != null && _upgradeavailable) @if (_package != null && _upgradeavailable)
@ -17,11 +19,11 @@
} }
else else
{ {
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage> <ModuleMessage Type="MessageType.Info" Message=@Localizer["Message.Text"]></ModuleMessage>
} }
</TabPanel> </TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload"> <TabPanel Name="Upload" ResourceKey="Upload">
<ModuleMessage Type="MessageType.Info" Message="Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade"></ModuleMessage> <ModuleMessage Type="MessageType.Info" Message=@Localizer["MessageUpgrade.Text"]></ModuleMessage>
<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 A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label> <Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
@ -33,8 +35,10 @@
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
}
@code { @code {
private bool _initialized = false;
private Package _package; private Package _package;
private bool _upgradeavailable = false; private bool _upgradeavailable = false;
@ -43,6 +47,12 @@
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{
if (NavigationManager.BaseUri.Contains("localhost:"))
{
AddModuleMessage(Localizer["Localhost.Text"], MessageType.Info);
}
else
{ {
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", ""); List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
if (packages != null) if (packages != null)
@ -57,6 +67,8 @@
_package = new Package { Name = Constants.PackageId, Version = Constants.Version }; _package = new Package { Name = Constants.PackageId, Version = Constants.Version };
} }
} }
_initialized = true;
}
} }
catch catch
{ {
@ -85,8 +97,8 @@
{ {
try try
{ {
await PackageService.DownloadPackageAsync(packageid, version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(packageid, version);
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version, Constants.PackagesFolder); await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success); AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -28,7 +28,7 @@ else
</div> </div>
</div> </div>
<br/> <br/>
<Pager Items="@_urlMappings"> <Pager Items="@_urlMappings" SearchProperties="Url">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -2,6 +2,7 @@
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@inject IUserRoleService UserRoleService
@inject INotificationService NotificationService @inject INotificationService NotificationService
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -10,9 +11,9 @@
{ {
<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="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label> <Label Class="col-sm-3" For="to" HelpText="Enter the user you wish to send a message to" ResourceKey="To">To: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="to" class="form-control" @bind="@username" /> <AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="username" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -30,11 +31,11 @@
</div> </div>
<br/> <br/>
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button> <button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
} }
@code { @code {
private string username = ""; private AutoComplete username;
private string subject = ""; private string subject = "";
private string body = ""; private string body = "";
@ -42,23 +43,37 @@
public override string Title => "Send Notification"; public override string Title => "Send Notification";
private async Task<Dictionary<string, string>> GetUsers(string filter)
{
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
return users.Where(item => item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase))
.ToDictionary(item => item.UserId.ToString(), item => item.User.Username);
}
private async Task Send() private async Task Send()
{ {
try try
{ {
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId); if (!string.IsNullOrEmpty(username.Key) && !string.IsNullOrEmpty(subject))
{
var user = await UserService.GetUserAsync(int.Parse(username.Key), ModuleState.SiteId);
if (user != null) if (user != null)
{ {
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body); var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body);
notification = await NotificationService.AddNotificationAsync(notification); notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId); await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
else else
{ {
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning); AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
} }
} }
else
{
AddModuleMessage(Localizer["Message.Required"], MessageType.Warning);
}
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message); await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);

View File

@ -11,6 +11,8 @@
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
@if (PageState.User != null && photo != null) @if (PageState.User != null && photo != null)
{ {
<img src="@ImageUrl(photofileid, 400, 400)" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block"> <img src="@ImageUrl(photofileid, 400, 400)" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
@ -21,8 +23,7 @@ else
} }
<TabStrip> <TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity"> <TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null && settings != null) <ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
{
<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="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label> <Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
@ -82,11 +83,8 @@ else
<br /> <br />
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
}
</TabPanel> </TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile"> <TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null && settings != null)
{
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles) @foreach (Profile profile in profiles)
@ -121,6 +119,8 @@ else
</select> </select>
} }
else else
{
@if (p.Rows == 1)
{ {
@if (p.IsRequired) @if (p.IsRequired)
{ {
@ -131,6 +131,18 @@ else
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
} }
} }
else
{
@if (p.IsRequired)
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
}
}
}
</div> </div>
</div> </div>
} }
@ -139,19 +151,19 @@ else
</div> </div>
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
}
</TabPanel> </TabPanel>
<TabPanel Name="Notifications" ResourceKey="Notifications"> <TabPanel Name="Notifications" ResourceKey="Notifications">
@if (notifications != null) <ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
{ <br />
<br />
<select class="form-select" @onchange="(e => FilterChanged(e))"> <select class="form-select" @onchange="(e => FilterChanged(e))">
<option value="to">@Localizer["Inbox"]</option> <option value="to">@Localizer["Inbox"]</option>
<option value="from">@Localizer["Items.Sent"]</option> <option value="from">@Localizer["Items.Sent"]</option>
</select> </select>
<br /> <br />
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" />
<br /><br />
@if (filter == "to") @if (filter == "to")
{
@if (notifications.Any())
{ {
<Pager Items="@notifications"> <Pager Items="@notifications">
<Header> <Header>
@ -162,7 +174,7 @@ else
<th>@Localizer["Received"]</th> <th>@Localizer["Received"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td> <td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td> <td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
@if (context.IsRead) @if (context.IsRead)
@ -202,19 +214,30 @@ else
</td> </td>
</Detail> </Detail>
</Pager> </Pager>
<br />
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
} }
else else
{
<div class="no-notifications-text">
@Localizer["NoNotificationsReceived.Text"]
</div>
}
}
else
{
@if (notifications.Any())
{ {
<Pager Items="@notifications"> <Pager Items="@notifications">
<Header> <Header>
<th>&nbsp;</th> <th style="width: 1px;"></th>
<th>&nbsp;</th> <th style="width: 1px;"></th>
<th>@Localizer["To"]</th> <th>@Localizer["To"]</th>
<th>@Localizer["Subject"]</th> <th>@Localizer["Subject"]</th>
<th>@Localizer["Sent"]</th> <th>@Localizer["Sent"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td> <td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td> <td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
@if (context.IsRead) @if (context.IsRead)
@ -255,18 +278,25 @@ else
</td> </td>
</Detail> </Detail>
</Pager> </Pager>
}
@if (notifications.Any())
{
<br /> <br />
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" /> <ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
} }
else
{
<div class="no-notifications-text">
@Localizer["NoNotificationsSent.Text"]
</div>
}
} }
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
<br /><br /> <br />
<br />
}
@code { @code {
private bool _initialized = false;
private string _passwordrequirements;
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";
@ -280,25 +310,25 @@ else
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;
private string notificationSummary = string.Empty; private string notificationSummary = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"])) profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
{
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
}
if (PageState.User != null) if (PageState.User != null)
{ {
@ -325,10 +355,11 @@ else
photo = null; photo = null;
} }
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
await LoadNotificationsAsync(); await LoadNotificationsAsync();
_initialized = true;
} }
else else
{ {
@ -362,9 +393,11 @@ else
{ {
try try
{ {
if (username != string.Empty && email != string.Empty && ValidateProfiles()) if (username != string.Empty && email != string.Empty)
{ {
if (_password == confirm) if (_password == confirm)
{
if (ValidateProfiles())
{ {
var user = PageState.User; var user = PageState.User;
user.Username = username; user.Username = username;
@ -402,6 +435,7 @@ 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);
@ -423,27 +457,33 @@ else
private bool ValidateProfiles() private bool ValidateProfiles()
{ {
bool valid = true;
foreach (Profile profile in profiles) foreach (Profile profile in profiles)
{ {
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue)) var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{ {
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) if (profile.IsRequired && string.IsNullOrEmpty(value))
{ {
valid = false; AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
return false;
} }
if (valid == true && !string.IsNullOrEmpty(profile.Validation)) if (!string.IsNullOrEmpty(profile.Validation))
{ {
Regex regex = new Regex(profile.Validation); Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; bool valid = regex.Match(value).Success;
if (!valid)
{
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
return false;
} }
} }
} }
return valid; }
return true;
} }
private void Cancel() private void Cancel()
@ -485,7 +525,6 @@ else
private async void FilterChanged(ChangeEventArgs e) private async void FilterChanged(ChangeEventArgs e)
{ {
filter = (string)e.Value; filter = (string)e.Value;
await LoadNotificationsAsync(); await LoadNotificationsAsync();
StateHasChanged(); StateHasChanged();
} }

View File

@ -7,95 +7,72 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null) @if (PageState.User != null)
{
@if (title == "From")
{ {
<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">@Localizer["Title"] </label> <Label Class="col-sm-3" For="username" HelpText="The user who sent the message" ResourceKey="From">From:</Label>
@if (title == "From")
{
<div class="col-sm-3">
<input class="form-control" @bind="@username" readonly />
</div>
}
@if (title == "To")
{
<div class="col-sm-3">
<input class="form-control" @bind="@username" />
</div>
}
</div>
<div class="row mb-1 align-items-center">
<label Class="col-sm-3">@Localizer["Subject"] </label>
@if (title == "From")
{
<div class="col-sm-3">
<input class="form-control" @bind="@subject" readonly />
</div>
}
@if (title == "To")
{
<div class="col-sm-3">
<input class="form-control" @bind="@subject" />
</div>
}
</div>
</div>
<div class="container">
@if (title == "From")
{
<div class="row mb-1 align-items-center">
<label class="col-sm-3">@Localizer["Date"] </label>
<div class="col-sm-9"> <div class="col-sm-9">
<input class="form-control" @bind="@createdon" readonly /> <input id="username" class="form-control" @bind="@username" readonly />
</div> </div>
</div> </div>
}
@if (title == "From")
{
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<label class="col-sm-3">@Localizer["Message"] </label> <Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea class="form-control" @bind="@body" rows="5" readonly /> <input id="subject" class="form-control" @bind="@subject" readonly />
</div> </div>
</div> </div>
}
@if (title == "To")
{
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<label class="col-sm-3">@Localizer["Message"] </label> <Label class="col-sm-3" For="date" HelpText="The date the message was sent" ResourceKey="Date">Sent:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea class="form-control" @bind="@body" rows="5" readonly /> <input id="date" class="form-control" @bind="@createdon" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label class="col-sm-3" For="message" HelpText="The contents of the message" ResourceKey="Message">Message:</Label>
<div class="col-sm-9">
<textarea id="message" class="form-control" @bind="@body" rows="5" readonly />
</div> </div>
</div> </div>
}
</div> </div>
@if (reply != string.Empty)
{
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
} }
else else
{ {
if (title == "From") <div class="container">
{ <div class="row mb-1 align-items-center">
<button type="button" class="btn btn-primary" @onclick="Reply">@Localizer["Reply"]</button> <Label Class="col-sm-3" For="username" HelpText="The user who will be the recipient of the message" ResourceKey="To">To:</Label>
} <div class="col-sm-9">
} <input id="username" class="form-control" @bind="@username" readonly />
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> </div>
<br /> </div>
<br /> <div class="row mb-1 align-items-center">
@if (title == "To") <Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
{ <div class="col-sm-9">
<div class="control-group"> <input id="subject" class="form-control" @bind="@subject" readonly="@(!reply)" />
<label class="control-label">@Localizer["OriginalMessage"] </label> </div>
<textarea class="form-control" @bind="@reply" rows="5" readonly /> </div>
<div class="row mb-1 align-items-center">
<Label class="col-sm-3" For="message" HelpText="The content of the message" ResourceKey="Message">Message:</Label>
<div class="col-sm-9">
<textarea id="message" class="form-control" @bind="@body" rows="5" readonly="@(!reply)" />
</div>
</div>
</div> </div>
} }
@if (reply)
{
<button type="button" class="btn btn-primary me-2" @onclick="Send">@SharedLocalizer["Send"]</button>
}
else
{
if (title == "From" && username != Localizer["System"])
{
<button type="button" class="btn btn-primary me-2" @onclick="Reply">@Localizer["Reply"]</button>
}
}
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
} }
@code { @code {
@ -105,7 +82,7 @@
private string subject = string.Empty; private string subject = string.Empty;
private string createdon = string.Empty; private string createdon = string.Empty;
private string body = string.Empty; private string body = string.Empty;
private string reply = string.Empty; private bool reply = false;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override string Title => "View Notification"; public override string Title => "View Notification";
@ -118,9 +95,6 @@
Notification notification = await NotificationService.GetNotificationAsync(notificationid); Notification notification = await NotificationService.GetNotificationAsync(notificationid);
if (notification != null) if (notification != null)
{ {
notification.IsRead = true;
notification = await NotificationService.UpdateNotificationAsync(notification);
int userid = -1; int userid = -1;
if (notification.ToUserId == PageState.User.UserId) if (notification.ToUserId == PageState.User.UserId)
{ {
@ -148,11 +122,17 @@
} }
if (username == "") if (username == "")
{ {
username = "System"; username = Localizer["System"];
} }
subject = notification.Subject; subject = notification.Subject;
createdon = notification.CreatedOn.ToString(); createdon = notification.CreatedOn.ToString();
body = notification.Body; body = notification.Body;
if (title == "From")
{
notification.IsRead = true;
notification = await NotificationService.UpdateNotificationAsync(notification);
}
} }
} }
catch (Exception ex) catch (Exception ex)
@ -165,12 +145,16 @@
private void Reply() private void Reply()
{ {
title = "To"; title = "To";
if (!subject.Contains("RE:")) if (!subject.Contains(Localizer["RE:"]))
{ {
subject = "RE: " + subject; subject = Localizer["RE"] + " " + subject;
} }
reply = body; body = $"\n\n____________________________________________\n" +
body = "\n\n____________________________________________\nSent: " + createdon + "\nSubject: " + subject + "\n\n" + body; $"{Localizer["From.Text"]} {username}\n" +
$"{Localizer["Date.Text"]} {createdon}\n" +
$"{Localizer["Subject.Text"]} {subject}\n\n" +
body;
reply = true;
StateHasChanged(); StateHasChanged();
} }
@ -184,7 +168,7 @@
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid); var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid);
notification = await NotificationService.AddNotificationAsync(notification); notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId); await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
else else
{ {

View File

@ -8,15 +8,18 @@
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
<TabStrip> <TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity"> <TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null) @if (profiles != null)
{ {
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<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="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label> <Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="username" class="form-control" @bind="@username" /> <input id="username" class="form-control" @bind="@_username" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -32,7 +35,7 @@
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label> <Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<div class="col-sm-9"> <div class="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" tabindex="-1">@_togglepassword</button> <button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div> </div>
</div> </div>
@ -40,22 +43,28 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label> <Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="email" class="form-control" @bind="@email" /> <input id="email" class="form-control" @bind="@_email" />
</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="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label> <Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@displayname" /> <input id="displayname" class="form-control" @bind="@_displayname" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
<div class="col-sm-9">
<select id="notify" class="form-select" @bind="@_notify" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div> </div>
</div> </div>
</div> </div>
} }
</TabPanel> </TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile"> <TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null)
{
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles) @foreach (Profile profile in profiles)
@ -71,36 +80,58 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label> <Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9"> <div class="col-sm-9">
@if (p.IsRequired) @if (!string.IsNullOrEmpty(p.Options))
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" /> <select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
{
<option value="@option" selected>@option</option>
} }
else else
{
<option value="@option">@option</option>
}
}
</select>
}
else
{
@if (p.Rows == 1)
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
} }
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
}
}
</div> </div>
</div> </div>
} }
</div> </div>
</div> </div>
}
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
<br /> <br />
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
}
@code { @code {
private string username = string.Empty; private bool _initialized = false;
private string _passwordrequirements;
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 string _notify = "True";
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;
@ -111,9 +142,11 @@
{ {
try try
{ {
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = new Dictionary<string, string>(); settings = new Dictionary<string, string>();
_initialized = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -136,20 +169,20 @@
{ {
try try
{ {
if (username != string.Empty && _password != string.Empty && confirm != string.Empty && email != string.Empty && ValidateProfiles()) if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty && _email != string.Empty)
{ {
if (_password == confirm) if (_password == _confirm)
{ {
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId); if (ValidateProfiles())
if (user == null)
{ {
user = new User(); var user = new User();
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.SuppressNotification = !bool.Parse(_notify);
user = await UserService.AddUserAsync(user); user = await UserService.AddUserAsync(user);
@ -161,14 +194,10 @@
} }
else else
{ {
await logger.LogError("Error Adding User {Username} {Email}", username, email); await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error); AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
} }
} }
else
{
AddModuleMessage(Localizer["Message.Username.Exists"], MessageType.Warning);
}
} }
else else
{ {
@ -182,34 +211,40 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", username, email, ex.Message); await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message);
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error); AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
} }
} }
private bool ValidateProfiles() private bool ValidateProfiles()
{ {
bool valid = true;
foreach (Profile profile in profiles) foreach (Profile profile in profiles)
{ {
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue)) var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{ {
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) if (profile.IsRequired && string.IsNullOrEmpty(value))
{ {
valid = false; AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
return false;
} }
if (valid == true && !string.IsNullOrEmpty(profile.Validation)) if (!string.IsNullOrEmpty(profile.Validation))
{ {
Regex regex = new Regex(profile.Validation); Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; bool valid = regex.Match(value).Success;
if (!valid)
{
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
return false;
} }
} }
} }
return valid; }
return true;
} }
private void ProfileChanged(ChangeEventArgs e, string SettingName) private void ProfileChanged(ChangeEventArgs e, string SettingName)

View File

@ -9,18 +9,11 @@
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null && photo != null) @if (_initialized)
{ {
<img src="@photo.Url" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
}
else
{
<br />
}
<TabStrip> <TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity"> <TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null) <ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
{
<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="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label> <Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label>
@ -58,12 +51,6 @@ else
<input id="displayname" class="form-control" @bind="@displayname" /> <input id="displayname" class="form-control" @bind="@displayname" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of the user" ResourceKey="Photo"></Label>
<div class="col-sm-9">
<FileManager FileId="@photofileid" @ref="filemanager" />
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label> <Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -86,11 +73,8 @@ else
</div> </div>
</div> </div>
</div> </div>
}
</TabPanel> </TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile"> <TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null)
{
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles) @foreach (Profile profile in profiles)
@ -124,21 +108,20 @@ else
} }
else else
{ {
@if (p.IsRequired) @if (p.Rows == 1)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
}
else
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
} }
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
}
} }
</div> </div>
</div> </div>
} }
</div> </div>
</div> </div>
}
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
@ -147,8 +130,11 @@ else
<br /> <br />
<br /> <br />
<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 bool _initialized = false;
private string _passwordrequirements;
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;
@ -157,9 +143,6 @@ else
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 int photofileid = -1;
private File photo = null;
private string isdeleted; private string isdeleted;
private string lastlogin; private string lastlogin;
private string lastipaddress; private string lastipaddress;
@ -177,31 +160,23 @@ else
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnParametersSetAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
if (PageState.QueryString.ContainsKey("id")) _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
{
_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"]);
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
{
userid = UserId;
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)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
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;
@ -215,6 +190,8 @@ else
deletedon = user.DeletedOn; deletedon = user.DeletedOn;
} }
} }
_initialized = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -237,9 +214,11 @@ else
{ {
try try
{ {
if (username != string.Empty && email != string.Empty && ValidateProfiles()) if (username != string.Empty && email != string.Empty)
{ {
if (_password == confirm) if (_password == confirm)
{
if (ValidateProfiles())
{ {
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;
@ -248,7 +227,6 @@ else
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();
if (user.PhotoFileId == -1) if (user.PhotoFileId == -1)
{ {
user.PhotoFileId = null; user.PhotoFileId = null;
@ -268,6 +246,7 @@ else
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
} }
} }
}
else else
{ {
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning); AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
@ -287,27 +266,33 @@ else
private bool ValidateProfiles() private bool ValidateProfiles()
{ {
bool valid = true;
foreach (Profile profile in profiles) foreach (Profile profile in profiles)
{ {
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue)) var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{ {
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) if (profile.IsRequired && string.IsNullOrEmpty(value))
{ {
valid = false; AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
return false;
} }
if (valid == true && !string.IsNullOrEmpty(profile.Validation)) if (!string.IsNullOrEmpty(profile.Validation))
{ {
Regex regex = new Regex(profile.Validation); Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; bool valid = regex.Match(value).Success;
if (!valid)
{
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
return false;
} }
} }
} }
return valid; }
return true;
} }
private void ProfileChanged(ChangeEventArgs e, string SettingName) private void ProfileChanged(ChangeEventArgs e, string SettingName)

View File

@ -17,27 +17,18 @@ else
{ {
<TabStrip> <TabStrip>
<TabPanel Name="Users" Heading="Users" ResourceKey="Users"> <TabPanel Name="Users" Heading="Users" ResourceKey="Users">
<div class="container"> <ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />&nbsp;
<div class="row mb-1 align-items-center"> <ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
<div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" /> <Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
</div>
<div class="col-sm-4">
<input class="form-control" @bind="@_search" />
</div>
<div class="col-sm-4">
<button type="button" class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
</div>
</div>
</div>
<Pager Items="@users" RowClass="align-middle">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th class="app-sort-th" @onclick="@(() => SortTable("Username"))">@Localizer["Username"]<i class="@(SetSortIcon("Username"))"></i></th> <th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Username"))">@Localizer["Username"]<i class="@(SetSortIcon("Username"))"></i></th>
<th class="app-sort-th" @onclick="@(() => SortTable("DisplayName"))">@Localizer["Name"]<i class="@(SetSortIcon("DisplayName"))"></i></th> <th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("DisplayName"))">@Localizer["Name"]<i class="@(SetSortIcon("DisplayName"))"></i></th>
<th class="app-sort-th" @onclick="@(() => SortTable("LastLoginOn"))">@Localizer["LastLoginOn"]<i class="@(SetSortIcon("LastLoginOn"))"></i></th> <th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Email"))">@Localizer["Email"]<i class="@(SetSortIcon("Email"))"></i></th>
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("LastLoginOn"))">@Localizer["LastLoginOn"]<i class="@(SetSortIcon("LastLoginOn"))"></i></th>
</Header> </Header>
<Row> <Row>
<td> <td>
@ -50,7 +41,8 @@ else
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" /> <ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
</td> </td>
<td>@context.User.Username</td> <td>@context.User.Username</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td> <td>@context.User.DisplayName</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.Email))</td>
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td> <td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td>
</Row> </Row>
</Pager> </Pager>
@ -106,6 +98,21 @@ else
<input id="cookiename" class="form-control" @bind="@_cookiename" /> <input id="cookiename" class="form-control" @bind="@_cookiename" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
<div class="col-sm-9">
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alwaysremember" HelpText="Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies." ResourceKey="AlwaysRemember">Always Remember User?</Label>
<div class="col-sm-9">
<select id="alwaysremember" class="form-select" @bind="@_alwaysremember">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
} }
</Section> </Section>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@ -259,6 +266,21 @@ else
<input id="parameters" class="form-control" @bind="@_parameters" /> <input id="parameters" class="form-control" @bind="@_parameters" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="authresponsetype" HelpText="Specify the authorization response type" ResourceKey="AuthResponseType">Authorization Response Type</Label>
<div class="col-sm-9">
<select id="authresponsetype" class="form-select" @bind="@_authresponsetype" required>
<option value="code">@Localizer["AuthFlow.Code"]</option>
<option value="code id_token">@Localizer["AuthFlow.CodeIdToken"]</option>
<option value="code id_token token">@Localizer["AuthFlow.CodeIdTokenToken"]</option>
<option value="code token">@Localizer["AuthFlow.CodeToken"]</option>
<option value="id_token">@Localizer["AuthFlow.IdToken"]</option>
<option value="id_token token">@Localizer["AuthFlow.IdTokenToken"]</option>
<option value="token">@Localizer["AuthFlow.Token"]</option>
<option value="none">@Localizer["AuthFlow.None"]</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="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label> <Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -316,6 +338,15 @@ else
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="verifyusers" HelpText="Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically." ResourceKey="VerifyUsers">Verify Existing Users?</Label>
<div class="col-sm-9">
<select id="verifyusers" class="form-select" @bind="@_verifyusers">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
} }
</Section> </Section>
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings"> <Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
@ -365,14 +396,14 @@ else
} }
@code { @code {
private List<UserRole> allusers;
private List<UserRole> users; private List<UserRole> users;
private string _search = "";
private string _allowregistration; private string _allowregistration;
private string _allowsitelogin; private string _allowsitelogin;
private string _twofactor; private string _twofactor;
private string _cookiename; private string _cookiename;
private string _cookieexpiration;
private string _alwaysremember;
private string _minimumlength; private string _minimumlength;
private string _uniquecharacters; private string _uniquecharacters;
@ -397,6 +428,7 @@ else
private string _scopes; private string _scopes;
private string _parameters; private string _parameters;
private string _pkce; private string _pkce;
private string _authresponsetype;
private string _redirecturl; private string _redirecturl;
private string _identifierclaimtype; private string _identifierclaimtype;
private string _emailclaimtype; private string _emailclaimtype;
@ -404,6 +436,7 @@ else
private string _profileclaimtypes; private string _profileclaimtypes;
private string _domainfilter; private string _domainfilter;
private string _createusers; private string _createusers;
private string _verifyusers;
private string _secret; private string _secret;
private string _secrettype = "password"; private string _secrettype = "password";
@ -420,7 +453,6 @@ else
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await LoadUserSettingsAsync();
await LoadUsersAsync(true); await LoadUsersAsync(true);
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
@ -431,6 +463,8 @@ else
{ {
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false"); _twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application"); _cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6"); _minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1"); _uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
@ -455,6 +489,7 @@ else
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub"); _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email"); _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
@ -462,6 +497,7 @@ else
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", ""); _profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", ""); _domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); _createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", ""); _secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
_togglesecret = SharedLocalizer["ShowPassword"]; _togglesecret = SharedLocalizer["ShowPassword"];
@ -475,32 +511,14 @@ else
{ {
if (load) if (load)
{ {
allusers = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered); users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host); var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
allusers.AddRange(hosts); users.AddRange(hosts);
allusers = allusers.OrderBy(u => u.User.DisplayName).ToList(); users = users.OrderBy(u => u.User.DisplayName).ToList();
} }
} }
users = allusers;
if (!string.IsNullOrEmpty(_search))
{
users = users.Where(item =>
(
item.User.Username.Contains(_search, StringComparison.OrdinalIgnoreCase) ||
item.User.Email.Contains(_search, StringComparison.OrdinalIgnoreCase) ||
item.User.DisplayName.Contains(_search, StringComparison.OrdinalIgnoreCase)
)
).ToList();
}
}
private async Task OnSearch()
{
await UpdateUserSettingsAsync();
await LoadUsersAsync(false);
} }
private async Task DeleteUser(UserRole UserRole) private async Task DeleteUser(UserRole UserRole)
@ -523,21 +541,6 @@ else
} }
} }
private string settingSearch = "AU-search";
private async Task LoadUserSettingsAsync()
{
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
_search = SettingService.GetSetting(settings, settingSearch, "");
}
private async Task UpdateUserSettingsAsync()
{
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
settings = SettingService.SetSetting(settings, settingSearch, _search);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
}
private async Task SaveSiteSettings() private async Task SaveSiteSettings()
{ {
try try
@ -553,6 +556,8 @@ else
{ {
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true); settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true); settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
@ -576,12 +581,14 @@ else
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true); settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true); settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
if (!string.IsNullOrEmpty(_secret) && _secret.Length < 16) _secret = (_secret + "????????????????").Substring(0, 16); if (!string.IsNullOrEmpty(_secret) && _secret.Length < 16) _secret = (_secret + "????????????????").Substring(0, 16);
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true); settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);

View File

@ -0,0 +1,69 @@
@namespace Oqtane.Modules.Admin.Users
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IStringLocalizer<Users> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="importfile" HelpText="Upload or select a tab delimited text file containing user information. The file must be in the Template format specified (Roles can be specified as a comma delimited list)." ResourceKey="ImportFile">Import File:</Label>
<div class="col-sm-9">
<FileManager Id="importfile" @ref="_filemanager" Filter="txt" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
<div class="col-sm-9">
<select id="notify" class="form-select" @bind="@_notify" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="ImportUsers">@Localizer["Import"]</button>&nbsp;
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>&nbsp;
<a class="btn btn-info" href="/users.txt" target="_new">@Localizer["Template"]</a>
@code {
private FileManager _filemanager;
public override string Title => "Import Users";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private string _notify = "True";
private async Task ImportUsers()
{
try
{
var fileid = _filemanager.GetFileId();
if (fileid != -1)
{
ShowProgressIndicator();
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
if (bool.Parse(results["Success"]))
{
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
}
HideProgressIndicator();
}
else
{
AddModuleMessage(Localizer["Message.Import.Validation"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Importing Users {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Import"], MessageType.Error);
}
}
}

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@using System.Text.Json @using System.Text.Json
@inherits LocalizableComponent @inherits LocalizableComponent
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_visible) @if (_visible)
{ {
@ -20,7 +21,7 @@
{ {
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button> <button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
} }
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@Localize("Cancel")</button> <button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -25,6 +25,8 @@
@code { @code {
private string _text = string.Empty; private string _text = string.Empty;
private int _moduleId = -1;
private string _path = string.Empty;
private string _parameters = string.Empty; private string _parameters = string.Empty;
private string _url = string.Empty; private string _url = string.Empty;
private List<Permission> _permissions; private List<Permission> _permissions;
@ -41,10 +43,13 @@
public string Text { get; set; } // optional - defaults to Action if not specified public string Text { get; set; } // optional - defaults to Action if not specified
[Parameter] [Parameter]
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y" public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
[Parameter] [Parameter]
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid public string Path { get; set; } = null; // optional - allows the link to target a specific page
[Parameter]
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y"
[Parameter] [Parameter]
public Action OnClick { get; set; } = null; // optional - executes a method in the calling component public Action OnClick { get; set; } = null; // optional - executes a method in the calling component
@ -79,6 +84,7 @@
[Parameter] [Parameter]
public string ReturnUrl { get; set; } // optional - used to set a url to redirect to public string ReturnUrl { get; set; } // optional - used to set a url to redirect to
protected override void OnInitialized() protected override void OnInitialized()
{ {
if (!string.IsNullOrEmpty(Permissions)) if (!string.IsNullOrEmpty(Permissions))
@ -102,6 +108,18 @@
_text = string.Empty; _text = string.Empty;
} }
_moduleId = ModuleState.ModuleId;
if (ModuleId != -1)
{
_moduleId = ModuleId;
}
_path = PageState.Page.Path;
if (Path != null)
{
_path = Path;
}
if (!string.IsNullOrEmpty(Parameters)) if (!string.IsNullOrEmpty(Parameters))
{ {
_parameters = Parameters; _parameters = Parameters;
@ -133,7 +151,8 @@
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList; _permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_text = Localize(nameof(Text), _text); _text = Localize(nameof(Text), _text);
_url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters);
_url = EditUrl(_path, _moduleId, Action, _parameters);
if (!string.IsNullOrEmpty(ReturnUrl)) if (!string.IsNullOrEmpty(ReturnUrl))
{ {
_url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}"; _url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}";

View File

@ -64,12 +64,12 @@
if (!String.IsNullOrEmpty(ModifiedBy)) if (!String.IsNullOrEmpty(ModifiedBy))
{ {
_text += $" {Localizer["by"]} <b>{ModifiedBy}</b>"; _text += $" {Localizer["By"]} <b>{ModifiedBy}</b>";
} }
if (ModifiedOn != null) if (ModifiedOn != null)
{ {
_text += $" {Localizer["on"]} <b>{ModifiedOn.Value.ToString(DateTimeFormat)}</b>"; _text += $" {Localizer["On"]} <b>{ModifiedOn.Value.ToString(DateTimeFormat)}</b>";
} }
_text += "</p>"; _text += "</p>";

View File

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

View File

@ -40,10 +40,17 @@
</div> </div>
</div> </div>
} }
else
{
if (FileId != -1 && _file != null && !UploadMultiple)
{
<input class="form-control" @bind="@_file.Name" disabled />
}
}
@if (ShowUpload && _haseditpermission) @if (ShowUpload && _haseditpermission)
{ {
<div class="row"> <div class="row mt-2">
<div class="col mt-2"> <div class="col">
@if (UploadMultiple) @if (UploadMultiple)
{ {
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple /> <input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
@ -53,9 +60,9 @@
<input type="file" id="@_fileinputid" name="file" accept="@_filter" /> <input type="file" id="@_fileinputid" name="file" accept="@_filter" />
} }
</div> </div>
<div class="col mt-2 text-end"> <div class="col-auto">
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button> <button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
@if (GetFileId() != -1) @if (FileId != -1 && !UploadMultiple)
{ {
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button> <button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
} }
@ -157,6 +164,15 @@
[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 void OnInitialized()
{
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid;
}
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
// packages folder is a framework folder for uploading installable nuget packages // packages folder is a framework folder for uploading installable nuget packages
@ -168,8 +184,6 @@
ShowSuccess = true; ShowSuccess = true;
} }
_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);
@ -185,6 +199,22 @@
} }
} }
if (ShowFolders)
{
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
}
else
{
if (FolderId != -1)
{
var folder = await FolderService.GetFolderAsync(FolderId);
if (folder != null)
{
_folders = new List<Folder> { folder };
}
}
}
if (FileId != -1) if (FileId != -1)
{ {
File file = await FileService.GetFileAsync(FileId); File file = await FileService.GetFileAsync(FileId);
@ -209,12 +239,6 @@
await GetFiles(); await GetFiles();
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid;
_initialized = true; _initialized = true;
} }
@ -324,7 +348,8 @@
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 filename = upload.Split(':')[0];
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower())) if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{ {
restricted += (restricted == "" ? "" : ",") + extension; restricted += (restricted == "" ? "" : ",") + extension;
@ -351,19 +376,38 @@
while (upload < uploads.Length && success) while (upload < uploads.Length && success)
{ {
success = false; success = false;
// note that progressive retry will only wait a maximum of 15 seconds which may not be long enough for very large file uploads var filename = uploads[upload].Split(':')[0];
var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes
var megabits = (size / 1048576.0) * 8; // binary conversion
var uploadspeed = 2; // 2 Mbps (3G ranges from 300Kbps to 3Mbps)
var uploadtime = (megabits / uploadspeed); // seconds
var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds)
var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds
int attempts = 0; int attempts = 0;
while (attempts < 5 && !success) while (attempts < maxattempts && !success)
{ {
attempts += 1; attempts += 1;
Thread.Sleep(1000 * attempts); // progressive retry Thread.Sleep(sleep);
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[upload]); if (Folder == Constants.PackagesFolder)
{
var files = await FileService.GetFilesAsync(folder);
if (files != null && files.Any(item => item.Name == filename))
{
success = true;
}
}
else
{
var file = await FileService.GetFileAsync(int.Parse(folder), filename);
if (file != null) if (file != null)
{ {
success = true; success = true;
} }
} }
}
if (success) if (success)
{ {
upload++; upload++;
@ -373,8 +417,8 @@
// reset progress indicators // reset progress indicators
if (ShowProgress) if (ShowProgress)
{ {
await interop.SetElementAttribute(_guid + "ProgressInfo", "style", "display: none;"); await interop.SetElementAttribute(_progressinfoid, "style", "display: none;");
await interop.SetElementAttribute(_guid + "ProgressBar", "style", "display: none;"); await interop.SetElementAttribute(_progressbarid, "style", "display: none;");
} }
else else
{ {
@ -405,11 +449,12 @@
else else
{ {
// set FileId to first file in upload collection // set FileId to first file in upload collection
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0]); var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
if (file != null) if (file != null)
{ {
FileId = file.FileId; FileId = file.FileId;
await SetImage(); await SetImage();
await OnSelect.InvokeAsync(FileId);
await OnUpload.InvokeAsync(FileId); await OnUpload.InvokeAsync(FileId);
} }
await GetFiles(); await GetFiles();
@ -456,6 +501,7 @@
await GetFiles(); await GetFiles();
FileId = -1; FileId = -1;
await SetImage(); await SetImage();
await OnSelect.InvokeAsync(FileId);
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)

View File

@ -0,0 +1,50 @@
@namespace Oqtane.Modules.Controls
@inherits LocalizableComponent
<input type="text" value="@Value" list="@_id" class="form-control" @onchange="(e => OnChange(e))" />
<datalist id="@_id" value="@Value">
@foreach(var kvp in DataList)
{
if (!string.IsNullOrEmpty(kvp.Value))
{
<option value="@kvp.Key">@Localize(kvp.Value, kvp.Value)</option>
}
else
{
<option value="@kvp.Key">@Localize(kvp.Key, kvp.Key)</option>
}
}
</datalist>
@code {
private string _id;
[Parameter]
public string Value { get; set; }
[EditorRequired]
[Parameter]
public Dictionary<string, string> DataList { get; set; }
[EditorRequired]
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
protected override void OnInitialized()
{
// create unique id for component
_id = "DataList_" + Guid.NewGuid().ToString("N");
}
protected void OnChange(ChangeEventArgs e)
{
if (!string.IsNullOrEmpty(e.Value.ToString()))
{
Value = e.Value.ToString();
if (ValueChanged.HasDelegate)
{
ValueChanged.InvokeAsync(Value);
}
}
}
}

View File

@ -1,10 +1,20 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleControlBase @inherits ModuleControlBase
@inject IStringLocalizerFactory LocalizerFactory @inject IStringLocalizerFactory LocalizerFactory
@inject IStringLocalizer<SharedResources> SharedLocalizer
@typeparam TableItem @typeparam TableItem
@if (ItemList != null) @if (ItemList != null)
{ {
@if (!string.IsNullOrEmpty(SearchProperties))
{
<div class="input-group my-3">
<input id="search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</div>
}
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems) @if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-center my-2">
@ -175,6 +185,9 @@
private int _startPage = 0; private int _startPage = 0;
private int _endPage = 0; private int _endPage = 0;
private int _columns = 0; private int _columns = 0;
private string _search = "";
private IEnumerable<TableItem> AllItems;
[Parameter] [Parameter]
public string Format { get; set; } // Table or Grid public string Format { get; set; } // Table or Grid
@ -221,6 +234,9 @@
[Parameter] [Parameter]
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
[Parameter]
public string SearchProperties { get; set; } // comma delimited list of property names to include in search
private IEnumerable<TableItem> ItemList { get; set; } private IEnumerable<TableItem> ItemList { get; set; }
protected override void OnInitialized() protected override void OnInitialized()
@ -276,6 +292,15 @@
} }
} }
if (!string.IsNullOrEmpty(SearchProperties))
{
AllItems = Items; // only used in search
if (!string.IsNullOrEmpty(_search))
{
Search();
}
}
if (!string.IsNullOrEmpty(PageSize)) if (!string.IsNullOrEmpty(PageSize))
{ {
_maxItems = int.Parse(PageSize); _maxItems = int.Parse(PageSize);
@ -369,4 +394,75 @@
UpdateList(_page); UpdateList(_page);
} }
public void Search()
{
if (!string.IsNullOrEmpty(_search))
{
Items = AllItems.Where(item =>
{
var values = SearchProperties.Split(',')
.Select(itemType => GetPropertyValue(item, itemType))
.Where(value => value != null)
.Select(value => value.ToString().ToLower());
return values.Any(value => value.Contains(_search.ToLower()));
}).ToList();
}
else
{
Items = AllItems;
}
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
UpdateList(1);
}
private object GetPropertyValue(object obj, string propertyName)
{
var index = propertyName.IndexOf(".");
if (index != -1)
{
var propertyInfo = obj.GetType().GetProperty(propertyName.Substring(0, index));
if (propertyInfo != null)
{
return GetPropertyValue(propertyInfo.GetValue(obj), propertyName.Substring(index + 1));
}
return null;
}
else
{
var propertyInfo = obj.GetType().GetProperty(propertyName);
if (propertyInfo != null)
{
return propertyInfo.GetValue(obj);
}
return null;
}
}
public void Reset()
{
_search = "";
Items = AllItems;
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
UpdateList(1);
}
private string FormatSearchProperties()
{
var properties = new List<string>();
foreach (var property in SearchProperties.Split(',', StringSplitOptions.RemoveEmptyEntries))
{
var index = property.LastIndexOf(".");
if (index != -1)
{
properties.Add(property.Substring(index + 1));
}
else
{
properties.Add(property);
}
}
return string.Join(",", properties);
}
} }

View File

@ -5,7 +5,7 @@
<div class="row" style="margin-bottom: 50px;"> <div class="row" style="margin-bottom: 50px;">
<div class="col"> <div class="col">
<TabStrip> <TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor"> <TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@if (_richfilemanager) @if (_richfilemanager)
{ {
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" /> <FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />

View File

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

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 System.Dynamic;
namespace Oqtane.Modules namespace Oqtane.Modules
{ {
@ -280,13 +281,17 @@ namespace Oqtane.Modules
public void SetModuleTitle(string title) public void SetModuleTitle(string title)
{ {
var obj = new { PageModuleId = ModuleState.PageModuleId, Title = title }; dynamic obj = new ExpandoObject();
obj.PageModuleId = ModuleState.PageModuleId;
obj.Title = title;
SiteState.Properties.ModuleTitle = obj; SiteState.Properties.ModuleTitle = obj;
} }
public void SetModuleVisibility(bool visible) public void SetModuleVisibility(bool visible)
{ {
var obj = new { PageModuleId = ModuleState.PageModuleId, Visible = visible }; dynamic obj = new ExpandoObject();
obj.PageModuleId = ModuleState.PageModuleId;
obj.Visible = visible;
SiteState.Properties.ModuleVisibility = obj; SiteState.Properties.ModuleVisibility = obj;
} }

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>4.0.1</Version> <Version>5.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>
@ -12,7 +12,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/v4.0.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.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>
@ -21,12 +21,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="7.0.1" /> <PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
</ItemGroup> </ItemGroup>

View File

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

View File

@ -0,0 +1,789 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Icon.AccountLogin" xml:space="preserve">
<value>Account Login</value>
</data>
<data name="Icon.AccountLogout" xml:space="preserve">
<value>Account Logout</value>
</data>
<data name="Icon.ActionRedo" xml:space="preserve">
<value>Action Redo</value>
</data>
<data name="Icon.ActionUndo" xml:space="preserve">
<value>Action Undo</value>
</data>
<data name="Icon.AlignCenter" xml:space="preserve">
<value>Align Center</value>
</data>
<data name="Icon.AlignLeft" xml:space="preserve">
<value>Align Left</value>
</data>
<data name="Icon.AlignRight" xml:space="preserve">
<value>Align Right</value>
</data>
<data name="Icon.Aperture" xml:space="preserve">
<value>Aperture</value>
</data>
<data name="Icon.ArrowBottom" xml:space="preserve">
<value>Arrow Bottom</value>
</data>
<data name="Icon.ArrowCircleBottom" xml:space="preserve">
<value>Arrow Circle Bottom</value>
</data>
<data name="Icon.ArrowCircleLeft" xml:space="preserve">
<value>Arrow Circle Left</value>
</data>
<data name="Icon.ArrowCircleRight" xml:space="preserve">
<value>Arrow Circle Right</value>
</data>
<data name="Icon.ArrowCircleTop" xml:space="preserve">
<value>Arrow Circle Top</value>
</data>
<data name="Icon.ArrowLeft" xml:space="preserve">
<value>Arrow Left</value>
</data>
<data name="Icon.ArrowRight" xml:space="preserve">
<value>Arrow Right</value>
</data>
<data name="Icon.ArrowThickBottom" xml:space="preserve">
<value>Arrow Thick Bottom</value>
</data>
<data name="Icon.ArrowThickLeft" xml:space="preserve">
<value>Arrow Thick Left</value>
</data>
<data name="Icon.ArrowThickRight" xml:space="preserve">
<value>Arrow Thick Right</value>
</data>
<data name="Icon.ArrowThickTop" xml:space="preserve">
<value>Arrow Thick Top</value>
</data>
<data name="Icon.ArrowTop" xml:space="preserve">
<value>Arrow Top</value>
</data>
<data name="Icon.AudioSpectrum" xml:space="preserve">
<value>Audio Spectrum</value>
</data>
<data name="Icon.Audio" xml:space="preserve">
<value>Audio</value>
</data>
<data name="Icon.Badge" xml:space="preserve">
<value>Badge</value>
</data>
<data name="Icon.Ban" xml:space="preserve">
<value>Ban</value>
</data>
<data name="Icon.BarChart" xml:space="preserve">
<value>Bar Chart</value>
</data>
<data name="Icon.Basket" xml:space="preserve">
<value>Basket</value>
</data>
<data name="Icon.BatteryEmpty" xml:space="preserve">
<value>Battery Empty</value>
</data>
<data name="Icon.BatteryFull" xml:space="preserve">
<value>Battery Full</value>
</data>
<data name="Icon.Beaker" xml:space="preserve">
<value>Beaker</value>
</data>
<data name="Icon.Bell" xml:space="preserve">
<value>Bell</value>
</data>
<data name="Icon.Bluetooth" xml:space="preserve">
<value>Bluetooth</value>
</data>
<data name="Icon.Bold" xml:space="preserve">
<value>Bold</value>
</data>
<data name="Icon.Bolt" xml:space="preserve">
<value>Bolt</value>
</data>
<data name="Icon.Book" xml:space="preserve">
<value>Book</value>
</data>
<data name="Icon.Bookmark" xml:space="preserve">
<value>Bookmark</value>
</data>
<data name="Icon.Box" xml:space="preserve">
<value>Box</value>
</data>
<data name="Icon.Briefcase" xml:space="preserve">
<value>Briefcase</value>
</data>
<data name="Icon.BritishPound" xml:space="preserve">
<value>British Pound</value>
</data>
<data name="Icon.Browser" xml:space="preserve">
<value>Browser</value>
</data>
<data name="Icon.Brush" xml:space="preserve">
<value>Brush</value>
</data>
<data name="Icon.Bug" xml:space="preserve">
<value>Bug</value>
</data>
<data name="Icon.Bullhorn" xml:space="preserve">
<value>Bullhorn</value>
</data>
<data name="Icon.Calculator" xml:space="preserve">
<value>Calculator</value>
</data>
<data name="Icon.Calendar" xml:space="preserve">
<value>Calendar</value>
</data>
<data name="Icon.CameraSlr" xml:space="preserve">
<value>Camera Slr</value>
</data>
<data name="Icon.CaretBottom" xml:space="preserve">
<value>Caret Bottom</value>
</data>
<data name="Icon.CaretLeft" xml:space="preserve">
<value>Caret Left</value>
</data>
<data name="Icon.CaretRight" xml:space="preserve">
<value>Caret Right</value>
</data>
<data name="Icon.CaretTop" xml:space="preserve">
<value>Caret Top</value>
</data>
<data name="Icon.Cart" xml:space="preserve">
<value>Cart</value>
</data>
<data name="Icon.Chat" xml:space="preserve">
<value>Chat</value>
</data>
<data name="Icon.Check" xml:space="preserve">
<value>Check</value>
</data>
<data name="Icon.ChevronBottom" xml:space="preserve">
<value>Chevron Bottom</value>
</data>
<data name="Icon.ChevronLeft" xml:space="preserve">
<value>Chevron Left</value>
</data>
<data name="Icon.ChevronRight" xml:space="preserve">
<value>Chevron Right</value>
</data>
<data name="Icon.ChevronTop" xml:space="preserve">
<value>Chevron Top</value>
</data>
<data name="Icon.CircleCheck" xml:space="preserve">
<value>Circle Check</value>
</data>
<data name="Icon.CircleX" xml:space="preserve">
<value>Circle X</value>
</data>
<data name="Icon.Clipboard" xml:space="preserve">
<value>Clipboard</value>
</data>
<data name="Icon.Clock" xml:space="preserve">
<value>Clock</value>
</data>
<data name="Icon.CloudDownload" xml:space="preserve">
<value>Cloud Download</value>
</data>
<data name="Icon.CloudUpload" xml:space="preserve">
<value>Cloud Upload</value>
</data>
<data name="Icon.Cloud" xml:space="preserve">
<value>Cloud</value>
</data>
<data name="Icon.Cloudy" xml:space="preserve">
<value>Cloudy</value>
</data>
<data name="Icon.Code" xml:space="preserve">
<value>Code</value>
</data>
<data name="Icon.Cog" xml:space="preserve">
<value>Cog</value>
</data>
<data name="Icon.CollapseDown" xml:space="preserve">
<value>Collapse Down</value>
</data>
<data name="Icon.CollapseLeft" xml:space="preserve">
<value>Collapse Left</value>
</data>
<data name="Icon.CollapseRight" xml:space="preserve">
<value>Collapse Right</value>
</data>
<data name="Icon.CollapseUp" xml:space="preserve">
<value>Collapse Up</value>
</data>
<data name="Icon.Command" xml:space="preserve">
<value>Command</value>
</data>
<data name="Icon.CommentSquare" xml:space="preserve">
<value>Comment Square</value>
</data>
<data name="Icon.Compass" xml:space="preserve">
<value>Compass</value>
</data>
<data name="Icon.Contrast" xml:space="preserve">
<value>Contrast</value>
</data>
<data name="Icon.Copywriting" xml:space="preserve">
<value>Copywriting</value>
</data>
<data name="Icon.CreditCard" xml:space="preserve">
<value>Credit Card</value>
</data>
<data name="Icon.Crop" xml:space="preserve">
<value>Crop</value>
</data>
<data name="Icon.Dashboard" xml:space="preserve">
<value>Dashboard</value>
</data>
<data name="Icon.DataTransferDownload" xml:space="preserve">
<value>Data Transfer Download</value>
</data>
<data name="Icon.DataTransferUpload" xml:space="preserve">
<value>Data Transfer Upload</value>
</data>
<data name="Icon.Delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Icon.Dial" xml:space="preserve">
<value>Dial</value>
</data>
<data name="Icon.Document" xml:space="preserve">
<value>Document</value>
</data>
<data name="Icon.Dollar" xml:space="preserve">
<value>Dollar</value>
</data>
<data name="Icon.DoubleQuoteSansLeft" xml:space="preserve">
<value>Double Quote Sans Left</value>
</data>
<data name="Icon.DoubleQuoteSansRight" xml:space="preserve">
<value>Double Quote Sans Right</value>
</data>
<data name="Icon.DoubleQuoteSerifLeft" xml:space="preserve">
<value>Double Quote Serif Left</value>
</data>
<data name="Icon.DoubleQuoteSerifRight" xml:space="preserve">
<value>Double Quote Serif Right</value>
</data>
<data name="Icon.Droplet" xml:space="preserve">
<value>Droplet</value>
</data>
<data name="Icon.Eject" xml:space="preserve">
<value>Eject</value>
</data>
<data name="Icon.Elevator" xml:space="preserve">
<value>Elevator</value>
</data>
<data name="Icon.Ellipses" xml:space="preserve">
<value>Ellipses</value>
</data>
<data name="Icon.EnvelopeClosed" xml:space="preserve">
<value>Envelope Closed</value>
</data>
<data name="Icon.EnvelopeOpen" xml:space="preserve">
<value>Envelope Open</value>
</data>
<data name="Icon.Euro" xml:space="preserve">
<value>Euro</value>
</data>
<data name="Icon.Excerpt" xml:space="preserve">
<value>Excerpt</value>
</data>
<data name="Icon.ExpandDown" xml:space="preserve">
<value>Expand Down</value>
</data>
<data name="Icon.ExpandLeft" xml:space="preserve">
<value>Expand Left</value>
</data>
<data name="Icon.ExpandRight" xml:space="preserve">
<value>Expand Right</value>
</data>
<data name="Icon.ExpandUp" xml:space="preserve">
<value>Expand Up</value>
</data>
<data name="Icon.ExternalLink" xml:space="preserve">
<value>External Link</value>
</data>
<data name="Icon.Eye" xml:space="preserve">
<value>Eye</value>
</data>
<data name="Icon.Eyedropper" xml:space="preserve">
<value>Eyedropper</value>
</data>
<data name="Icon.File" xml:space="preserve">
<value>File</value>
</data>
<data name="Icon.Fire" xml:space="preserve">
<value>Fire</value>
</data>
<data name="Icon.Flag" xml:space="preserve">
<value>Flag</value>
</data>
<data name="Icon.Flash" xml:space="preserve">
<value>Flash</value>
</data>
<data name="Icon.Folder" xml:space="preserve">
<value>Folder</value>
</data>
<data name="Icon.Fork" xml:space="preserve">
<value>Fork</value>
</data>
<data name="Icon.FullscreenEnter" xml:space="preserve">
<value>Fullscreen Enter</value>
</data>
<data name="Icon.FullscreenExit" xml:space="preserve">
<value>Fullscreen Exit</value>
</data>
<data name="Icon.Globe" xml:space="preserve">
<value>Globe</value>
</data>
<data name="Icon.Graph" xml:space="preserve">
<value>Graph</value>
</data>
<data name="Icon.GridFourUp" xml:space="preserve">
<value>Grid Four Up</value>
</data>
<data name="Icon.GridThreeUp" xml:space="preserve">
<value>Grid Three Up</value>
</data>
<data name="Icon.GridTwoUp" xml:space="preserve">
<value>Grid Two Up</value>
</data>
<data name="Icon.HardDrive" xml:space="preserve">
<value>Hard Drive</value>
</data>
<data name="Icon.Header" xml:space="preserve">
<value>Header</value>
</data>
<data name="Icon.Headphones" xml:space="preserve">
<value>Headphones</value>
</data>
<data name="Icon.Heart" xml:space="preserve">
<value>Heart</value>
</data>
<data name="Icon.Home" xml:space="preserve">
<value>Home</value>
</data>
<data name="Icon.Image" xml:space="preserve">
<value>Image</value>
</data>
<data name="Icon.Inbox" xml:space="preserve">
<value>Inbox</value>
</data>
<data name="Icon.Infinity" xml:space="preserve">
<value>Infinity</value>
</data>
<data name="Icon.Info" xml:space="preserve">
<value>Info</value>
</data>
<data name="Icon.Italic" xml:space="preserve">
<value>Italic</value>
</data>
<data name="Icon.JustifyCenter" xml:space="preserve">
<value>Justify Center</value>
</data>
<data name="Icon.JustifyLeft" xml:space="preserve">
<value>Justify Left</value>
</data>
<data name="Icon.JustifyRight" xml:space="preserve">
<value>Justify Right</value>
</data>
<data name="Icon.Key" xml:space="preserve">
<value>Key</value>
</data>
<data name="Icon.Laptop" xml:space="preserve">
<value>Laptop</value>
</data>
<data name="Icon.Layers" xml:space="preserve">
<value>Layers</value>
</data>
<data name="Icon.Lightbulb" xml:space="preserve">
<value>Lightbulb</value>
</data>
<data name="Icon.LinkBroken" xml:space="preserve">
<value>Link Broken</value>
</data>
<data name="Icon.LinkIntact" xml:space="preserve">
<value>Link Intact</value>
</data>
<data name="Icon.ListRich" xml:space="preserve">
<value>List Rich</value>
</data>
<data name="Icon.List" xml:space="preserve">
<value>List</value>
</data>
<data name="Icon.Location" xml:space="preserve">
<value>Location</value>
</data>
<data name="Icon.LockLocked" xml:space="preserve">
<value>Lock Locked</value>
</data>
<data name="Icon.LockUnlocked" xml:space="preserve">
<value>Lock Unlocked</value>
</data>
<data name="Icon.LoopCircular" xml:space="preserve">
<value>Loop Circular</value>
</data>
<data name="Icon.LoopSquare" xml:space="preserve">
<value>Loop Square</value>
</data>
<data name="Icon.Loop" xml:space="preserve">
<value>Loop</value>
</data>
<data name="Icon.MagnifyingGlass" xml:space="preserve">
<value>Magnifying Glass</value>
</data>
<data name="Icon.MapMarker" xml:space="preserve">
<value>Map Marker</value>
</data>
<data name="Icon.Map" xml:space="preserve">
<value>Map</value>
</data>
<data name="Icon.MediaPause" xml:space="preserve">
<value>Media Pause</value>
</data>
<data name="Icon.MediaPlay" xml:space="preserve">
<value>Media Play</value>
</data>
<data name="Icon.MediaRecord" xml:space="preserve">
<value>Media Record</value>
</data>
<data name="Icon.MediaSkipBackward" xml:space="preserve">
<value>Media Skip Backward</value>
</data>
<data name="Icon.MediaSkipForward" xml:space="preserve">
<value>Media Skip Forward</value>
</data>
<data name="Icon.MediaStepBackward" xml:space="preserve">
<value>Media Step Backward</value>
</data>
<data name="Icon.MediaStepForward" xml:space="preserve">
<value>Media Step Forward</value>
</data>
<data name="Icon.MediaStop" xml:space="preserve">
<value>Media Stop</value>
</data>
<data name="Icon.MedicalCross" xml:space="preserve">
<value>Medical Cross</value>
</data>
<data name="Icon.Menu" xml:space="preserve">
<value>Menu</value>
</data>
<data name="Icon.Microphone" xml:space="preserve">
<value>Microphone</value>
</data>
<data name="Icon.Minus" xml:space="preserve">
<value>Minus</value>
</data>
<data name="Icon.Monitor" xml:space="preserve">
<value>Monitor</value>
</data>
<data name="Icon.Moon" xml:space="preserve">
<value>Moon</value>
</data>
<data name="Icon.Move" xml:space="preserve">
<value>Move</value>
</data>
<data name="Icon.MusicalNote" xml:space="preserve">
<value>Musical Note</value>
</data>
<data name="Icon.Paperclip" xml:space="preserve">
<value>Paperclip</value>
</data>
<data name="Icon.Pencil" xml:space="preserve">
<value>Pencil</value>
</data>
<data name="Icon.People" xml:space="preserve">
<value>People</value>
</data>
<data name="Icon.Person" xml:space="preserve">
<value>Person</value>
</data>
<data name="Icon.Phone" xml:space="preserve">
<value>Phone</value>
</data>
<data name="Icon.PieChart" xml:space="preserve">
<value>Pie Chart</value>
</data>
<data name="Icon.Pin" xml:space="preserve">
<value>Pin</value>
</data>
<data name="Icon.PlayCircle" xml:space="preserve">
<value>Play Circle</value>
</data>
<data name="Icon.Plus" xml:space="preserve">
<value>Plus</value>
</data>
<data name="Icon.PowerStandby" xml:space="preserve">
<value>Power Standby</value>
</data>
<data name="Icon.Print" xml:space="preserve">
<value>Print</value>
</data>
<data name="Icon.Project" xml:space="preserve">
<value>Project</value>
</data>
<data name="Icon.Pulse" xml:space="preserve">
<value>Pulse</value>
</data>
<data name="Icon.PuzzlePiece" xml:space="preserve">
<value>Puzzle Piece</value>
</data>
<data name="Icon.QuestionMark" xml:space="preserve">
<value>Question Mark</value>
</data>
<data name="Icon.Rain" xml:space="preserve">
<value>Rain</value>
</data>
<data name="Icon.Random" xml:space="preserve">
<value>Random</value>
</data>
<data name="Icon.Reload" xml:space="preserve">
<value>Reload</value>
</data>
<data name="Icon.ResizeBoth" xml:space="preserve">
<value>Resize Both</value>
</data>
<data name="Icon.ResizeHeight" xml:space="preserve">
<value>Resize Height</value>
</data>
<data name="Icon.ResizeWidth" xml:space="preserve">
<value>Resize Width</value>
</data>
<data name="Icon.RssAlt" xml:space="preserve">
<value>Rss Alt</value>
</data>
<data name="Icon.Rss" xml:space="preserve">
<value>Rss</value>
</data>
<data name="Icon.Script" xml:space="preserve">
<value>Script</value>
</data>
<data name="Icon.ShareBoxed" xml:space="preserve">
<value>Share Boxed</value>
</data>
<data name="Icon.Share" xml:space="preserve">
<value>Share</value>
</data>
<data name="Icon.Shield" xml:space="preserve">
<value>Shield</value>
</data>
<data name="Icon.Signal" xml:space="preserve">
<value>Signal</value>
</data>
<data name="Icon.Signpost" xml:space="preserve">
<value>Signpost</value>
</data>
<data name="Icon.SortAscending" xml:space="preserve">
<value>Sort Ascending</value>
</data>
<data name="Icon.SortDescending" xml:space="preserve">
<value>Sort Descending</value>
</data>
<data name="Icon.Spreadsheet" xml:space="preserve">
<value>Spreadsheet</value>
</data>
<data name="Icon.Star" xml:space="preserve">
<value>Star</value>
</data>
<data name="Icon.Sun" xml:space="preserve">
<value>Sun</value>
</data>
<data name="Icon.Tablet" xml:space="preserve">
<value>Tablet</value>
</data>
<data name="Icon.Tag" xml:space="preserve">
<value>Tag</value>
</data>
<data name="Icon.Tags" xml:space="preserve">
<value>Tags</value>
</data>
<data name="Icon.Target" xml:space="preserve">
<value>Target</value>
</data>
<data name="Icon.Task" xml:space="preserve">
<value>Task</value>
</data>
<data name="Icon.Terminal" xml:space="preserve">
<value>Terminal</value>
</data>
<data name="Icon.Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="Icon.ThumbDown" xml:space="preserve">
<value>Thumb Down</value>
</data>
<data name="Icon.ThumbUp" xml:space="preserve">
<value>Thumb Up</value>
</data>
<data name="Icon.Timer" xml:space="preserve">
<value>Timer</value>
</data>
<data name="Icon.Transfer" xml:space="preserve">
<value>Transfer</value>
</data>
<data name="Icon.Trash" xml:space="preserve">
<value>Trash</value>
</data>
<data name="Icon.Underline" xml:space="preserve">
<value>Underline</value>
</data>
<data name="Icon.VerticalAlignBottom" xml:space="preserve">
<value>Vertical Align Bottom</value>
</data>
<data name="Icon.VerticalAlignCenter" xml:space="preserve">
<value>Vertical Align Center</value>
</data>
<data name="Icon.VerticalAlignTop" xml:space="preserve">
<value>Vertical Align Top</value>
</data>
<data name="Icon.Video" xml:space="preserve">
<value>Video</value>
</data>
<data name="Icon.VolumeHigh" xml:space="preserve">
<value>Volume High</value>
</data>
<data name="Icon.VolumeLow" xml:space="preserve">
<value>Volume Low</value>
</data>
<data name="Icon.VolumeOff" xml:space="preserve">
<value>Volume Off</value>
</data>
<data name="Icon.Warning" xml:space="preserve">
<value>Warning</value>
</data>
<data name="Icon.Wifi" xml:space="preserve">
<value>Wifi</value>
</data>
<data name="Icon.Wrench" xml:space="preserve">
<value>Wrench</value>
</data>
<data name="Icon.X" xml:space="preserve">
<value>X</value>
</data>
<data name="Icon.Yen" xml:space="preserve">
<value>Yen</value>
</data>
<data name="Icon.ZoomIn" xml:space="preserve">
<value>Zoom In</value>
</data>
<data name="Icon.ZoomOut" xml:space="preserve">
<value>Zoom Out</value>
</data>
</root>

View File

@ -162,4 +162,7 @@
<data name="Name.Text" xml:space="preserve"> <data name="Name.Text" xml:space="preserve">
<value>Name:</value> <value>Name:</value>
</data> </data>
<data name="DownloadFiles.Heading" xml:space="preserve">
<value>Download Files</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
@ -150,4 +150,7 @@
<data name="Description.Text" xml:space="preserve"> <data name="Description.Text" xml:space="preserve">
<value>Description:</value> <value>Description:</value>
</data> </data>
<data name="File Management" xml:space="preserve">
<value>File Management</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
@ -178,9 +178,21 @@
<value>Capacity:</value> <value>Capacity:</value>
</data> </data>
<data name="ImageSizes.HelpText" xml:space="preserve"> <data name="ImageSizes.HelpText" xml:space="preserve">
<value>Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,x200,200x)</value> <value>Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes.</value>
</data> </data>
<data name="ImageSizes.Text" xml:space="preserve"> <data name="ImageSizes.Text" xml:space="preserve">
<value>Image Sizes:</value> <value>Image Sizes:</value>
</data> </data>
<data name="FolderManagement.Title" xml:space="preserve">
<value>Folder Management!</value>
</data>
<data name="Private" xml:space="preserve">
<value>Private</value>
</data>
<data name="Public" xml:space="preserve">
<value>Public</value>
</data>
<data name="Folder Management" xml:space="preserve">
<value>Folder Management</value>
</data>
</root> </root>

View File

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

View File

@ -124,7 +124,7 @@
<value>Error Loading Packages</value> <value>Error Loading Packages</value>
</data> </data>
<data name="Success.Module.Download" xml:space="preserve"> <data name="Success.Module.Download" xml:space="preserve">
<value>Module Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value> <value>Module Package Downloaded 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>
@ -136,9 +136,12 @@
<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>
</data> </data>
<data name="Download.Heading" xml:space="preserve"> <data name="Download.Heading" xml:space="preserve">
<value>Download</value> <value>Marketplace</value>
</data> </data>
<data name="Upload.Heading" xml:space="preserve"> <data name="Upload.Heading" xml:space="preserve">
<value>Upload</value> <value>Upload</value>
</data> </data>
<data name="Product" xml:space="preserve">
<value>Product</value>
</data>
</root> </root>

View File

@ -132,6 +132,9 @@
<data name="Message.Require.ValidName" xml:space="preserve"> <data name="Message.Require.ValidName" xml:space="preserve">
<value>You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value> <value>You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value>
</data> </data>
<data name="Message.Require.ValidDescription" xml:space="preserve">
<value>You Must Provide A Valid Description (ie. No Punctuation)</value>
</data>
<data name="OwnerName.HelpText" xml:space="preserve"> <data name="OwnerName.HelpText" xml:space="preserve">
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.</value> <value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.</value>
</data> </data>

View File

@ -196,7 +196,7 @@
<value>Information</value> <value>Information</value>
</data> </data>
<data name="PackageName.HelpText" xml:space="preserve"> <data name="PackageName.HelpText" xml:space="preserve">
<value>The unique name of the package from which this module was installed</value> <value>The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification.</value>
</data> </data>
<data name="PackageName.Text" xml:space="preserve"> <data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value> <value>Package Name:</value>
@ -225,4 +225,19 @@
<data name="IsEnabled.Text" xml:space="preserve"> <data name="IsEnabled.Text" xml:space="preserve">
<value>Enabled?</value> <value>Enabled?</value>
</data> </data>
<data name="View License" xml:space="preserve">
<value>View License</value>
</data>
<data name="Error.Validate" xml:space="preserve">
<value>Error Validating Package</value>
</data>
<data name="Message.Download" xml:space="preserve">
<value>Package Version Has Been Verified. Please Select The Download Button To Obtain The Package.</value>
</data>
<data name="Message.Validate" xml:space="preserve">
<value>This Package Version Has Not Been Registered In The Oqtane Marketplace Or You Do Not Have The Right To Use It From This Installation</value>
</data>
<data name="Validate" xml:space="preserve">
<value>Validate</value>
</data>
</root> </root>

View File

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

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
@ -132,4 +132,7 @@
<data name="Success.Content.Export" xml:space="preserve"> <data name="Success.Content.Export" xml:space="preserve">
<value>Content Exported Successfully</value> <value>Content Exported Successfully</value>
</data> </data>
<data name="Export Content" xml:space="preserve">
<value>Export Content</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
@ -138,4 +138,7 @@
<data name="Message.Required.ImportContent" xml:space="preserve"> <data name="Message.Required.ImportContent" xml:space="preserve">
<value>You Must Enter Some Content To Import</value> <value>You Must Enter Some Content To Import</value>
</data> </data>
<data name="Import Content" xml:space="preserve">
<value>Import Content</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
@ -156,4 +156,13 @@
<data name="Module.Text" xml:space="preserve"> <data name="Module.Text" xml:space="preserve">
<value>Module:</value> <value>Module:</value>
</data> </data>
<data name="Module Settings" xml:space="preserve">
<value>Module Settings</value>
</data>
<data name="Pane.HelpText" xml:space="preserve">
<value>The pane where the module will be displayed</value>
</data>
<data name="Pane.Text" xml:space="preserve">
<value>Pane:</value>
</data>
</root> </root>

View File

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

View File

@ -189,4 +189,10 @@
<data name="Validation.Text" xml:space="preserve"> <data name="Validation.Text" xml:space="preserve">
<value>Validation: </value> <value>Validation: </value>
</data> </data>
<data name="Rows.HelpText" xml:space="preserve">
<value>The number of rows for text entry (one is the default)</value>
</data>
<data name="Rows.Text" xml:space="preserve">
<value>Rows: </value>
</data>
</root> </root>

View File

@ -138,4 +138,13 @@
<data name="EditProfile.Text" xml:space="preserve"> <data name="EditProfile.Text" xml:space="preserve">
<value>Edit</value> <value>Edit</value>
</data> </data>
<data name="Category" xml:space="preserve">
<value>Category</value>
</data>
<data name="Order" xml:space="preserve">
<value>Order</value>
</data>
<data name="Title" xml:space="preserve">
<value>Title</value>
</data>
</root> </root>

View File

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

View File

@ -136,7 +136,7 @@
<value>User Account Created. Please Check Your Email For Verification Instructions.</value> <value>User Account Created. Please Check Your Email For Verification Instructions.</value>
</data> </data>
<data name="Error.User.AddInfo" xml:space="preserve"> <data name="Error.User.AddInfo" xml:space="preserve">
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.</value> <value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use.</value>
</data> </data>
<data name="Message.Password.NoMatch" xml:space="preserve"> <data name="Message.Password.NoMatch" xml:space="preserve">
<value>Passwords Entered Do Not Match</value> <value>Passwords Entered Do Not Match</value>
@ -177,19 +177,4 @@
<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

@ -312,6 +312,9 @@
<data name="Database.HelpText" xml:space="preserve"> <data name="Database.HelpText" xml:space="preserve">
<value>The type of database</value> <value>The type of database</value>
</data> </data>
<data name="DeleteSite.Header" xml:space="preserve">
<value>Delete Site</value>
</data>
<data name="DeleteSite.Text" xml:space="preserve"> <data name="DeleteSite.Text" xml:space="preserve">
<value>Delete Site</value> <value>Delete Site</value>
</data> </data>
@ -381,4 +384,22 @@
<data name="Version.Text" xml:space="preserve"> <data name="Version.Text" xml:space="preserve">
<value>Version:</value> <value>Version:</value>
</data> </data>
<data name="DeleteAlias.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="DeleteAlias.Header" xml:space="preserve">
<value>Delete Alias</value>
</data>
<data name="SiteGuid.HelpText" xml:space="preserve">
<value>The Unique Identifier For The Site</value>
</data>
<data name="SiteGuid.Text" xml:space="preserve">
<value>ID:</value>
</data>
<data name="Retention.HelpText" xml:space="preserve">
<value>Number of days of notifications to retain</value>
</data>
<data name="Retention.Text" xml:space="preserve">
<value>Retention (Days):</value>
</data>
</root> </root>

View File

@ -132,9 +132,6 @@
<data name="Theme.Select" xml:space="preserve"> <data name="Theme.Select" xml:space="preserve">
<value>Select Theme</value> <value>Select Theme</value>
</data> </data>
<data name="DefaultContainer.Admin" xml:space="preserve">
<value>Default Admin Container</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve"> <data name="Aliases.HelpText" xml:space="preserve">
<value>The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder).</value> <value>The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder).</value>
</data> </data>
@ -183,9 +180,6 @@
<data name="DefaultTheme.HelpText" xml:space="preserve"> <data name="DefaultTheme.HelpText" xml:space="preserve">
<value>Select the default theme for the site</value> <value>Select the default theme for the site</value>
</data> </data>
<data name="AdminContainer.HelpText" xml:space="preserve">
<value>Select the admin container for the site</value>
</data>
<data name="SiteTemplate.HelpText" xml:space="preserve"> <data name="SiteTemplate.HelpText" xml:space="preserve">
<value>Select the site template</value> <value>Select the site template</value>
</data> </data>
@ -207,9 +201,6 @@
<data name="Name.Text" xml:space="preserve"> <data name="Name.Text" xml:space="preserve">
<value>Site Name: </value> <value>Site Name: </value>
</data> </data>
<data name="AdminContainer.Text" xml:space="preserve">
<value>Admin Container: </value>
</data>
<data name="SiteTemplate.Text" xml:space="preserve"> <data name="SiteTemplate.Text" xml:space="preserve">
<value>Site Template: </value> <value>Site Template: </value>
</data> </data>

View File

@ -219,11 +219,11 @@
<data name="Success.Register" xml:space="preserve"> <data name="Success.Register" xml:space="preserve">
<value>You Have Been Successfully Registered For Updates</value> <value>You Have Been Successfully Registered For Updates</value>
</data> </data>
<data name="PackageService.HelpText" xml:space="preserve"> <data name="PackageManager.HelpText" xml:space="preserve">
<value>Specify If The Package Service Is Enabled For Installing Modules, Themes, And Translations</value> <value>Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation.</value>
</data> </data>
<data name="PackageService.Text" xml:space="preserve"> <data name="PackageManager.Text" xml:space="preserve">
<value>Package Service Enabled?</value> <value>Package Manager:</value>
</data> </data>
<data name="Swagger.HelpText" xml:space="preserve"> <data name="Swagger.HelpText" xml:space="preserve">
<value>Specify If Swagger Is Enabled For Your Server API</value> <value>Specify If Swagger Is Enabled For Your Server API</value>

View File

@ -124,7 +124,7 @@
<value>Theme: </value> <value>Theme: </value>
</data> </data>
<data name="Success.Theme.Download" xml:space="preserve"> <data name="Success.Theme.Download" xml:space="preserve">
<value>Theme Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value> <value>Theme Package Downloaded 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>
@ -135,4 +135,13 @@
<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>
</data> </data>
<data name="Download.Heading" xml:space="preserve">
<value>Marketplace</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
<data name="Product" xml:space="preserve">
<value>Product</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
@ -163,7 +163,7 @@
<value>The license of the theme</value> <value>The license of the theme</value>
</data> </data>
<data name="PackageName.HelpText" xml:space="preserve"> <data name="PackageName.HelpText" xml:space="preserve">
<value>The unique name of the package from which this module was installed</value> <value>The unique name of the package from which this theme was installed. This value must be specified within the themee's ITheme interface specification.</value>
</data> </data>
<data name="PackageName.Text" xml:space="preserve"> <data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value> <value>Package Name:</value>
@ -177,4 +177,19 @@
<data name="IsEnabled.Text" xml:space="preserve"> <data name="IsEnabled.Text" xml:space="preserve">
<value>Enabled?</value> <value>Enabled?</value>
</data> </data>
<data name="View License" xml:space="preserve">
<value>View License</value>
</data>
<data name="Error.Validate" xml:space="preserve">
<value>Error Validating Package</value>
</data>
<data name="Message.Download" xml:space="preserve">
<value>Package Version Has Been Verified. Please Select The Download Button To Obtain The Package.</value>
</data>
<data name="Message.Validate" xml:space="preserve">
<value>This Package Version Has Not Been Registered In The Oqtane Marketplace Or You Do Not Have The Right To Use It From This Installation</value>
</data>
<data name="Validate" xml:space="preserve">
<value>Validate</value>
</data>
</root> </root>

View File

@ -138,12 +138,21 @@
<data name="DeleteTheme.Header" xml:space="preserve"> <data name="DeleteTheme.Header" xml:space="preserve">
<value>Delete Theme</value> <value>Delete Theme</value>
</data> </data>
<data name="DeleteTheme.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="CreateTheme.Text" xml:space="preserve"> <data name="CreateTheme.Text" xml:space="preserve">
<value>Create Theme</value> <value>Create Theme</value>
</data> </data>
<data name="InstallTheme.Text" xml:space="preserve">
<value>Install Theme</value>
</data>
<data name="ViewTheme.Text" xml:space="preserve"> <data name="ViewTheme.Text" xml:space="preserve">
<value>View</value> <value>View</value>
</data> </data>
<data name="EditTheme.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Enabled" xml:space="preserve"> <data name="Enabled" xml:space="preserve">
<value>Enabled?</value> <value>Enabled?</value>
</data> </data>

View File

@ -141,4 +141,13 @@
<data name="Upload.Heading" xml:space="preserve"> <data name="Upload.Heading" xml:space="preserve">
<value>Upload</value> <value>Upload</value>
</data> </data>
<data name="Message.Text" xml:space="preserve">
<value>Framework Is Already Up To Date</value>
</data>
<data name="MessageUpgrade.Text" xml:space="preserve">
<value>Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade</value>
</data>
<data name="Localhost.Text" xml:space="preserve">
<value>You Cannot Perform A System Update In A Development Environment</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
@ -121,7 +121,7 @@
<value>Message: </value> <value>Message: </value>
</data> </data>
<data name="Message.User.Invalid" xml:space="preserve"> <data name="Message.User.Invalid" xml:space="preserve">
<value>User Does Not Exist. Please Verify That The Username Provided Is Correct.</value> <value>The User Specified Does Not Exist</value>
</data> </data>
<data name="Error.Notification.Add" xml:space="preserve"> <data name="Error.Notification.Add" xml:space="preserve">
<value>Error Adding Notification</value> <value>Error Adding Notification</value>
@ -133,7 +133,7 @@
<value>Enter the subject of the message</value> <value>Enter the subject of the message</value>
</data> </data>
<data name="Message.HelpText" xml:space="preserve"> <data name="Message.HelpText" xml:space="preserve">
<value>Enter the message</value> <value>Enter the message content</value>
</data> </data>
<data name="To.Text" xml:space="preserve"> <data name="To.Text" xml:space="preserve">
<value>To: </value> <value>To: </value>
@ -141,4 +141,13 @@
<data name="Subject.Text" xml:space="preserve"> <data name="Subject.Text" xml:space="preserve">
<value>Subject: </value> <value>Subject: </value>
</data> </data>
<data name="Send Notification" xml:space="preserve">
<value>Send Notification</value>
</data>
<data name="Message.Required" xml:space="preserve">
<value>You Must Enter All Required Information</value>
</data>
<data name="Username.Enter" xml:space="preserve">
<value>Enter Username</value>
</data>
</root> </root>

View File

@ -228,4 +228,16 @@
<data name="Profile.Heading" xml:space="preserve"> <data name="Profile.Heading" xml:space="preserve">
<value>Profile</value> <value>Profile</value>
</data> </data>
<data name="ViewNotification.Text" xml:space="preserve">
<value>View</value>
</data>
<data name="DeleteNotification.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="NoNotificationsReceived.Text" xml:space="preserve">
<value>No notifications have been received</value>
</data>
<data name="NoNotificationsSent.Text" xml:space="preserve">
<value>No notifications have been sent</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
@ -126,22 +126,43 @@
<data name="Error.Notification.Add" xml:space="preserve"> <data name="Error.Notification.Add" xml:space="preserve">
<value>Error Adding Notification</value> <value>Error Adding Notification</value>
</data> </data>
<data name="Title" xml:space="preserve"> <data name="View Notification" xml:space="preserve">
<value>Title:</value> <value>View Notification</value>
</data> </data>
<data name="Subject" xml:space="preserve"> <data name="Date.HelpText" xml:space="preserve">
<value>Subject:</value> <value>The date the message was sent</value>
</data> </data>
<data name="Date" xml:space="preserve"> <data name="Date.Text" xml:space="preserve">
<value>Date:</value> <value>Sent:</value>
</data> </data>
<data name="Message" xml:space="preserve"> <data name="From.HelpText" xml:space="preserve">
<value>The user who sent the message</value>
</data>
<data name="From.Text" xml:space="preserve">
<value>From:</value>
</data>
<data name="Message.HelpText" xml:space="preserve">
<value>The content of the message</value>
</data>
<data name="Message.Text" xml:space="preserve">
<value>Message:</value> <value>Message:</value>
</data> </data>
<data name="Reply" xml:space="preserve"> <data name="RE" xml:space="preserve">
<value>Reply</value> <value>RE:</value>
</data> </data>
<data name="OriginalMessage" xml:space="preserve"> <data name="Subject.HelpText" xml:space="preserve">
<value>Original Message</value> <value>The subject of the message</value>
</data>
<data name="Subject.Text" xml:space="preserve">
<value>Subject:</value>
</data>
<data name="System" xml:space="preserve">
<value>System</value>
</data>
<data name="To.HelpText" xml:space="preserve">
<value>The user who will be the recipient of the message</value>
</data>
<data name="To.Text" xml:space="preserve">
<value>To:</value>
</data> </data>
</root> </root>

View File

@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Error.User.AddCheckPass" xml:space="preserve"> <data name="Error.User.AddCheckPass" xml:space="preserve">
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.</value> <value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use.</value>
</data> </data>
<data name="Message.Password.NoMatch" xml:space="preserve"> <data name="Message.Password.NoMatch" xml:space="preserve">
<value>Passwords Entered Do Not Match</value> <value>Passwords Entered Do Not Match</value>
@ -171,4 +171,10 @@
<data name="Password.Placeholder" xml:space="preserve"> <data name="Password.Placeholder" xml:space="preserve">
<value>Password</value> <value>Password</value>
</data> </data>
<data name="Notify.HelpText" xml:space="preserve">
<value>Indicate if new users should receive an email notification</value>
</data>
<data name="Notify.Text" xml:space="preserve">
<value>Notify?</value>
</data>
</root> </root>

View File

@ -168,12 +168,6 @@
<data name="Password.Text" xml:space="preserve"> <data name="Password.Text" xml:space="preserve">
<value>Password:</value> <value>Password:</value>
</data> </data>
<data name="Photo.HelpText" xml:space="preserve">
<value>A photo of the user</value>
</data>
<data name="Photo.Text" xml:space="preserve">
<value>Photo:</value>
</data>
<data name="Username.HelpText" xml:space="preserve"> <data name="Username.HelpText" xml:space="preserve">
<value>The unique username for a user. Note that this field can not be modified.</value> <value>The unique username for a user. Note that this field can not be modified.</value>
</data> </data>

View File

@ -396,4 +396,61 @@
<data name="ProfileClaimTypes.Text" xml:space="preserve"> <data name="ProfileClaimTypes.Text" xml:space="preserve">
<value>User Profile Claims:</value> <value>User Profile Claims:</value>
</data> </data>
<data name="Username" xml:space="preserve">
<value>User Name</value>
</data>
<data name="Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Email" xml:space="preserve">
<value>Email</value>
</data>
<data name="ImportUsers.Text" xml:space="preserve">
<value>Import Users</value>
</data>
<data name="AuthFlow.Code" xml:space="preserve">
<value>code</value>
</data>
<data name="AuthFlow.CodeIdToken" xml:space="preserve">
<value>code id_token</value>
</data>
<data name="AuthFlow.CodeIdTokenToken" xml:space="preserve">
<value>code id_token token</value>
</data>
<data name="AuthFlow.CodeToken" xml:space="preserve">
<value>code token</value>
</data>
<data name="AuthFlow.IdToken" xml:space="preserve">
<value>id_token</value>
</data>
<data name="AuthFlow.IdTokenToken" xml:space="preserve">
<value>id_token token</value>
</data>
<data name="AuthFlow.None" xml:space="preserve">
<value>none</value>
</data>
<data name="AuthFlow.Token" xml:space="preserve">
<value>token</value>
</data>
<data name="AuthResponseType" xml:space="preserve">
<value>Authorization Response Type</value>
</data>
<data name="VerifyUsers.HelpText" xml:space="preserve">
<value>Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically.</value>
</data>
<data name="VerifyUsers.Text" xml:space="preserve">
<value>Verify Existing Users?</value>
</data>
<data name="AlwaysRemember.HelpText" xml:space="preserve">
<value>Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies.</value>
</data>
<data name="AlwaysRemember.Text" xml:space="preserve">
<value>Always Remember User?</value>
</data>
<data name="CookieExpiration.HelpText" xml:space="preserve">
<value>You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified.</value>
</data>
<data name="CookieExpiration.Text" xml:space="preserve">
<value>Cookie Expiration Timespan:</value>
</data>
</root> </root>

View File

@ -117,61 +117,34 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Template.Text" xml:space="preserve"> <data name="ImportFile.HelpText" xml:space="preserve">
<value>Template: </value> <value>Upload or select a tab delimited text file containing user information. The file must be in the Template format specified (Roles can be specified as a comma delimited list).</value>
</data> </data>
<data name="Template.Select" xml:space="preserve"> <data name="ImportFile.Text" xml:space="preserve">
<value>Select Template</value> <value>Import File:</value>
</data> </data>
<data name="Module.Create" xml:space="preserve"> <data name="Error.Import" xml:space="preserve">
<value>Create Module</value> <value>Error Importing Users</value>
</data> </data>
<data name="Module.Activate" xml:space="preserve"> <data name="Import" xml:space="preserve">
<value>Activate Module</value> <value>Import</value>
</data> </data>
<data name="Info.Module.Creator" xml:space="preserve"> <data name="Message.Import.Failure" xml:space="preserve">
<value>Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment</value> <value>User Import Failed. Please Review Your Event Log For More Detailed Information.</value>
</data> </data>
<data name="Info.Module.Activate" xml:space="preserve"> <data name="Message.Import.Success" xml:space="preserve">
<value>Once You Have Compiled The Module And Restarted The Application You Can Activate The Module Below</value> <value>User Import Successful. {0} Users Imported.</value>
</data> </data>
<data name="Success.Module.Create" xml:space="preserve"> <data name="Message.Import.Validation" xml:space="preserve">
<value>The Source Code For Your Module Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value> <value>You Must Specify A User File For Import</value>
</data> </data>
<data name="Message.Require.ValidName" xml:space="preserve"> <data name="Template" xml:space="preserve">
<value>You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value> <value>Template</value>
</data> </data>
<data name="OwnerName.HelpText" xml:space="preserve"> <data name="Notify.HelpText" xml:space="preserve">
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.</value> <value>Indicate if new users should receive an email notification</value>
</data> </data>
<data name="ModuleName.HelpText" xml:space="preserve"> <data name="Notify.Text" xml:space="preserve">
<value>Enter a name for this module. It should not contain spaces or punctuation.</value> <value>Notify?</value>
</data>
<data name="Description.HelpText" xml:space="preserve">
<value>Enter a short description for the module</value>
</data>
<data name="Template.HelpText" xml:space="preserve">
<value>Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server.</value>
</data>
<data name="FrameworkReference.HelpText" xml:space="preserve">
<value>Select a framework reference version</value>
</data>
<data name="Location.HelpText" xml:space="preserve">
<value>Location where the module will be created</value>
</data>
<data name="OwnerName.Text" xml:space="preserve">
<value>Owner Name: </value>
</data>
<data name="ModuleName.Text" xml:space="preserve">
<value>Module Name: </value>
</data>
<data name="Description.Text" xml:space="preserve">
<value>Description: </value>
</data>
<data name="FrameworkReference.Text" xml:space="preserve">
<value>Framework Reference: </value>
</data>
<data name="Location.Text" xml:space="preserve">
<value>Location: </value>
</data> </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
@ -120,4 +120,7 @@
<data name="PageOfPages" xml:space="preserve"> <data name="PageOfPages" xml:space="preserve">
<value>Page {0} of {1}</value> <value>Page {0} of {1}</value>
</data> </data>
<data name="SearchPlaceholder" xml:space="preserve">
<value>Search: {0}</value>
</data>
</root> </root>

View File

@ -129,4 +129,25 @@
<data name="Message.Username.DontExist" xml:space="preserve"> <data name="Message.Username.DontExist" xml:space="preserve">
<value>User Does Not Exist With Name Specified</value> <value>User Does Not Exist With Name Specified</value>
</data> </data>
<data name="ModuleDefinition" xml:space="preserve">
<value>Module</value>
</data>
<data name="Page" xml:space="preserve">
<value>Page</value>
</data>
<data name="Folder" xml:space="preserve">
<value>Folder</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Edit" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Utilize" xml:space="preserve">
<value>Utilize</value>
</data>
<data name="Browse" xml:space="preserve">
<value>Browse</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
@ -156,6 +156,9 @@
<data name="Message.Content.Restored" xml:space="preserve"> <data name="Message.Content.Restored" xml:space="preserve">
<value>Version Restored</value> <value>Version Restored</value>
</data> </data>
<data name="Edit Html/Text" xml:space="preserve">
<value>Edit Html/Text</value>
</data>
<data name="Restore.Header" xml:space="preserve"> <data name="Restore.Header" xml:space="preserve">
<value>Restore Version</value> <value>Restore Version</value>
</data> </data>
@ -165,4 +168,16 @@
<data name="View.Text" xml:space="preserve"> <data name="View.Text" xml:space="preserve">
<value>View</value> <value>View</value>
</data> </data>
<data name="Edit.Heading" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Versions.Heading" xml:space="preserve">
<value>Versions</value>
</data>
<data name="HtmlEditor.Heading" xml:space="preserve">
<value>Raw HTML Editor</value>
</data>
<data name="RichTextEditor.Heading" xml:space="preserve">
<value>Rich Text Editor</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
@ -120,6 +120,9 @@
<data name="Edit.Action" xml:space="preserve"> <data name="Edit.Action" xml:space="preserve">
<value>Edit</value> <value>Edit</value>
</data> </data>
<data name="Edit.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Error.Content.Load" xml:space="preserve"> <data name="Error.Content.Load" xml:space="preserve">
<value>An Error Occurred Loading Content</value> <value>An Error Occurred Loading Content</value>
</data> </data>

View File

@ -223,13 +223,13 @@
<value>by</value> <value>by</value>
</data> </data>
<data name="Search.Downloads" xml:space="preserve"> <data name="Search.Downloads" xml:space="preserve">
<value>downloads</value> <value>Downloads</value>
</data> </data>
<data name="Search.Released" xml:space="preserve"> <data name="Search.Released" xml:space="preserve">
<value>released</value> <value>Released</value>
</data> </data>
<data name="Search.Version" xml:space="preserve"> <data name="Search.Version" xml:space="preserve">
<value>version</value> <value>Version</value>
</data> </data>
<data name="Edit" xml:space="preserve"> <data name="Edit" xml:space="preserve">
<value>Edit</value> <value>Edit</value>
@ -277,19 +277,19 @@
<value>Installed Version</value> <value>Installed Version</value>
</data> </data>
<data name="Search.Source" xml:space="preserve"> <data name="Search.Source" xml:space="preserve">
<value>source</value> <value>Source</value>
</data> </data>
<data name="Message.InfoRequired" xml:space="preserve"> <data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value> <value>Please Provide All Required Information</value>
</data> </data>
<data name="Free" xml:space="preserve"> <data name="Free" xml:space="preserve">
<value>Free</value> <value>Open Source</value>
</data> </data>
<data name="Paid" xml:space="preserve"> <data name="Paid" xml:space="preserve">
<value>Paid</value> <value>Commercial</value>
</data> </data>
<data name="Search.Price" xml:space="preserve"> <data name="Search.Price" xml:space="preserve">
<value>price</value> <value>Price</value>
</data> </data>
<data name="Accept" xml:space="preserve"> <data name="Accept" xml:space="preserve">
<value>Accept</value> <value>Accept</value>
@ -390,4 +390,46 @@
<data name="Support" xml:space="preserve"> <data name="Support" xml:space="preserve">
<value>Support</value> <value>Support</value>
</data> </data>
<data name="Search.Alphabetical" xml:space="preserve">
<value>Alphabetical</value>
</data>
<data name="Buy" xml:space="preserve">
<value>Buy Now</value>
</data>
<data name="Search.Popularity" xml:space="preserve">
<value>Popularity</value>
</data>
<data name="Search.Results" xml:space="preserve">
<value>Results</value>
</data>
<data name="Search.RecentlyReleased" xml:space="preserve">
<value>Recently Released</value>
</data>
<data name="From" xml:space="preserve">
<value>From</value>
</data>
<data name="To" xml:space="preserve">
<value>To</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>
<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="ProfileInvalid" xml:space="preserve">
<value>{0} Is Not Valid</value>
</data>
<data name="ProfileRequired" xml:space="preserve">
<value>{0} Is Required</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
@ -186,4 +186,16 @@
<data name="VisibilityView" xml:space="preserve"> <data name="VisibilityView" xml:space="preserve">
<value>Same As Page</value> <value>Same As Page</value>
</data> </data>
<data name="Confirm.Page.Delete" xml:space="preserve">
<value>Are You Sure You Want To Delete This Page?</value>
</data>
<data name="Location" xml:space="preserve">
<value>Location:</value>
</data>
<data name="LocationBottom" xml:space="preserve">
<value>Bottom</value>
</data>
<data name="LocationTop" xml:space="preserve">
<value>Top</value>
</data>
</root> </root>

View File

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

View File

@ -29,7 +29,7 @@ namespace Oqtane.Services
public async Task<Folder> GetFolderAsync(int siteId, [NotNull] string folderPath) public async Task<Folder> GetFolderAsync(int siteId, [NotNull] string folderPath)
{ {
var path = WebUtility.UrlEncode(folderPath); var path = WebUtility.UrlEncode(folderPath);
return await GetJsonAsync<Folder>($"{ApiUrl}/{siteId}/{path}"); return await GetJsonAsync<Folder>($"{ApiUrl}/path/{siteId}/?path={path}");
} }
public async Task<Folder> AddFolderAsync(Folder folder) public async Task<Folder> AddFolderAsync(Folder folder)

View File

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

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