Compare commits

...

1250 Commits

Author SHA1 Message Date
3967017ebb Merge pull request #3687 from oqtane/master
5.0.2 release
2024-01-25 14:56:55 -05:00
d93862043a Merge pull request #3686 from oqtane/dev
5.0.2 release
2024-01-25 14:56:33 -05:00
719276b32a Merge pull request #3685 from sbwalker/dev
fix incorrect migration id
2024-01-25 14:40:48 -05:00
bf13f06e68 fix incorrect migration id 2024-01-25 14:40:32 -05:00
9f770d08f5 Merge pull request #3684 from sbwalker/dev
update default Module and Theme template to .NET 8.0.1 to match framework
2024-01-25 13:50:32 -05:00
e62c529c1f update default Module and Theme template to .NET 8.0.1 to match framework 2024-01-25 13:50:13 -05:00
4e1fc9b031 Merge pull request #3681 from zyhfish/task/fix-bug-3648
fix #3648: update the template to correct the entity table name.
2024-01-25 13:41:13 -05:00
7d175ad38d Merge pull request #3683 from sbwalker/dev
resolve regression installation issue with PostgreSQL
2024-01-25 13:39:35 -05:00
de2c56560e resolve regression installation issue with PostgreSQL 2024-01-25 13:39:23 -05:00
0db413cb18 Merge pull request #3682 from sbwalker/dev
fix #3653 - mySQL installation failing on Rows reserved word
2024-01-25 10:59:32 -05:00
7c6424e05c fix #3653 - mySQL installation failing on Rows reserved word 2024-01-25 10:59:18 -05:00
Ben
3c328a00b5 fix #3648: update the template to correct the entity table name. 2024-01-25 23:00:45 +08:00
e36f13c595 Merge pull request #3679 from sbwalker/dev
upgrade to MySQL dependencies
2024-01-24 16:49:23 -05:00
38a5cc33e8 upgrade to MySQL dependencies 2024-01-24 16:49:02 -05:00
245a3a73d8 Merge pull request #3677 from sbwalker/dev
improvements to .NET MAUI file upload based on #3674 (credit @thabaum)
2024-01-24 09:37:45 -05:00
d57d7ec4d8 improvements to .NET MAUI file upload based on #3674 (credit @thabaum) 2024-01-24 09:37:27 -05:00
09a4e4a6a5 Merge pull request #3673 from sbwalker/dev
remove unnecessary service and usings
2024-01-23 13:14:40 -05:00
d9beb4b660 remove unnecessary service and usings 2024-01-23 13:14:28 -05:00
63507a5567 Update README.md 2024-01-23 11:07:37 -05:00
e42dc259d8 Merge pull request #3672 from sbwalker/dev
prepare for 5.0.2 release
2024-01-23 11:06:30 -05:00
f59f8d1937 prepare for 5.0.2 release 2024-01-23 11:06:15 -05:00
0387bde81b Merge pull request #3671 from sbwalker/dev
fix #3669 add CORS policy and use Jwt with XHR to allow file uploads to work in .NET MAUI
2024-01-23 10:34:39 -05:00
b8fe95b945 fix #3669 add CORS policy and use Jwt with XHR to allow file uploads to work in .NET MAUI 2024-01-23 10:34:18 -05:00
792a1652e3 Merge pull request #3668 from sbwalker/dev
fix typo
2024-01-22 08:38:46 -05:00
4816bfa26d fix typo 2024-01-22 08:38:31 -05:00
95d1680ac3 Merge pull request #3667 from sbwalker/dev
fix #3656 - UploadableFiles and ImageFiles settings not loaded properly to handle empty string value resulting in upload issues
2024-01-22 08:24:20 -05:00
5e1e6aea16 fix #3656 - UploadableFiles and ImageFiles settings not loaded properly to handle empty string value resulting in upload issues 2024-01-22 08:24:01 -05:00
00696943d1 Merge pull request #3660 from sbwalker/dev
update license
2024-01-19 16:44:17 -05:00
c1d293f9f9 update license 2024-01-19 16:44:06 -05:00
d4d61d10bc Merge pull request #3659 from sbwalker/dev
improve help text for autocomplete
2024-01-19 15:36:01 -05:00
c09e5e6552 improve help text for autocomplete 2024-01-19 15:35:48 -05:00
ab08732ba8 Merge pull request #3652 from thabaum/ENH-Adds-Profile-Autocomplete-Setting
Enh: Autocomplete Attribute Enhancement
2024-01-19 15:25:13 -05:00
584a7eb9e2 Merge pull request #3598 from thabaum/module-creator-notification-tool-tips
Module Creator Owner/Name + Notification Help Text Includes Warning Not To Use The Word "Oqtane".  Fixes #3597
2024-01-19 15:18:42 -05:00
d44b04f425 Merge pull request #3658 from sbwalker/dev
update copyright year
2024-01-19 14:54:05 -05:00
eeec04b21a update copyright year 2024-01-19 14:53:54 -05:00
0642bc28bc Merge pull request #3657 from sbwalker/dev
do not include Licensing assembly with framework but prevent uninstall
2024-01-19 13:22:52 -05:00
2b12b67582 do not include Licensing assembly with framework but prevent uninstall 2024-01-19 13:22:38 -05:00
d9b575b051 undo changes to autocomplete. 2024-01-19 08:15:48 -08:00
c6dbb41724 removes recently added autocomplete attributes 2024-01-18 14:41:02 -08:00
ea4c1bf5e5 removes hardcoded autocomplete attributes last added 2024-01-18 14:38:31 -08:00
67afc050b8 Adds to form autocomplete="on" attribute and setting. 2024-01-18 14:23:10 -08:00
6e90da90f1 adds autocomplete to email and username + fixes a typo 2024-01-18 14:08:05 -08:00
26872c829b adds autocomplete attribute to username, email and display name input elements. 2024-01-18 14:06:10 -08:00
34f32c8ba5 Merge pull request #3649 from thabaum/clean-protocal-save-alias
Cleans protocol to check for duplicate alias prior to saving.  Fixes #3647
2024-01-18 07:57:53 -05:00
b0d847dc18 Merge pull request #3654 from thabaum/XMLHttpRequest-statusText
Updates request.status to request.statusText in Interop.js
2024-01-18 07:57:31 -05:00
adabef9706 Merge pull request #3655 from sbwalker/dev
fix issue when inserting images into RichTextEditor
2024-01-17 13:36:05 -05:00
474adf2d53 fix issue when inserting images into RichTextEditor 2024-01-17 13:35:52 -05:00
4f23ad37f5 Sets Profile Autocomplete Property 50 Character Limit 2024-01-17 09:07:03 -08:00
eb6b160377 updates autocomplete default tooltip and limits characters to 30. 2024-01-16 17:56:32 -08:00
0770e00d8d Updates Autocomplete.HelpText 2024-01-16 17:46:30 -08:00
ab0e95177e Updates request.status to request.statusText 2024-01-16 17:30:41 -08:00
cfd9e4b256 Updates request.status to request.statusText 2024-01-16 17:29:54 -08:00
bfed0ed791 Refactor SaveAlias method and add URL protocol check
Restructured the SaveAlias method for improved readability and added a check to handle cases where the _aliasname contains a URL protocol (e.g., "://"). This ensures proper handling of different URL formats.
2024-01-16 10:43:24 -08:00
3cec9f7ee0 fix formatting 2024-01-15 09:59:22 -08:00
3a5dc62908 Sanitize _aliasname by removing protocols 2024-01-15 09:56:11 -08:00
e0cdfcf403 adds Autocomplete property 2024-01-15 09:30:37 -08:00
439866216c adds autocomplete attribute to select element 2024-01-15 09:10:12 -08:00
a1b0731665 profile autocomplete migration 2024-01-15 08:24:06 -08:00
bcc216fd2e Merge pull request #3645 from thabaum/file-controller-upload-null
Handle null or empty formfile in UploadFile Method - Fixes #3646
2024-01-15 08:55:04 -05:00
574d848828 Merge pull request #3644 from thabaum/interop-xrs-status-update
Interop.js xrs status update Fixes #3643
2024-01-15 08:54:24 -05:00
22acc2307c remove extra line 2024-01-13 21:09:41 -08:00
d01a3c327e remove space 2024-01-13 21:09:00 -08:00
7c52b6ab23 remove name from password input 2024-01-13 21:07:08 -08:00
23d27aee6b ENH: Adds Profile Properties Autocomplete 2024-01-13 21:04:15 -08:00
9315358fa6 adds autocomplete 2024-01-13 20:59:42 -08:00
83aaa94cfe ENH: Profile Properties Autocomplete Setting 2024-01-13 20:54:31 -08:00
7744099ee5 cleans protocol to check for duplicate alias prior to saving 2024-01-13 13:09:17 -08:00
35bb6b62d8 Handle null or empty formfile in UploadFile Method 2024-01-12 14:08:15 -08:00
99844931f4 XRS status error update Fixes #3643 2024-01-12 13:43:02 -08:00
d8c34a7cdf XRS status error update Fixes #3643
Resolved an issue with XRS status not updating correctly. This commit addresses the problem and ensures the status is now accurately reflected.

Fixes #3643
2024-01-12 13:38:15 -08:00
c284edcd81 Merge pull request #3630 from thabaum/upgrade-nuget-packages
Upgrade NuGet Packages for Oqtane Framework
2024-01-12 16:37:42 -05:00
f5d4b352b3 Merge pull request #3632 from leigh-pointer/SchedStartEndDateCheck
Start end End date Range check on Job Items
2024-01-12 16:28:22 -05:00
e44b31d755 Merge pull request #3641 from sbwalker/dev
fix issue where rich text was not being refreshed in the editor when content changed, and original rich text was not always preserved
2024-01-12 15:49:53 -05:00
3a28068b48 fix issue where rich text was not being refreshed in the editor when content changed, and original rich text was not always preserved 2024-01-12 15:49:34 -05:00
03433b00d2 Merge pull request #3639 from sbwalker/dev
fix #3637 - ensure ServerState Scripts only contains site level script resources
2024-01-12 08:20:17 -05:00
6f39ebf48f fix #3637 - ensure ServerState Scripts only contains site level script resources 2024-01-12 08:19:58 -05:00
949ca00dbb Merge pull request #3638 from sbwalker/dev
add defensive null check
2024-01-12 08:15:44 -05:00
bfdf1ee8f5 add defensive null check 2024-01-12 08:15:30 -05:00
e047d4e8a6 Start end End date Range check
Checks that the Start date starts before the End date.
Message added to Resx
2024-01-10 14:49:47 +01:00
15cf2069b6 Update NuGet Packages to New Versions
This commit updates the versions of several NuGet packages used in the project. The following packages have been upgraded:

- Microsoft.EntityFrameworkCore from 8.0.0 to 8.0.1
- Microsoft.EntityFrameworkCore.Relational from 8.0.0 to 8.0.1
- System.Text.Json from 8.0.0 to 8.0.1

The versions of Microsoft.Extensions.DependencyInjection.Abstractions and System.ComponentModel.Annotations remain unchanged.
2024-01-09 14:09:56 -08:00
564b0ee0c4 Upgrade NuGet Packages to Latest Versions
This commit updates the versions of several NuGet packages used in the project. The following packages have been upgraded:

- Microsoft.AspNetCore.Components.WebAssembly.Server from 8.0.0 to 8.0.1
- Microsoft.AspNetCore.Identity.EntityFrameworkCore from 8.0.0 to 8.0.1
- Microsoft.Data.SqlClient from 5.2.0-preview3.23201.1 to 5.2.0-preview4.23342.2
- Microsoft.EntityFrameworkCore from 8.0.0 to 8.0.1
- Microsoft.EntityFrameworkCore.Design from 8.0.0 to 8.0.1
- Microsoft.Extensions.Localization from 8.0.0 to 8.0.1
- Microsoft.AspNetCore.Authentication.OpenIdConnect from 8.0.0 to 8.0.1
- Microsoft.Data.Sqlite.Core from 8.0.0 to 8.0.1

The versions of SixLabors.ImageSharp, Swashbuckle.AspNetCore, SQLitePCLRaw.bundle_e_sqlite3, and Oqtane.Licensing remain unchanged.
2024-01-09 14:08:09 -08:00
b454932ff5 Upgrade multiple NuGet packages
Upgraded the following NuGet packages:
- Microsoft.EntityFrameworkCore.Sqlite from v8.0.0 to v8.0.1
2024-01-09 14:03:24 -08:00
191c07b6a1 Upgrade multiple NuGet packages
Upgraded the following NuGet packages:
- Microsoft.EntityFrameworkCore.SqlServer from v8.0.0 to v8.0.1
2024-01-09 13:58:28 -08:00
5b0deb9fc2 Upgrade multiple NuGet packages
Upgraded the following NuGet packages:
- EFCore.NamingConventions from v8.0.0-rc.2 to v8.0.2
- Microsoft.EntityFrameworkCore.Relational from v8.0.0 to v8.0.1
2024-01-09 13:55:56 -08:00
297ec64bde Upgrade multiple NuGet packages 2024-01-09 13:53:39 -08:00
a9b85caeb7 Upgrade multiple NuGet packages
Upgraded the following NuGet packages:
- Microsoft.AspNetCore.Components.Authorization from v8.0.0 to v8.0.1
- Microsoft.AspNetCore.Components.WebAssembly from v8.0.0 to v8.0.1
- Microsoft.Extensions.Localization from v8.0.0 to v8.0.1
2024-01-09 13:44:47 -08:00
e30b883148 Merge pull request #3628 from thabaum/update-getting-started-readme
Clarify Getting Started Instructions. Fixes #3627
2024-01-09 14:53:53 -05:00
cd6be0d755 Merge pull request #3624 from thabaum/documentation-spelling-correction
Documentation spelling correction.  Fixes #3623
2024-01-09 14:52:55 -05:00
95b74c5f2f Fix capitalization in list items. 2024-01-08 14:46:37 -08:00
45f26bb22e Clarify Getting Started Instructions. 2024-01-08 14:10:03 -08:00
09b0e09932 Documentation Spelling Correction 2024-01-07 11:10:53 -08:00
ea9d88009f Documentation Spelling Correction 2024-01-07 11:09:06 -08:00
e5013c918e Documentation Spelling Correction 2024-01-07 11:08:08 -08:00
4b45103a4e Documentation Spelling Correction 2024-01-07 11:07:35 -08:00
1ea28411da Documentation Spelling Correction 2024-01-07 11:07:08 -08:00
e27fce1227 Documentation Spelling Correction 2024-01-07 11:06:32 -08:00
cf3b8378bd Documentation Spelling Correction 2024-01-07 11:05:46 -08:00
9f07532140 documentation spelling correction 2024-01-07 10:59:11 -08:00
f1aedc619e Merge pull request #3622 from sbwalker/dev
fix #3584 - browse/edit using relative path for current site
2024-01-06 15:29:45 -05:00
5f778e706f fix #3584 - browse/edit using relative path for current site 2024-01-06 15:29:28 -05:00
ee15663679 Merge pull request #3620 from sbwalker/dev
improve ShouldRender logic
2024-01-06 08:52:26 -05:00
c271d4bfe4 improve ShouldRender logic 2024-01-06 08:52:05 -05:00
99188f7427 Merge pull request #3619 from sbwalker/dev
more rendering optimizations
2024-01-06 08:42:28 -05:00
88a07ecf63 more rendering optimizations 2024-01-06 08:42:07 -05:00
f1af2e35cd Merge pull request #3616 from sbwalker/dev
remove commented code
2024-01-05 12:14:57 -05:00
352e20f01b remove commented code 2024-01-05 12:14:43 -05:00
f7d10a6cf1 Merge pull request #3602 from leigh-pointer/ENH#3538-PgModDateActive
Start Date and Expiry Date for Module instances and Pages #3538
2024-01-05 12:09:19 -05:00
6a5808077c Merge pull request #3608 from thabaum/readme-add-marketplace-button
README - adds marketplace information for issue #3607
2024-01-05 12:08:20 -05:00
09be11b5a8 Merge pull request #3615 from sbwalker/dev
changed max profiel field rows to 10
2024-01-05 12:06:52 -05:00
5117c1a528 changed max profiel field rows to 10 2024-01-05 12:06:36 -05:00
ead9857203 Merge pull request #3610 from W6HBR/dev
Update Oqtane.Client\Modules\Admin\Profiles\Edit.razor to fix breaking changes
2024-01-05 12:04:30 -05:00
cd3493e040 Merge pull request #3614 from sbwalker/dev
improved rendering optimization
2024-01-05 11:53:57 -05:00
02c22c1894 improved rendering optimization 2024-01-05 11:49:57 -05:00
8b41a03080 Modified Logic from || to &&
UserSecurity.IsAuthorized && Utilities.IsPageModuleVisible fixed
2024-01-05 10:15:51 +01:00
306f4f119b Merge pull request #3613 from sbwalker/dev
Fix #3604 - display message if user is attempting to run the application prior to performing a full compilation
2024-01-04 16:40:36 -05:00
3dc28c7291 Fix #3604 - display message if user is attempting to run the application prior to performing a full compilation 2024-01-04 16:40:15 -05:00
f2f2215059 Merge pull request #3611 from sbwalker/dev
component rendering optimizations
2024-01-04 13:48:06 -05:00
f75179b2f6 component rendering optimizations 2024-01-04 13:47:51 -05:00
3ead35c984 Update (Profiles) Edit.razor to fix breaking changes
Prior functionality allowed 4 characters for "order" which now causes validation errors for any order value > 99.  This change allows a value of up to 9999 which is consistent with the prior 4 character length value.

Prior functionality allowed 2 characters for "rows" which now causes validattion error for any row count > 10.  This change allows a value up to 99 which is consistent with the prior 2 character length value.
2024-01-04 10:12:17 -08:00
efe20a1fe8 Adds Marketplace Section 2024-01-03 10:59:14 -08:00
ab1e56a14e Updates Marketplace Message 2024-01-03 10:49:14 -08:00
055d6bf601 Adjust Marketplace Placement 2024-01-03 10:48:24 -08:00
683c3e96d6 Update Marketplace Message 2024-01-03 10:47:13 -08:00
d8e414a4a2 Icon and Link for Oqtane Marketplace 2024-01-03 10:45:43 -08:00
9237d68b79 Update Marketplace Icon 2024-01-03 10:35:24 -08:00
12a14fbe29 Adds Marketplace Button 2024-01-03 10:25:13 -08:00
22e4e4efc1 Updates
Loops tighter
Updated Logout base
SiteController corrected.
2024-01-03 12:12:27 +01:00
588327fd68 Merge pull request #3605 from sbwalker/dev
synchronize static assets with .NET MAUI project
2024-01-02 15:25:21 -05:00
d35ef2d360 synchronize static assets with .NET MAUI project 2024-01-02 15:25:07 -05:00
5ce5193430 Modifications 2024-01-02 16:09:54 +01:00
6e74215b2e Merge pull request #3600 from thabaum/clear-module-message
Clear Module Message Prior To Adding A Message To UI. Fixes #3599
2024-01-02 08:21:06 -05:00
884a9835c4 Merge pull request #3586 from thabaum/patch-6
Refreshes Admin Settings Page When User Updates Logo.  Fixes #3582
2024-01-02 08:19:37 -05:00
233f40f3e9 Start Date and Expiry Date for Module instances and Pages #3538
This is complete excluding Reporting and Notifications which can be added at a later date.  I just really wanted to get the schema and the functionality into place.
2023-12-31 12:21:38 +01:00
9b24e61033 Clear Module Message Proir To Adding A Message To UI 2023-12-29 12:49:49 -08:00
47cee5f6a1 Updates Module Owner/Name HelpText 2023-12-29 11:39:35 -08:00
6820b748f7 Updates Module Name/Owner and Notification Text 2023-12-29 11:37:31 -08:00
42422845b0 Updates Owner/Module Name Tooltip/Notification 2023-12-29 11:00:29 -08:00
8ed11cdc07 Adds the comment for client logo refresh boolean 2023-12-23 12:02:58 -08:00
006cd1ee89 Refresh Page On Client After Saving Changed Logo 2023-12-23 11:26:57 -08:00
49f7ee6042 change to reload from refresh to update site logo 2023-12-23 09:25:02 -08:00
9612e4d4e9 Removes unnecessary logo refresh logic. 2023-12-22 10:30:28 -08:00
4b938db0c1 refresh = true when logo is updated 2023-12-22 10:28:14 -08:00
b0a079dce9 Merge pull request #3592 from sbwalker/dev
refactor logic related to domain filtering for emails during external login
2023-12-21 15:55:07 -05:00
52069f35c5 refactor logic related to domain filtering for emails during external login 2023-12-21 15:54:46 -05:00
140535d875 Update README.md 2023-12-21 12:50:13 -05:00
611084d00c Merge pull request #3590 from oqtane/master
5.0.1 Release
2023-12-21 12:38:37 -05:00
1b7c810eeb Merge pull request #3589 from oqtane/dev
5.0.1 Release
2023-12-21 12:38:08 -05:00
dd2a698147 Refreshes settings page if logo changes. 2023-12-20 14:17:23 -08:00
e99df58bce Merge pull request #3585 from sbwalker/dev
add an AllowRichText parameter to the RichTextEditor component and add logic to handle JS Interop failures
2023-12-20 16:59:06 -05:00
70139221ab add an AllowRichText parameter to the RichTextEditor component and add logic to handle JS Interop failures 2023-12-20 16:58:46 -05:00
5d8d2b4a34 Merge pull request #3579 from sbwalker/dev
ensure database provider assemblies are included in Publish folder
2023-12-19 16:54:25 -05:00
f59f322226 ensure database provider assemblies are included in Publish folder 2023-12-19 16:54:11 -05:00
acca3f37b5 Merge pull request #3577 from sbwalker/dev
remove spaces from file extensions
2023-12-19 14:30:30 -05:00
0de62f620d remove spaces from file extensions 2023-12-19 14:30:18 -05:00
351c4a00e9 Merge pull request #3576 from sbwalker/dev
prepare for 5.0.1 release
2023-12-19 12:51:58 -05:00
b048d81ac0 prepare for 5.0.1 release 2023-12-19 12:51:44 -05:00
b9c9a2b75f Merge pull request #3575 from sbwalker/dev
add defensive logic to Alias.Path, improve new GetAlias method
2023-12-19 08:41:49 -05:00
4cea22d813 add defensive logic to Alias.Path, improve new GetAlias method 2023-12-19 08:41:36 -05:00
edad4e7d35 Merge pull request #3572 from thabaum/patch-5
Sets Logo max height to 90px plus padding 5px right and left.
2023-12-19 08:02:09 -05:00
e28acd8d2d Merge pull request #3574 from sbwalker/dev
handle loading/saving of File Extensions
2023-12-19 07:40:01 -05:00
86feaff82f handle loading/saving of File Extensions 2023-12-19 07:39:48 -05:00
5f747db224 Logo max-height and padding 2023-12-18 14:12:26 -08:00
ccda41e18e Logo max-height 90px, Padding 5px L/R 2023-12-18 14:11:29 -08:00
2bb83c6532 Revert app logo css changes 2023-12-18 14:10:23 -08:00
ec59faba8c Merge pull request #3573 from sbwalker/dev
add database provider projects to Oqtane.sln
2023-12-18 16:31:06 -05:00
c5d8c44b81 add database provider projects to Oqtane.sln 2023-12-18 16:30:49 -05:00
7a4e4629e5 Logo max height 90px 2023-12-18 13:03:19 -08:00
4d3dd95a60 Merge pull request #3568 from sbwalker/dev
add additional SetAlias overload
2023-12-18 15:07:20 -05:00
713021359e add additional SetAlias overload 2023-12-18 15:07:07 -05:00
927d2c36e8 Merge pull request #3566 from sbwalker/dev
abstract padding logic - don't repeat
2023-12-18 09:58:44 -05:00
afc6368915 abstract padding logic - don't repeat 2023-12-18 09:58:30 -05:00
3f604ad7b5 Merge pull request #3565 from sbwalker/dev
fix #3556 - pad token secret to 32 characters to resolve IDX1-720, change id of "secret" input to reduce chance of form autocomplete causing issues
2023-12-18 09:51:43 -05:00
c5d4e237ad fix #3556 - pad token secret to 32 characters to resolve IDX1-720, change id of "secret" input to reduce chance of form autocomplete causing issues 2023-12-18 09:51:18 -05:00
0b50b21779 Merge pull request #3559 from thabaum/patch-3
Correct formatting of _pagesWithModules declaration
2023-12-18 08:01:44 -05:00
cafc70f1c9 Correct formatting of _pagesWithModules declaration 2023-12-15 10:47:41 -08:00
b4377c346f Merge pull request #3558 from sbwalker/dev
modify module and theme template release.cmd to remove old nupkg files before packaging
2023-12-15 11:08:24 -05:00
7c206af757 modify module and theme template release.cmd to remove old nupkg files before packaging 2023-12-15 11:08:05 -05:00
6212d0a052 Merge pull request #3557 from sbwalker/dev
a simple dependency manager for assemblies
2023-12-15 11:06:01 -05:00
8909822aea a simple dependency manager for assemblies 2023-12-15 11:05:48 -05:00
5fc0964f73 Merge pull request #3555 from sbwalker/dev
suppress compiler warnings for internal Entity Framework Core API usage and Analyzer 'Microsoft.AspNetCore.Analyzers.WebApplicationBuilder.WebApplicationBuilderAnalyzer' (.NET8)
2023-12-14 15:23:04 -05:00
7725559a44 suppress compiler warnings for internal Entity Framework Core API usage and Analyzer 'Microsoft.AspNetCore.Analyzers.WebApplicationBuilder.WebApplicationBuilderAnalyzer' (.NET8) 2023-12-14 15:22:43 -05:00
03414a58e9 Merge pull request #3553 from sbwalker/dev
eliminate database provider nuget packages
2023-12-14 14:32:32 -05:00
ade0419bf6 eliminate database provider nuget packages 2023-12-14 14:32:19 -05:00
9d646b2eed Merge pull request #3552 from sbwalker/dev
ignore Blazor framework requests
2023-12-13 18:25:34 -05:00
0d718a5ca2 ignore Blazor framework requests 2023-12-13 18:25:21 -05:00
78c8b36dfa Merge pull request #3551 from sbwalker/dev
remove unecessary resource key
2023-12-13 13:34:24 -05:00
b5ca0874fa remove unecessary resource key 2023-12-13 13:34:08 -05:00
e209becff9 Merge pull request #3548 from leigh-pointer/ModuleMangPages
Module Management Ehancement - show where modules are used.
2023-12-13 11:22:22 -05:00
652d42aa6b Rework
Not to display the number of instances of the module on those pages.
Name the tab "Pages".
2023-12-13 17:19:19 +01:00
058a11597f Merge pull request #3550 from sbwalker/dev
fix regression issue
2023-12-13 11:18:44 -05:00
dd73d6e19a fix regression issue 2023-12-13 11:18:32 -05:00
3793f618a8 Merge pull request #3549 from sbwalker/dev
HTML encode notifications sent by non-admins to prevent HTML injection
2023-12-13 10:07:43 -05:00
6621983a9c HTML encode notifications sent by non-admins to prevent HTML injection 2023-12-13 10:07:21 -05:00
e0b0302f5a Check for Deleted Pageds 2023-12-12 22:56:02 +01:00
b02584bec6 Enhancment to the Module Managment to show where module is used.
Added a new tab that lists all the pages where there is a module instance with the count of the number of instances.
This helps when making a decision to delete the module from the framework.
2023-12-12 22:28:18 +01:00
c832d61409 Merge pull request #3547 from sbwalker/dev
set authentication cookie to HttpOnly
2023-12-12 15:56:30 -05:00
ac701f28b5 set authentication cookie to HttpOnly 2023-12-12 15:56:16 -05:00
53032140e7 Merge pull request #3546 from sbwalker/dev
security improvement - ensure returnurl is a relativre path
2023-12-12 15:55:03 -05:00
3c7633564f security improvement - ensure returnurl is a relativre path 2023-12-12 15:54:52 -05:00
43eae40404 Merge pull request #3545 from sbwalker/dev
add support for root sitemap.xml and robots.txt
2023-12-12 14:50:21 -05:00
e34b1b54d5 add support for root sitemap.xml and robots.txt 2023-12-12 14:50:08 -05:00
0658a7de0d Merge pull request #3544 from sbwalker/dev
comsider name and email claim values as optional
2023-12-12 14:04:08 -05:00
9e0a4dfac8 comsider name and email claim values as optional 2023-12-12 14:03:47 -05:00
82034ade90 Merge pull request #3543 from sbwalker/dev
modify #3528 so that it only saves the ImagesFiles and UploadableFiles if they are different than the default - otherwise it sets them to blank. This provides forward compatibility if the default values are updated in future framework versions.
2023-12-12 13:52:03 -05:00
b979203e12 modify #3528 so that it only saves the ImagesFiles and UploadableFiles if they are different than the default - otherwise it sets them to blank. This provides forward compatibility if the default values are updated in future framework versions. 2023-12-12 13:51:41 -05:00
c9a368f13a Merge pull request #3542 from sbwalker/dev
move message into file upload region
2023-12-12 13:39:52 -05:00
cfabf22fef move message into file upload region 2023-12-12 13:39:37 -05:00
61aef77916 Merge pull request #3541 from sbwalker/dev
refactor fix for #3539 to avoid string comparison
2023-12-12 13:32:45 -05:00
8b6c6beceb refactor fix for #3539 to avoid string comparison 2023-12-12 13:32:33 -05:00
4688e49454 Merge pull request #3540 from leigh-pointer/LoginURL
Fix #3539 for Multiple click on login button
2023-12-12 13:16:16 -05:00
76cc28b215 Merge pull request #3535 from rcpacheco/rcp_manage_language
Edit languages to set default.
2023-12-12 13:02:17 -05:00
467e5423d2 Fix #3539 for Multiple click on login button
Updated the LoginBase, if the URI doesn't contain the login URL then the NavigationManager is updated.
2023-12-12 07:25:35 +01:00
422bf7b689 Edit languages to set default. 2023-12-07 18:12:49 -06:00
1d230bd4aa Merge pull request #3533 from sbwalker/dev
fix issue where the list of containers was not being refreshed when the theme was changed
2023-12-05 16:57:42 -05:00
029850842b fix issue where the list of containers was not being refreshed when the theme was changed 2023-12-05 16:57:22 -05:00
3b090396a4 Merge pull request #3532 from sbwalker/dev
added HybridEnabled field to Site table to indicate if .NET MAUI hybrid applications can be integrated
2023-12-04 16:35:24 -05:00
2e4656ae8b added HybridEnabled field to Site table to indicate if .NET MAUI hybrid applications can be integrated 2023-12-04 16:35:03 -05:00
ebce63baa4 Merge pull request #3531 from sbwalker/dev
add generic Result model for leveraging Result pattern
2023-12-04 15:41:19 -05:00
08f691ee0d add generic Result model for leveraging Result pattern 2023-12-04 15:41:00 -05:00
7550a19951 Merge pull request #3528 from leigh-pointer/FileExtentions
File Extension management - site wide. Fix for #3493
2023-12-04 15:16:42 -05:00
daeb76df11 Corrected the IsPrivate 2023-12-04 17:26:37 +01:00
31315e5ba6 Merge pull request #3530 from sbwalker/dev
allow AddModuleMessage to support displaying the message at the bottom of the module instance
2023-12-04 09:57:10 -05:00
e4e1fa6a4c allow AddModuleMessage to support displaying the message at the bottom of the module instance 2023-12-04 09:55:53 -05:00
6e36312be8 Update Site with ImageFiles and UploadableFiles 2023-12-04 09:07:11 +01:00
9cc7ba1d82 Merge branch 'oqtane:dev' into FileExtentions 2023-12-04 08:15:49 +01:00
9182001147 Merge pull request #3529 from sbwalker/dev
invalidate client assemblies cache when site is updated
2023-12-03 10:27:53 -05:00
5d1510083e invalidate client assemblies cache when site is updated 2023-12-03 10:27:33 -05:00
7035f4cc1f File Extension management - site wide.
I have added to the site settings file extension management.
The Constants remain for backward compatibility.
If the extensions are not updated then the Constant will be used.
2023-12-01 21:09:33 +01:00
da826b2a22 Merge pull request #3527 from sbwalker/dev
optimize assembly loading performance
2023-12-01 14:54:56 -05:00
a152d8664d optimize assembly loading performance 2023-12-01 14:54:44 -05:00
6ad75d963a Merge pull request #3526 from sbwalker/dev
only allow download of assemblies when using WebAssembly
2023-12-01 14:38:32 -05:00
e92d34a9e3 only allow download of assemblies when using WebAssembly 2023-12-01 14:38:16 -05:00
768066db58 Merge pull request #3525 from leigh-pointer/Retention
Retention controls and data should be number / int not string
2023-12-01 12:19:48 -05:00
886b63e55b Retention controls and data should be number / int not string
The Retention input controls in Log, Visitors and Notifications allowed text to be saved.  Modified all controls to accept only number.
2023-12-01 18:05:52 +01:00
3c7ee65b7d Merge pull request #3523 from beolafsen/dev
Extend the Section component  with "IsVisible" parameter
2023-12-01 08:50:19 -05:00
0e060c4564 Extend the Section component with "Visible" parameter 2023-12-01 07:34:17 +01:00
3e127dbd9c Merge pull request #3522 from sbwalker/dev
remove Blazor Hybrid from Hosting Models selection as it is not actually a hosting model
2023-11-30 17:19:18 -05:00
ad66d14a1b remove Blazor Hybrid from Hosting Models selection as it is not actually a hosting model 2023-11-30 17:18:37 -05:00
5db61b78ea Merge pull request #3518 from sbwalker/dev
add defensive logic to handle scenario where a tenant connection string does not exist in appsettings.json
2023-11-29 17:32:08 -05:00
85f9597f2c add defensive logic to handle scenario where a tenant connection string does not exist in appsettings.json 2023-11-29 17:31:51 -05:00
73aca22605 Merge pull request #3517 from sbwalker/dev
suppress logging for resized image files which no longer exist
2023-11-29 13:59:06 -05:00
077343ca20 suppress logging for resized image files which no longer exist 2023-11-29 13:58:52 -05:00
c76946c770 Merge pull request #3516 from sbwalker/dev
update SQLite and PostgreSQL to latest packages
2023-11-29 13:29:17 -05:00
2344a49260 update SQLite and PostgreSQL to latest packages 2023-11-29 13:28:53 -05:00
65f463dbbd Merge pull request #3471 from PfaffIC/dev-loginauthcookie
Added auth cookie expiration for external login via OAuth2.
2023-11-29 13:22:39 -05:00
6e100a70b9 Merge pull request #3510 from markdav-is/patch-4
Update _Host.cshtml.cs to account for fully qualified type names in theme resources
2023-11-29 13:21:39 -05:00
77650ebe09 Merge pull request #3515 from sbwalker/dev
refactor alwaysremember logic for consistency
2023-11-29 13:19:59 -05:00
cdba2bd9e2 refactor alwaysremember logic for consistency 2023-11-29 13:19:45 -05:00
e84cc92fcd Merge pull request #3514 from sbwalker/dev
add ClaimsPrincipal extension methods
2023-11-29 13:02:10 -05:00
a2890948bb add ClaimsPrincipal extension methods 2023-11-29 13:01:54 -05:00
f2e1ebef45 Merge pull request #3513 from sbwalker/dev
user identity improvements
2023-11-29 10:42:50 -05:00
3c33614115 user identity improvements 2023-11-29 10:42:23 -05:00
fcc4b1e8ee Merge pull request #3511 from sbwalker/dev
include Review Claims option in External Login for troubleshooting settings
2023-11-27 15:37:51 -05:00
c8ac4ec1e8 include a more detailed error message for tenant database migration issues 2023-11-27 15:35:58 -05:00
93ab8b88d4 include Review Claims option in External Login for troubleshooting settings 2023-11-27 15:07:48 -05:00
cc12c302e3 Update _Host.cshtml.cs
Noticed some spaces showing up in theme resources and tracked it down to this line.  Wrapping the name in a call to Utilities.GetTypeName cleared things up.
2023-11-26 18:13:41 -08:00
73941ca30e Merge pull request #3506 from sbwalker/dev
remove InstallDatabase method as it is no longer required now that all database providers are installed automatically
2023-11-22 16:10:03 -05:00
f963711820 remove InstallDatabase method as it is no longer required now that all database providers are installed automatically 2023-11-22 15:53:25 -05:00
1a0a301fb6 Merge pull request #3505 from sbwalker/dev
change terminology to Uninstall rather than Delete in Module Definitions and Themes - to better describe the action
2023-11-22 15:44:25 -05:00
f87a9b7aae change terminology to Uninstall rather than Delete in Module Definitions and Themes - to better describe the action 2023-11-22 15:44:07 -05:00
f18f0bc762 Merge pull request #3504 from sbwalker/dev
only display Authorization Response Type option for OpenIDConnect and fix missing localization
2023-11-22 15:37:08 -05:00
c3b1bc6674 only display Authorization Response Type option for OpenIDConnect and fix missing localization 2023-11-22 15:36:49 -05:00
e9bb228528 Merge pull request #3503 from sbwalker/dev
prevent localized Microsoft.CodeAnalysis.*.resources.dll files from being included in release
2023-11-22 15:04:19 -05:00
a906957454 prevent localized Microsoft.CodeAnalysis.*.resources.dll files from being included in release 2023-11-22 15:04:01 -05:00
49d10337a1 Merge pull request #3502 from sbwalker/dev
add Update API validation to default module template
2023-11-22 14:55:07 -05:00
87c6c31f1f add Update API validation to default module template 2023-11-22 14:54:06 -05:00
5f3639e396 Merge pull request #3501 from sbwalker/dev
add additional validation logic to Update API methods to ensure model ID matches ID parameter
2023-11-22 14:47:53 -05:00
14d36ef8dc add additional validation logic to Update API methods to ensure model ID matches ID parameter 2023-11-22 14:47:28 -05:00
fc186f1718 Merge pull request #3496 from sbwalker/dev
ignore nuget packages
2023-11-21 15:41:23 -05:00
70aa93fe8d ignore nuget packages 2023-11-21 15:41:13 -05:00
901df22d39 Merge pull request #3495 from sbwalker/dev
add database provider packages back to wwwroot/Packages as they are needed when cloning a fresh instance of source from Github
2023-11-21 15:22:05 -05:00
cd0fe3d6d4 add database provider packages back to wwwroot/Packages as they are needed when cloning a fresh instance of source from Github 2023-11-21 15:21:44 -05:00
09f2c3f645 Merge pull request #3494 from sbwalker/dev
fix .NET upgrade issue related to database provider packages
2023-11-21 15:15:17 -05:00
116542d8e4 fix .NET upgrade issue related to database provider packages 2023-11-21 15:09:14 -05:00
ffae6e269b Merge pull request #3487 from sbwalker/dev
increase size of MaximumReceiveMessageSize for Blazor Server
2023-11-17 16:06:24 -05:00
2194dc0463 increase size of MaximumReceiveMessageSize for Blazor Server 2023-11-17 16:06:11 -05:00
da2d426137 Update README.md 2023-11-16 15:49:22 -05:00
8df7672624 Update README.md 2023-11-16 15:15:34 -05:00
b9dba61c97 Update README.md 2023-11-16 15:07:53 -05:00
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
96e8e9736f Added auth cookie expiration for external login via OAuth2.
Auth cookie expiration time ist set to value provided in Setting "LoginOptions:CookieExpiration" (if provided).
2023-11-13 11:28:58 +01: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
f56e102f20 Merge pull request #3045 from oqtane/master
Merge pull request #3044 from oqtane/dev
2023-07-18 12:25:59 -04:00
010c669ce2 Merge pull request #3044 from oqtane/dev
4.0.1 Release
2023-07-18 12:25:41 -04:00
d328058075 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-18 17:40:29 +02:00
9609e7b297 Merge pull request #3042 from leigh-pointer/ControlPanelPadding
Fixed up margins in Control Panel
2023-07-18 11:08:57 -04:00
92718372b8 Fixed up margins in Control Panel
Added top margin to the Module Management
2023-07-18 12:47:48 +02:00
9108eb5616 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-18 12:37:53 +02:00
354ad9d0d5 Merge pull request #3040 from sbwalker/dev
use html line breaks in error log notifications
2023-07-17 16:59:54 -04:00
100b92036b use html line breaks in error log notifications 2023-07-17 16:59:40 -04:00
e9ed1ecc8c Merge pull request #3039 from sbwalker/dev
prevent client looping if server is down
2023-07-17 11:49:16 -04:00
92595ccc35 prevent client looping if server is down 2023-07-17 11:49:06 -04:00
ea45044dc7 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-17 16:48:24 +02:00
1ef28494b3 Merge pull request #3038 from sbwalker/dev
use discretion with LogLevel.Error messages
2023-07-17 09:29:00 -04:00
985d324593 use discretion with LogLevel.Error messages 2023-07-17 09:28:29 -04:00
96e7178cd2 Merge pull request #3036 from sbwalker/dev
Added logic to ensure assembly version being installed is equal to or greater than existing assembly
2023-07-17 08:16:16 -04:00
f41e2358a9 Added logic to ensure assembly version being installed is equal to or greater than existing assembly 2023-07-17 08:15:56 -04:00
0f146ff355 Merge pull request #3035 from sbwalker/dev
Added a ShowProgress parameter to FileManager (enabled by default). Disabling will display a simple spinner rather than detailed progress information during upload.
2023-07-17 07:43:18 -04:00
465f241e89 Added a ShowProgress parameter to FileManager (enabled by default). Disabling will display a simple spinner rather than detailed progress information during upload. 2023-07-17 07:42:48 -04:00
2bdb011240 Merge pull request #3026 from vnetonline/enhance-filemanager
[ENHANCE] - Moved `SetImage()` before the OnSelect fires so that the …
2023-07-17 07:11:41 -04:00
dd55725f9b Merge pull request #3034 from vnetonline/fix-publish-module
[FIX] - Page disappears if you unpublish and publish a module
2023-07-17 07:10:55 -04:00
6b16808207 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-17 11:01:10 +02:00
411111fa55 [FIX] - Page disappears if you unpublish and publish a module on the page 2023-07-17 09:33:04 +10:00
5345a12aca [ENHANCE] - Moved SetImage() before the OnSelect fires so that the Parent can retrieve the Image from fileManager rather then calling FileService to retrieve again 2023-07-17 07:58:39 +10:00
39adcde16d Merge pull request #3030 from sbwalker/dev
display message if package service does not return package requested
2023-07-15 15:27:01 -04:00
3c37ee60af display message if package service does not return package requested 2023-07-15 15:26:48 -04:00
4effc4dd60 Merge pull request #3028 from sbwalker/dev
marketplace changes
2023-07-15 10:01:41 -04:00
fec9fa17e8 marketplace changes 2023-07-15 10:01:27 -04:00
4071b285ce Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-15 12:34:10 +02:00
fca306ffa6 Merge pull request #3025 from sbwalker/dev
SupportUrl integration
2023-07-14 15:19:04 -04:00
0e042925f2 SupportUrl integration 2023-07-14 15:18:52 -04:00
99421846c7 Merge pull request #3024 from sbwalker/dev
remove OnSelect call from OnParametersSet() which was causing infinite loop
2023-07-14 12:15:34 -04:00
592885ff6c remove OnSelect call from OnParametersSet() which was causing infinite loop 2023-07-14 12:15:16 -04:00
a95306c317 Merge pull request #3022 from sbwalker/dev
moved UserManager to Managers namespace
2023-07-13 17:05:21 -04:00
8bdbf7b994 moved UserManager to Managers namespace 2023-07-13 17:05:01 -04:00
7bc80f0814 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-13 17:21:55 +02:00
aed65d2594 Merge pull request #3020 from sbwalker/dev
add handling for DisplayName
2023-07-13 11:17:04 -04:00
63b3e4d90d add handling for DisplayName 2023-07-13 11:16:49 -04:00
f9c459eab2 Merge pull request #3019 from sbwalker/dev
fix bash script line endings and preserve using gitattributes (credit @rcpacheco in #2958)
2023-07-13 10:19:14 -04:00
a6d83149a4 fix bash script line endings and preserve using gitattributes (credit @rcpacheco in #2958) 2023-07-13 10:18:52 -04:00
387b477034 Merge pull request #3018 from sbwalker/dev
allow page themes to be different from site theme - display warning message
2023-07-13 09:09:00 -04:00
68cdcd193c allow page themes to be different from site theme - display warning message 2023-07-13 08:58:43 -04:00
08438c7725 Merge pull request #2964 from OpenEugene/release/v4.0.0
Show all themes on page add/edit
2023-07-13 07:52:34 -04:00
1894b46d3a Merge pull request #3016 from leigh-pointer/UserSort
User management sort
2023-07-13 07:41:40 -04:00
367a825955 Merge pull request #3017 from sbwalker/dev
added logging to ServiceBase to capture HTTP errors
2023-07-13 07:40:55 -04:00
be799eb254 added logging to ServiceBase to capture HTTP errors 2023-07-13 07:39:24 -04:00
4820a27016 User management sort
Added Sorting to User management component.
2023-07-13 12:54:08 +02:00
6b57202421 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-13 11:40:58 +02:00
4e82212956 Merge pull request #3015 from sbwalker/dev
fix issue for site folders
2023-07-12 19:49:56 -04:00
6b0f5ed12d fix issue for site folders 2023-07-12 19:49:38 -04:00
0a258caf48 Merge pull request #3013 from sbwalker/dev
remove unnecessary using statement
2023-07-12 17:59:34 -04:00
f546a68f7c remove unnecessary using statement 2023-07-12 17:59:21 -04:00
1e5bab09db Merge pull request #3012 from sbwalker/dev
migrate remaining methods to UserManager
2023-07-12 17:52:29 -04:00
35fa18a852 migrate remaining methods to UserManager 2023-07-12 17:52:16 -04:00
90c1eeb312 Merge pull request #3009 from ajahangard/dev
Using DynamicComponent Instead of RenderFragment in ContainerBuilder
2023-07-12 16:40:09 -04:00
404c32b49b Merge pull request #3011 from sbwalker/dev
add a UserManager to simplify user creation, improve response validation in ServiceBase, allow Section component to support parameter changes
2023-07-12 16:37:54 -04:00
c0f4cd2097 add a UserManager to simplify user creation, improve response validation in ServiceBase, allow Section component to support parameter changes 2023-07-12 16:37:18 -04:00
fa98908dea Merge branch 'oqtane:dev' into dev 2023-07-12 12:43:40 +03:30
23e8567e86 Using DynamicComponent Instead of RenderFragment in ContainerBuilder 2023-07-12 12:43:00 +03:30
ca3edb6687 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 15:22:30 +02:00
a02e28f493 Merge pull request #3005 from leigh-pointer/DropIsPublic
The Down path is incorrect
2023-07-11 08:59:39 -04:00
7428f87899 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 14:47:53 +02:00
d6a7e32ca9 Merge pull request #3006 from sbwalker/dev
support both 404 and 403 status codes in GET API response (404 should not log)
2023-07-11 08:14:52 -04:00
df0f562817 support both 404 andf 403 status codes in API response (404 should not log) 2023-07-11 08:14:00 -04:00
0dea2bc79e The Down path is incorrect
The Down Path drops the wrong column
2023-07-11 08:37:23 +02:00
02481560a9 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 08:29:05 +02:00
dc1d1015ed Merge pull request #2999 from sbwalker/dev
prepare for 4.0.1 release
2023-07-10 16:51:20 -04:00
59fffbd3ee prepare for 4.0.1 release 2023-07-10 16:51:03 -04:00
655cbca574 Merge pull request #2998 from sbwalker/dev
add module name to the Module Settings UI
2023-07-10 16:36:57 -04:00
e22c28a3c9 add module name to the Module Settings UI 2023-07-10 16:36:37 -04:00
e823e371f6 Merge pull request #2997 from sbwalker/dev
fix #2976 - add logging methods to ThemeBase
2023-07-10 16:26:49 -04:00
feae7d6269 fix #2976 - add logging methods to ThemeBase 2023-07-10 16:26:30 -04:00
e1c0ea0720 Merge pull request #2996 from sbwalker/dev
fix #2977 - margin between edit and cog
2023-07-10 16:20:33 -04:00
4e1a389c8b fix #2977 - margin between edit and cog 2023-07-10 16:20:16 -04:00
0362a6a3dc Merge pull request #2995 from sbwalker/dev
fix #2978 - allow host users and admins to have personalized pages
2023-07-10 16:14:14 -04:00
3891dea009 fix #2978 - allow host users and admins to have personalized pages 2023-07-10 16:13:56 -04:00
2d66063763 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 21:19:27 +02:00
1ccb7e2092 Merge pull request #2994 from sbwalker/dev
prevent logging of error for personalized pages
2023-07-10 14:51:58 -04:00
62ad99d0b6 prevent logging of error for personalized pages 2023-07-10 14:51:32 -04:00
66d07fbed3 Merge pull request #2980 from vnetonline/fix-ispersonalised
Fixing personalized page created to be UserName if DisplayName is null
2023-07-10 14:28:55 -04:00
ec117adb2d Merge pull request #2993 from sbwalker/dev
make GetHttpClient() public
2023-07-10 14:16:35 -04:00
3aff31b826 make GetHttpClient() public 2023-07-10 14:16:17 -04:00
1621944105 Merge pull request #2992 from sbwalker/dev
refactored upload so that it is not dependent on Folder Browse permission
2023-07-10 11:44:28 -04:00
16d0d11e0b refactored upload so that it is not dependent on Folder Browse permission 2023-07-10 11:44:05 -04:00
9158e24295 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 17:41:35 +02:00
0b81f28e99 Merge pull request #2991 from sbwalker/dev
fix validation issue in FileManager related to Browse permissions
2023-07-10 10:10:16 -04:00
2ccb814223 fix validation issue in FileManager related to Browse permissions 2023-07-10 10:09:57 -04:00
f213abd70a Merge pull request #2990 from sbwalker/dev
fix migration error caused by new IsRead field in Notifications. Microsoft.Data.SqlClient.SqlException: 'ALTER TABLE only allows columns to be added that can contain nulls, or have a DEFAULT definition specified.'
2023-07-10 08:59:37 -04:00
b87eddeeda fix migration error caused by new IsRead field in Notifications. Microsoft.Data.SqlClient.SqlException: 'ALTER TABLE only allows columns to be added that can contain nulls, or have a DEFAULT definition specified.' 2023-07-10 08:59:09 -04:00
92fc56e70d Merge pull request #2988 from leigh-pointer/TemplateConfig
Remove Build for Oqtane Server in ConfigManger
2023-07-10 08:45:16 -04:00
e22f3238fb Merge pull request #2989 from sbwalker/dev
add API method to get File based on name, and fix permission validation for Folder
2023-07-10 08:44:41 -04:00
c597c4c234 add API method to get File based on name, and fix permission validation for Folder 2023-07-10 08:44:14 -04:00
5973c3d1a0 Remove Build for Oqtane Server in ConfigManger
Remove Build for Oqtane Server in ConfigManger
2023-07-10 14:40:00 +02:00
118e177756 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 14:37:16 +02:00
d50472cd7b Merge pull request #2984 from leigh-pointer/ConfigExtern
Fix for #2983 Oqtane. Server selected in for build in configmanager
2023-07-10 07:44:46 -04:00
11604bbcfd Merge pull request #2985 from vnetonline/fix-modulesettings-resource-type-name
[FIX] - Fixed the ResourceType in Module Settings with correct NameSpace
2023-07-10 07:41:34 -04:00
cf428598d5 [FIX] - Fixed the Resource Type in Module Settings with correct Namespace 2023-07-10 12:52:02 +10:00
a45fc0c4df Fix for #2983 Oqtane. Server selected in for build in configmanager
Removed Oqtane. Server build from configuration manager debug and release
2023-07-09 21:30:45 +02:00
88ac82a013 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-09 21:12:47 +02:00
5abf0df154 Merge pull request #2982 from sbwalker/dev
Package enhancements for Marketplace
2023-07-09 08:36:34 -04:00
9a3b458c45 Package enhancements for Marketplace 2023-07-09 08:36:14 -04:00
1b2de37c4e Merge pull request #2975 from vnetonline/enhance-notifications-isread
[ENHANCE] - Added IsRead property to Notifications
2023-07-08 10:07:44 -04:00
f7338bf00e Update GetNotifications (read) to retrieve descending oder by CreatedOn at repository level 2023-07-07 10:21:22 +10:00
bad7be39a6 Fixing personalized page created to be UserName if DisplayName is null 2023-07-07 09:21:49 +10:00
40459defa4 Cosmetic change to more the filter drop down to top of the notifications tab 2023-07-06 15:28:11 +10:00
b7de4b81a6 [ENHANCE] - Added IsRead property to Notifications
Fixed Version to Tenant.04.00.01.01 and reverted the Program.cs back to the way it was

This reverts commit 82fef82c4f.

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

Fixed Typo in Notification Controller

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

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

Includeda few setting options as per suggestion by @sbwalker  intended to provide a simple demonstration of how to use Theme and Container settings
2023-06-30 08:04:13 +10:00
a2a64e73bc Merge branch 'oqtane:dev' into dev 2023-06-30 07:41:49 +10:00
15305e3a0b Merge pull request #2956 from leigh-pointer/RoleDates
Closes #2955 User Roles Effective and Expiry date Rendering as Text
2023-06-29 14:42:17 -04:00
bcb484ba62 Closes #2955 User Roles Effective and Expiry date Rendering as Text
Updated the User Roles to use Date control and fixed the code to accept DateTime values
2023-06-29 19:17:38 +02:00
278fd8023b Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-29 18:37:15 +02:00
ba7efb45e7 Merge pull request #2954 from sbwalker/dev
handle paths during Folder add/update
2023-06-29 12:24:37 -04:00
8d3d218067 handle paths during Folder add/update 2023-06-29 12:24:20 -04:00
5185934f25 Merge pull request #2953 from sbwalker/dev
fix progress bar styling in FileManager
2023-06-29 11:54:20 -04:00
0a9cbfd0e5 fix progress bar styling in FileManager 2023-06-29 11:54:05 -04:00
35e237b5b3 Merge pull request #2952 from sbwalker/dev
allow FileManager to support changes in parameters
2023-06-29 11:21:02 -04:00
ba18cd505b allow FileManager to support changes in parameters 2023-06-29 11:20:45 -04:00
662df53a10 Fixed Indenting in ThemeSettings.cs 2023-06-29 13:22:51 +10:00
eadf2e89a8 Fix HasChildren property not considering if page is not in navigation (i.e. hidden) 2023-06-29 11:56:19 +10:00
a3b54b9891 Merge branch 'oqtane:dev' into dev 2023-06-29 11:05:12 +10:00
18cab2c759 Merge pull request #2950 from sbwalker/dev
allow admin to navigate to site settings for deleted site
2023-06-28 13:11:29 -04:00
8edc4fd67a allow admin to navigate to site settings for deleted site 2023-06-28 13:11:15 -04:00
b914163499 Merge pull request #2949 from sbwalker/dev
fix #2941 - HasChildren property not considering deleted pages
2023-06-28 13:05:46 -04:00
c55c8bff6e fix #2941 - HasChildren property not considering deleted pages 2023-06-28 13:05:34 -04:00
e669bb1f82 Merge pull request #2948 from sbwalker/dev
fix #2942 - reverse ordering of page name and site name for page title
2023-06-28 12:51:11 -04:00
452c3fd355 fix #2942 - reverse ordering of page name and site name for page title 2023-06-28 12:50:58 -04:00
60be0c92da Merge pull request #2947 from sbwalker/dev
improve helptext for Aliases
2023-06-28 12:44:16 -04:00
03bd88a2d5 improve helptext for Aliases 2023-06-28 12:44:00 -04:00
7f355698bc Merge pull request #2946 from sbwalker/dev
use case insensitive comparison for activetab name
2023-06-28 12:27:34 -04:00
22ec675cec use case insensitive comparison for activetab name 2023-06-28 12:27:21 -04:00
3954c482ce Merge pull request #2945 from sbwalker/dev
set Expanded to lowercase if specified
2023-06-28 12:25:13 -04:00
d06feec37b set Expanded to lowercase if specified 2023-06-28 12:24:59 -04:00
536af96ccc Merge pull request #2944 from sbwalker/dev
fix #2938 - path not updated correctly when parent page changed
2023-06-28 12:17:42 -04:00
e1a8d3db54 fix #2938 - path not updated correctly when parent page changed 2023-06-28 12:17:27 -04:00
83d51a85ce Adding Solution file back to Default Theme Template 2023-06-28 22:32:42 +10:00
4400744f35 Added ThemeSettings and ContainerSettings to Default Theme Template
Added ThemeSettings and ContainerSettings to Default Theme Template to Demonstrate how these features could be added to developers designing a theme as requested and discussed in Issue #2633
2023-06-28 22:25:07 +10:00
ea4b85ad54 Merge pull request #2939 from sbwalker/dev
filter deleted sites in notification job
2023-06-27 11:42:36 -04:00
60b6a9ce47 filter deleted sites in notification job 2023-06-27 11:42:15 -04:00
927e00bd66 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-27 09:31:07 +02:00
cd8d2dd8f3 Update README.md 2023-06-26 14:36:53 -04:00
6861965779 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-26 20:14:07 +02:00
8a9a1d7df7 Merge pull request #2937 from sbwalker/dev
cosmetic fix for input elements within a table intoduced with Bootstrap 5.3
2023-06-26 14:00:03 -04:00
e7e66699b1 cosmetic fix for input elements within a table intoduced with Bootstrap 5.3 2023-06-26 13:59:44 -04:00
681321feeb Merge pull request #2936 from oqtane/master
Merge pull request #2935 from oqtane/dev
2023-06-26 11:31:16 -04:00
a637b6d4d0 Merge pull request #2935 from oqtane/dev
4.0.0 Release
2023-06-26 11:30:59 -04:00
97026746f8 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-26 14:57:48 +02:00
5161ade786 Merge pull request #2932 from sbwalker/dev
added deprecation message for IHostResources
2023-06-26 08:12:01 -04:00
6dd62a164e added deprecation message for IHostResources 2023-06-26 08:11:46 -04:00
3bece7599c Merge pull request #2931 from sbwalker/dev
fix validation message behavior in installer
2023-06-25 09:10:07 -04:00
b2f3a53980 fix validation message behavior in installer 2023-06-25 09:09:53 -04:00
cea31a8573 Merge pull request #2930 from sbwalker/dev
remove A/B testing logic for stylesheets - discard root component approach in favor of legacy JS Interop approach to eliminate FOUC issues
2023-06-25 08:39:41 -04:00
ce6647a84a remove A/B testing logic for stylesheets - discard root component approach in favor of legacy JS Interop approach to eliminate FOUC issues 2023-06-25 08:39:12 -04:00
d54ba9a5c6 Merge pull request #2929 from sbwalker/dev
added logging to HostedServiceBase
2023-06-23 15:19:33 -04:00
1e18d913e0 added logging to HostedServiceBase 2023-06-23 15:19:18 -04:00
ef8c47a49f Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-23 14:32:00 +02:00
0041c7d3b3 Merge pull request #2928 from sbwalker/dev
allow for testing CSS page transitions
2023-06-23 08:16:56 -04:00
f6cca5cb35 allow for testing CSS page transitions 2023-06-23 08:16:39 -04:00
fa5abbf96f Merge pull request #2927 from sbwalker/dev
fix ordering of stylesheets
2023-06-23 08:07:22 -04:00
2ff365765d fix ordering of stylesheets 2023-06-23 08:07:06 -04:00
ee31e4a411 Merge pull request #2926 from sbwalker/dev
clarify documentation and parameter names
2023-06-23 07:44:31 -04:00
ee4068d671 clarify documentation and parameter names 2023-06-23 07:44:15 -04:00
5e86c95db6 Merge pull request #2925 from sbwalker/dev
add validation logic to ensure page theme martches site theme
2023-06-23 07:38:52 -04:00
6126624631 add validation logic to ensure page theme martches site theme 2023-06-23 07:38:33 -04:00
a320e4d48d Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-22 21:59:51 +02:00
a7c5841e76 Merge pull request #2924 from sbwalker/dev
external template changes to support non-Windows environments
2023-06-22 15:24:43 -04:00
0f242a94b4 external template changes to support non-Windows environments 2023-06-22 15:24:27 -04:00
8f8a0897e4 Add files via upload 2023-06-22 15:23:10 -04:00
7c6b64123c Add files via upload 2023-06-22 15:22:10 -04:00
55642093ed Merge pull request #2923 from sbwalker/dev
allow CSS testing using old and new method
2023-06-22 14:05:25 -04:00
5660f40512 allow CSS testing using old and new method 2023-06-22 14:05:08 -04:00
3259b7183b Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-22 17:43:30 +02:00
df7cd648ac Merge pull request #2921 from sbwalker/dev
restrict to a single theme per site
2023-06-22 11:32:21 -04:00
2a5713ed67 restrict to a single theme per site 2023-06-22 11:32:03 -04:00
b7c36c07c6 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-22 06:03:04 +02:00
de93281f3e Merge pull request #2920 from sbwalker/dev
cleanup and ensure site level scripts work properly
2023-06-21 15:49:13 -04:00
86fbdced1b cleanup and ensure site level scripts work properly 2023-06-21 15:49:00 -04:00
43bcfb9a4e Merge pull request #2910 from vnetonline/dev
[ENHANCE] - Added Module to NameSpace [Owner].Module.[Module]
2023-06-21 09:57:36 -04:00
0daf6af3b8 Merge pull request #2918 from sbwalker/dev
fix #2387 - improve container selection in Edit Page
2023-06-21 09:20:23 -04:00
c3bccbade8 fix #2387 - improve container selection in Edit Page 2023-06-21 09:20:05 -04:00
3261628da4 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-21 14:59:49 +02:00
1e39f48235 Merge pull request #2917 from sbwalker/dev
fix null handling in stylesheet logic
2023-06-21 08:47:39 -04:00
05b5d9da9b fix null handling in stylesheet logic 2023-06-21 08:47:25 -04:00
3491dde94a Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-21 14:46:44 +02:00
db2aa920e2 Merge pull request #2916 from sbwalker/dev
fix #2912 - move JavaScript handling from ThemeBuilder to component OnAfterRenderAsync
2023-06-21 08:32:11 -04:00
8067b2e634 fix #2912 - move JavaScript handling from ThemeBuilder to component OnAfterRenderAsync 2023-06-21 08:31:51 -04:00
273c6d7dee Merge branch 'oqtane:dev' into dev 2023-06-21 10:21:30 +10:00
4d0149a4a0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-20 14:53:57 +02:00
d9f3db5bf3 Merge pull request #2914 from sbwalker/dev
integrate old logic for managing stylesheets using JS Interop
2023-06-20 08:52:18 -04:00
c8a679ecce integrate old logic for managing stylesheets using JS Interop 2023-06-20 08:52:02 -04:00
3abe47ae9e [ENHANCE] - Added Module to NameSpace [Owner].Module.[Module]
[ENHANCE] - Added Module to NameSpace [Owner].Module.[Module]
2023-06-20 16:58:30 +10:00
725a8dc237 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-20 07:50:00 +02:00
fcb05a7834 Merge pull request #2908 from sbwalker/dev
fix parent page selection in add page and fix navigation on save/cancel
2023-06-19 16:22:57 -04:00
02e22df4d5 fix parent page selection in add page and fix navigation on save/cancel 2023-06-19 16:22:49 -04:00
694c0af146 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-19 21:52:12 +02:00
8bfca2f10a Merge pull request #2905 from sbwalker/dev
more optimizations of Head component
2023-06-19 15:11:07 -04:00
6bea9a087c more optimizations of Head component 2023-06-19 15:10:58 -04:00
3f4218f75f Merge pull request #2904 from sbwalker/dev
fix #2900 - theme deletion and fallback
2023-06-19 12:29:51 -04:00
3849f59126 fix #2900 - theme deletion and fallback 2023-06-19 12:29:43 -04:00
239b12cbb0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-19 17:03:37 +02:00
8ccee87378 Merge pull request #2898 from vnetonline/dev
[ENHANCE] - Added Theme to Name Space [Owner].Theme.[Theme]
2023-06-19 10:25:01 -04:00
9e518ffc4c Merge pull request #2903 from sbwalker/dev
add defensive logic
2023-06-19 10:11:32 -04:00
e3233fd19f add defensive logic 2023-06-19 10:11:24 -04:00
d3af9b0f14 Merge pull request #2902 from sbwalker/dev
optimize head component rendering
2023-06-19 09:01:53 -04:00
08daca848b optimize head component rendering 2023-06-19 09:01:40 -04:00
c5da8e3b10 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-19 10:32:47 +02:00
3b0ffde1fb [ENHANCE] - Added Theme to Name Space [Owner].Theme.[Theme]
changes all template files to conform to  [Owner].Theme.[Theme]
2023-06-16 14:52:13 +10:00
6ca97e8d5d Merge pull request #2897 from sbwalker/dev
allow selection of site template during installation
2023-06-15 19:10:52 -04:00
34a727b435 allow selection of site template during installation 2023-06-15 19:10:37 -04:00
6a9c865fec Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-14 15:52:56 +02:00
b1b9100600 Merge pull request #2896 from sbwalker/dev
create AppendHeadContent method to consolidate logic
2023-06-14 09:37:48 -04:00
bda0943d58 create AppendHeadContent method to consolidate logic 2023-06-14 09:37:34 -04:00
ad6db71f7e Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-14 15:11:40 +02:00
b21d202c7f Merge pull request #2895 from vnetonline/dev 2023-06-14 07:32:11 -04:00
3ea8ea1e3b Fixing the Library needed for declaring new way of theme resources 2023-06-14 19:13:04 +10:00
ee897a9973 Fixed targeting .NET 7.0 in ThemeController and ModuleDefinitionController missed in #2888 and #2890 2023-06-14 16:51:23 +10:00
bb9ea3f60f Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-14 06:35:28 +02:00
5f8cfb9977 Merge pull request #2893 from sbwalker/dev
update Installer to use new method for including CSS
2023-06-13 17:26:23 -04:00
d7928a70bf update Installer to use new method for including CSS 2023-06-13 17:26:07 -04:00
93568af5a9 Merge pull request #2892 from sbwalker/dev
upgrade external theme template to Bootstrap 5.3
2023-06-13 17:14:13 -04:00
9e86d97253 upgrade external theme template to Bootstrap 5.3 2023-06-13 17:13:59 -04:00
84bac0c462 Merge pull request #2891 from sbwalker/dev
upgrade to Bootstrap 5.3
2023-06-13 17:10:03 -04:00
3c18a258ff upgrade to Bootstrap 5.3 2023-06-13 17:09:49 -04:00
9759c68d58 Merge pull request #2890 from sbwalker/dev
upgrade external theme template to .NET 7
2023-06-13 14:16:58 -04:00
f57a190405 upgrade external theme template to .NET 7 2023-06-13 14:16:43 -04:00
d2eb203aa1 Merge pull request #2889 from sbwalker/dev
fix regression issue from #2852
2023-06-13 14:00:24 -04:00
dd2c2dbe61 fix regression issue from #2852 2023-06-13 14:00:09 -04:00
2529592b66 Merge pull request #2888 from sbwalker/dev
upgraded external module template to .NET 7
2023-06-13 13:10:41 -04:00
f248d2fe4e upgraded external module template to .NET 7 2023-06-13 13:10:20 -04:00
875bcd8ce2 Merge pull request #2885 from sbwalker/dev
handle type for favicon and improve helptext
2023-06-12 12:31:09 -04:00
726f9375e1 handle type for favicon and improve helptext 2023-06-12 12:30:56 -04:00
0c2e200245 Merge pull request #2884 from sbwalker/dev
allow PNG and GIF for favicon specification
2023-06-12 12:00:42 -04:00
a10db462eb allow PNG and GIF for favicon specification 2023-06-12 12:00:25 -04:00
6df28d8171 Merge pull request #2883 from vnetonline/dev
Made the default SQL Server and App Service provisioned Basic Tier
2023-06-12 09:56:34 -04:00
8367f15638 Made the default SQL Server and App Service provisioned Basic Tier 2023-06-12 23:51:36 +10:00
69456d3569 Merge pull request #2881 from sbwalker/dev
move logic for populating theme control names
2023-06-10 13:35:44 -04:00
3b644338bc move logic for populating theme control names 2023-06-10 13:35:22 -04:00
02e29aa22f Merge pull request #2880 from sbwalker/dev
fix for child pages in templates
2023-06-10 10:10:59 -04:00
88c489a585 fix for child pages in templates 2023-06-10 10:10:34 -04:00
706e73210d Merge pull request #2879 from sbwalker/dev
fix page template logic on install
2023-06-10 09:18:13 -04:00
d52809c914 fix page template logic on install 2023-06-10 09:17:52 -04:00
6058286577 Update README.md 2023-06-09 12:37:28 -04:00
a271f24157 Merge pull request #2878 from sbwalker/dev
change project tagline
2023-06-09 12:36:27 -04:00
2299375aaa change project tagline 2023-06-09 12:36:15 -04:00
c3290526a4 Merge pull request #2877 from sbwalker/dev
improvements to page template processing in cases where a page parent and name is specified without a path
2023-06-09 12:33:27 -04:00
a9d871e9af improvements to page template processing in cases where a page parent and name is specified without a path 2023-06-09 12:33:07 -04:00
5eb25796ca Merge pull request #2876 from sbwalker/dev
order theme controls and container comtrols in alphabetical order based on name
2023-06-09 08:50:03 -04:00
5d650bd276 order theme controls and container comtrols in alphabetical order based on name 2023-06-09 08:49:41 -04:00
b6be519537 Merge pull request #2875 from sbwalker/dev
add ScrollToPageTop method to ThemeBase
2023-06-08 08:39:33 -04:00
160477d612 add ScrollToPageTop method to ThemeBase 2023-06-08 08:39:20 -04:00
4a89403c37 Merge remote-tracking branch 'origin/dev' into dev 2023-06-08 08:32:54 +02:00
e54cfa629e Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-08 08:32:33 +02:00
b2bc09be16 Merge pull request #2873 from sbwalker/dev
fix to allow Theme Settings to be supported in Page Management
2023-06-07 10:50:39 -04:00
2f5d1cebb0 fix to allow Theme Settings to be supported in Page Management 2023-06-07 10:50:25 -04:00
e4d144651e Merge pull request #2872 from sbwalker/dev
fixed page setting cleanup on delete and centralized module delete logic within PageModuleRepository
2023-06-06 09:11:28 -04:00
ce56dfc239 fixed page setting cleanup on delete and centralized module delete logic within PageModuleRepository 2023-06-06 09:11:08 -04:00
ad4f8fd7a0 Merge pull request #2871 from leigh-pointer/ExtModDescription
Fix for External Module Description not updated
2023-06-06 08:20:48 -04:00
c4070cb603 External Module Description not updated
Fix for External Module Description not updated #2870
2023-06-06 12:26:39 +02:00
75c9322f75 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-06 07:28:06 +02:00
602fd7d658 Merge pull request #2869 from sbwalker/dev
fix JavaScript injection issue on first render for Resources defined in IModule and ITheme
2023-06-05 22:00:27 -04:00
818fd2fb43 fix JavaScript injection issue on first render for Resources defined in IModule and ITheme 2023-06-05 22:00:02 -04:00
e4912fcb09 Merge pull request #2868 from sbwalker/dev
Head root component integration in .NET MAUI
2023-06-05 14:51:06 -04:00
3692ec49c1 Head root component integration in .NET MAUI 2023-06-05 14:50:54 -04:00
9a1ea3f375 Merge pull request #2867 from sbwalker/dev
ability for non-administrators to edit page settings
2023-06-05 14:33:17 -04:00
10a754642a ability for non-administrators to edit page settings 2023-06-05 14:33:05 -04:00
bc9de5d094 Merge pull request #2865 from sbwalker/dev
use ReturnUrl for Page Management invoked by Control Panel
2023-06-05 09:42:54 -04:00
9f93476167 use ReturnUrl for Page Management invoked by Control Panel 2023-06-05 09:42:33 -04:00
f61492d353 Merge pull request #2864 from sbwalker/dev
fix personalization redirect
2023-06-05 09:30:29 -04:00
50cf67546b fix personalization redirect 2023-06-05 09:30:17 -04:00
41d83ec421 Merge pull request #2863 from sbwalker/dev
improvements for personalized pages
2023-06-05 08:22:47 -04:00
cc9377b37d improvements for personalized pages 2023-06-05 08:22:29 -04:00
7f986d5f60 Merge pull request #2862 from sbwalker/dev
fix page add/edit path
2023-06-02 15:24:31 -04:00
d272bf8a29 fix page add/edit path 2023-06-02 15:24:14 -04:00
ba5260f5b1 Merge pull request #2861 from sbwalker/dev
include methods for dynamic scenarios
2023-06-02 08:34:23 -04:00
40c788fc33 include methods for dynamic scenarios 2023-06-02 08:34:05 -04:00
7eca15d50f Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-02 10:42:16 +02:00
8f147e7de2 Merge pull request #2859 from sbwalker/dev
make page path and moduile title lookups case insensitive
2023-06-01 14:41:32 -04:00
85358c7dd4 make page path and moduile title lookups case insensitive 2023-06-01 14:40:59 -04:00
87c7eafb87 Merge pull request #2858 from sbwalker/dev
use constant rather than magic string
2023-06-01 12:41:14 -04:00
613f4848da use constant rather than magic string 2023-06-01 12:41:03 -04:00
3cb06e4819 Update README.md 2023-06-01 12:22:42 -04:00
811701ef7f Merge pull request #2856 from sbwalker/dev
fix log message spelling
2023-06-01 12:16:53 -04:00
c14dc67d8a fix log message spelling 2023-06-01 12:16:41 -04:00
867767f009 Merge pull request #2855 from sbwalker/dev
ensure PageModule Order is updated
2023-06-01 11:29:43 -04:00
640216d076 ensure PageModule Order is updated 2023-06-01 11:29:31 -04:00
3a1f378e2d Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-01 16:58:00 +02:00
6b7c61f228 Merge pull request #2853 from sbwalker/dev
make AliasName explicit
2023-06-01 10:37:53 -04:00
28ba2c00fc make AliasName explicit 2023-06-01 10:37:39 -04:00
b56daae321 Merge pull request #2852 from sbwalker/dev
optimize pane rendering, preserve querystring parameters in edit mode , relocate anchor tags to ensure they are always injected, add ability to determine if navigation is internal
2023-06-01 08:44:30 -04:00
b57450398c optimize pane rendering, preserve querystring parameters in edit mode, relocate anchor tags to ensure they are always injected, add ability to determine if navigation is internal 2023-06-01 08:44:07 -04:00
0c25cf0ec5 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-31 15:42:33 +02:00
c3d884e895 Merge pull request #2851 from sbwalker/dev
added IEventSubscriber amd EventDistributorHostedService to optimize event processing
2023-05-31 09:36:56 -04:00
7a21f96552 added IEventSubscriber amd EventDistributorHostedService to optimize event processing 2023-05-31 09:36:32 -04:00
55cfbb721b Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-31 08:25:26 +02:00
4ccb4c636f Merge pull request #2849 from sbwalker/dev
ability to specify PageTemplates for modules
2023-05-30 15:52:42 -04:00
0d5c3a3a0c ability to specify PageTemplates for modules 2023-05-30 15:52:27 -04:00
6bc1507114 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-26 16:32:21 +02:00
b093cbff15 Merge pull request #2847 from sbwalker/dev
ensure consistent admin dashboard styling
2023-05-26 08:54:58 -04:00
30cb8ec9c3 ensure consistent admin dashboard styling 2023-05-26 08:54:47 -04:00
d5e51d6c38 Merge pull request #2846 from sbwalker/dev
clarify scroll method name
2023-05-26 07:41:59 -04:00
6f0a6c7f69 clarify scroll method name 2023-05-26 07:41:49 -04:00
549d71073e Merge pull request #2845 from sbwalker/dev
add module base class method for ScrollToTop
2023-05-26 07:39:22 -04:00
4ad5522f9e add module base class method for ScrollToTop 2023-05-26 07:39:06 -04:00
2145c1a48c Merge pull request #2844 from sbwalker/dev
utilize new Resources capability in default module/theme
2023-05-25 17:14:14 -04:00
0f093b1238 utilize new Resources capability in default module/theme 2023-05-25 17:14:00 -04:00
1aa3fd7056 Merge pull request #2843 from sbwalker/dev
added validation support for user profile fields
2023-05-25 16:16:28 -04:00
cc4c47c3ee added validation support for user profile fields 2023-05-25 16:16:16 -04:00
6e3f3fac9d Merge pull request #2842 from sbwalker/dev
added ability to disable SMTP and set IsBodyHtml by default
2023-05-25 15:19:20 -04:00
261adefbc7 added ability to disable SMTP and set IsBodyHtml by default 2023-05-25 15:19:05 -04:00
7eafcfd7ad Merge pull request #2841 from sbwalker/dev
add support for Job and Theme settings in API
2023-05-25 15:00:44 -04:00
6183d6a22e add support for Job and Theme settings in API 2023-05-25 15:00:30 -04:00
51d42692ba Merge pull request #2840 from sbwalker/dev
fix site provisioning issue for host module definitions
2023-05-25 14:56:04 -04:00
18a9c059f4 fix site provisioning issue for host module definitions 2023-05-25 14:55:46 -04:00
50a13ffd6d Update README.md 2023-05-25 13:02:03 -04:00
b360944742 Merge pull request #2839 from sbwalker/dev
add ability to modify Theme Name
2023-05-25 12:57:02 -04:00
59d1a47846 add ability to modify Theme Name 2023-05-25 12:56:49 -04:00
22969d8daa Merge pull request #2838 from sbwalker/dev
optimize client assembly download service, add support for site level scripts
2023-05-25 12:32:39 -04:00
95ba87945b optimize client assembly download service, add support for site level scripts 2023-05-25 12:32:21 -04:00
63aeee8b22 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-25 13:14:52 +02:00
da11a86f99 Merge pull request #2837 from sbwalker/dev
ability to specify if a theme is enabled for a site
2023-05-24 13:09:28 -04:00
98c2f012ee ability to specify if a theme is enabled for a site 2023-05-24 13:09:10 -04:00
386c6144f8 Merge pull request #2835 from sbwalker/dev
use SiteKey as a cache key for multi-tenancy
2023-05-24 10:29:57 -04:00
666f9c2db9 use SiteKey as a cache key for multi-tenancy 2023-05-24 10:29:45 -04:00
f7c49588fb Merge pull request #2834 from sbwalker/dev
ability to specify if a module definition is enabled for a site
2023-05-24 09:40:20 -04:00
c0e6f06a5c ability to specify if a module definition is enabled for a site 2023-05-24 09:40:05 -04:00
6668c8ff66 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-23 17:19:25 +02:00
31907f2d16 Merge pull request #2833 from sbwalker/dev
add null check
2023-05-23 11:16:21 -04:00
452d0af8c9 add null check 2023-05-23 11:16:07 -04:00
c5a28f3601 Merge remote-tracking branch 'origin/dev' into dev 2023-05-23 15:55:00 +02:00
d13f26dc7e Merge pull request #2832 from sbwalker/dev
format head content, remove scripts, and filter duplicate elements
2023-05-23 09:12:21 -04:00
03374483e4 format head content, remove scripts, and filter duplicate elements 2023-05-23 09:12:03 -04:00
b1c0a865a0 Merge pull request #2831 from sbwalker/dev
utilize ResourceLocation consistently
2023-05-23 08:08:31 -04:00
7b0799a6f6 utilize ResourceLocation consistently 2023-05-23 08:08:16 -04:00
0de2976933 Merge pull request #2830 from sbwalker/dev
add support for body content
2023-05-22 15:02:49 -04:00
20c7bf3c48 add support for body content 2023-05-22 15:02:36 -04:00
55e090ba2a Merge pull request #2829 from sbwalker/dev
changes to support page level scripts, ability to detect prerendering
2023-05-22 13:57:06 -04:00
ded326c822 changes to support page level scripts, ability to detect prerendering 2023-05-22 13:56:48 -04:00
1ee9c2cf0e Merge pull request #2828 from sbwalker/dev
ability to specify Resources in IModule and ITheme interfaces,, fixed module settings error for personalized pages
2023-05-19 18:08:40 -04:00
e41d9008b3 ability to specify Resources in IModule and ITheme interfaces,, fixed module settings for personalized pages 2023-05-19 18:08:15 -04:00
1ec24251a2 Merge pull request #2827 from sbwalker/dev
remove unused local reference to ThemeType
2023-05-19 11:59:06 -04:00
2be48c3847 remove unused local reference to ThemeType 2023-05-19 11:58:52 -04:00
2137b79f6d Merge pull request #2824 from sbwalker/dev
optimize JavaScript handling
2023-05-18 14:36:19 -04:00
0b8086bd36 optimize JavaScript handling 2023-05-18 14:36:06 -04:00
d16659890c Merge pull request #2823 from sbwalker/dev
add support for type attribute in JSInterop IncludeScript
2023-05-18 09:36:23 -04:00
076d150f72 add support for type attribute in JSInterop IncludeScript 2023-05-18 09:36:09 -04:00
62deffd017 Merge pull request #2822 from sbwalker/dev
move PWA elements back to _Host
2023-05-18 08:37:36 -04:00
f1ec70ff14 move PWA elements back to _Host 2023-05-18 08:37:21 -04:00
af27c763e0 Merge pull request #2821 from sbwalker/dev
handle id attribute automatically for headcontent inline scripts
2023-05-18 08:27:07 -04:00
7336417634 handle id attribute automatically for headcontent inline scripts 2023-05-18 08:26:51 -04:00
cde46c0889 Merge pull request #2820 from sbwalker/dev
allow HeadContent to support script tags
2023-05-17 17:13:25 -04:00
5da4dadc31 allow HeadContent to support script tags 2023-05-17 17:13:08 -04:00
e32ca089ec Merge pull request #2817 from sbwalker/dev
added HeadContent property to Site and replaced Meta property on Page with HeadContent property.
2023-05-16 16:23:34 -04:00
8d2f644177 added HeadContent property to Site and replaced Meta property on Page with HeadContent property. 2023-05-16 16:23:07 -04:00
a003010be5 Merge pull request #2815 from sbwalker/dev
migrate CSS references and remove JS Interop methods
2023-05-16 09:09:32 -04:00
89ada83012 migrate CSS references and remove JS Interop methods 2023-05-16 09:09:18 -04:00
65fbf6926c Merge pull request #2814 from sbwalker/dev
migrate PWA script injection
2023-05-16 08:01:00 -04:00
5e652364c9 migrate PWA script injection 2023-05-16 08:00:48 -04:00
8b7c3d6cfa Merge pull request #2813 from sbwalker/dev
relocate favicon rendering
2023-05-16 07:42:06 -04:00
3b214a0105 relocate favicon rendering 2023-05-16 07:41:50 -04:00
c0c4073bc4 Merge pull request #2811 from sbwalker/dev
ability to add arbitrary content to head and body during client and server rendering
2023-05-15 16:43:40 -04:00
dbe7324c7f ability to add arbitrary content to head and body during client and server rendering 2023-05-15 16:43:22 -04:00
9316255e87 Merge pull request #2809 from sbwalker/dev
added AboutAssets.txt to Maui project and changed assembly references to Oqtane 4.0.0
2023-05-15 13:21:59 -04:00
7f7dff7019 added AboutAssets.txt to Maui project and changed assembly references to Oqtane 4.0.0 2023-05-15 13:21:30 -04:00
961e004a49 Merge pull request #2808 from sbwalker/dev
remove unneccesary package reference to Microsoft.AspNetCore.Mvc.ViewFeatures (not used and deprecated)
2023-05-15 12:58:43 -04:00
02c1e4fb65 remove unneccesary package reference to Microsoft.AspNetCore.Mvc.ViewFeatures (not used and deprecated) 2023-05-15 12:58:14 -04:00
bcbc2a6e95 Merge pull request #2807 from sbwalker/dev
initial changes to upgrade to .NET 7
2023-05-15 12:01:53 -04:00
0c749a126c initial changes to upgrade to .NET 7 2023-05-15 12:01:29 -04:00
3698ebad7c Merge pull request #2804 from sbwalker/dev
Modify the FilterModuleDefinition() method to return null if the object passed to the method is null instead of returning an initialized object
2023-05-11 17:11:02 -04:00
f59a5c90a5 Modify the FilterModuleDefinition() method to return null if the object passed to the method is null instead of returning an initialized object 2023-05-11 17:10:39 -04:00
36e4a69891 Merge pull request #2802 from chlupac/Backslash_fix
FileService backslash fix
2023-05-11 16:58:17 -04:00
19b560f7c1 Update README.md 2023-05-11 11:58:15 -04:00
0bec92e87e FileService backslash fix 2023-05-10 17:08:56 +02:00
a0d02f16cd Update README.md 2023-05-10 08:44:08 -04:00
4a0f655e1c Update README.md 2023-05-09 08:56:42 -04:00
91bf65292d Update README.md 2023-05-09 08:55:07 -04:00
a69d52d688 Merge pull request #2799 from sbwalker/dev
fix #2795 - remove protocol from alias names during add/update
2023-05-08 11:44:18 -04:00
79fe224cea fix #2795 - remove protocol from alias names during add/update 2023-05-08 11:43:59 -04:00
757d8e59a2 Merge pull request #2797 from thabaum/patch-25
Add missing settings excluded from Fix #2727
2023-05-08 07:29:38 -04:00
53ec1416b4 Add missing settings excluded from Fix #2727 2023-05-05 08:44:29 -07:00
e5add21612 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-04 09:15:58 +02:00
d1b52534de Update README.md 2023-05-03 15:46:38 -04:00
35edc053b5 Update README.md 2023-05-03 15:31:34 -04:00
2aa8d3be0f Update README.md 2023-05-03 15:20:55 -04:00
ec0a5377b3 Merge pull request #2784 from oqtane/master
Merge pull request #2783 from oqtane/dev
2023-05-03 14:59:27 -04:00
b876da069d Merge pull request #2783 from oqtane/dev
3.4.3 release
2023-05-03 14:59:09 -04:00
f06063d7eb Merge pull request #2782 from sbwalker/dev
fix #2763 - prevent module definitions from having duplicate names
2023-05-03 12:46:44 -04:00
c6ba4f4bee fix #2763 - prevent module definitions from having duplicate names 2023-05-03 12:46:29 -04:00
89da4ab2a1 Merge pull request #2781 from sbwalker/dev
add defensive logic in case list of pages is empty
2023-05-03 12:32:44 -04:00
99ac0a3cab add defensive logic in case list of pages is empty 2023-05-03 12:32:28 -04:00
a964c30705 Merge pull request #2776 from thabaum/patch-24
Updated Dashboard Index
2023-05-03 12:27:32 -04:00
2334d0297d Merge pull request #2780 from sbwalker/dev
fix #2777 - module rendering order within pane - moved default module ordering logic to server API for consistency and better performance
2023-05-03 12:26:17 -04:00
e444c6bcf0 fix #2777 - module rendering order within pane - moved default module ordering logic to server API for consistency and better performance 2023-05-03 12:25:52 -04:00
601582fc98 Update Index.razor 2023-05-03 08:19:24 -07:00
e939dbe24e Updated SecurityAccessLevel.Admin 2023-05-02 17:14:46 -07:00
143ad85fd5 Update README.md 2023-05-02 16:00:23 -04:00
20377e9789 Merge pull request #2775 from sbwalker/dev
prepare for 3.4.3 release
2023-05-02 15:54:49 -04:00
e4a24df7b4 prepare for 3.4.3 release 2023-05-02 15:54:36 -04:00
e88ca00658 Merge pull request #2770 from thabaum/patch-23
Remove Admin Page/Module Registered User View
2023-05-02 15:41:27 -04:00
2312c612d7 Merge pull request #2774 from sbwalker/dev
remove message items as these are handled by MessageType enum and UX styles
2023-05-02 15:14:34 -04:00
3aee52482d remove message items as these are handled by MessageType enum and UX styles 2023-05-02 15:14:17 -04:00
32248e0be6 Merge pull request #2745 from thabaum/patch-18
Add support for new language translation values for #2732.
2023-05-02 15:05:36 -04:00
6c18c320bd Merge pull request #2773 from sbwalker/dev
fixed compilation error and improved UTF8 support
2023-05-02 15:03:43 -04:00
e31f32e5aa fixed compilation error and improved UTF8 support 2023-05-02 15:03:26 -04:00
a856390566 Merge pull request #2772 from sbwalker/dev
improve module/theme/translation upload user experience to be consistent with download
2023-05-02 14:22:56 -04:00
64b8291487 improve module/theme/translation upload user experience to be consistent with download 2023-05-02 14:22:34 -04:00
2ebd1310c9 Merge pull request #2747 from thabaum/patch-19
Add content-type to sitemap: Fixes issues 2749 2764
2023-05-02 14:20:39 -04:00
09118fcb42 Updates lastmod date 2023-05-02 07:15:22 -07:00
5d5167abbc Merge pull request #2771 from vnetonline/dev
Invalid template when I try and use the Deploy to Azure button
2023-05-02 09:01:41 -04:00
bf43dea060 Merge branch 'oqtane:dev' into dev 2023-05-02 15:53:56 +10:00
6ebab830c5 Invalid template when I try and use the Deploy to Azure button
1. removed depends on connection strings
2. Moved sku outside properties for type Microsoft.Web/serverfarms and added various app plans
3. Removed worker size and replaced with instance capacity
4. Replaced database and sqlserver to use various editions
5. Updated ApiVersions for various types
2023-05-02 15:47:31 +10:00
9a9a78e0bd Remove Admin Page/Module Registered User View 2023-05-01 20:01:49 -07:00
418eff7d21 Merge pull request #2754 from pepsinio/dev
Add ability to use environment variables in order to set them as app settings in Azure
2023-05-01 16:14:47 -04:00
d39869ca8e Merge pull request #2759 from leigh-pointer/MenuHorizontal#2757
Fix for MenuHorizontal #2757
2023-05-01 16:14:16 -04:00
7829168ea7 Merge pull request #2768 from sbwalker/dev
fix #2761 - updating Module Definition name, description, category not invalidating cache
2023-05-01 15:38:46 -04:00
dd83e3ee67 fix #2761 - updating Module Definition name, description, category not invalidating cache 2023-05-01 15:38:18 -04:00
c3ac0e365d Removed PR comments 2023-04-27 16:32:48 -07:00
2986625605 Update content type to XML and include UTF-8 2023-04-27 16:22:35 -07:00
8beaeabf09 Include utf-8 encoding. 2023-04-27 16:21:14 -07:00
fa9b4b6112 Fix for MenuHorizontal #2757
Add the css class to MenuHorizontal to handle scrolling when hamburger Menu is in use.
2023-04-26 09:43:11 +02:00
a20c2aa996 Merge remote-tracking branch 'oqtane/dev' into dev 2023-04-26 09:29:31 +02:00
d81fbe4585 Fixed missing logic from PR 2023-04-19 10:18:27 -07:00
536c044139 Add environment settings needed for Azure deployment 2023-04-19 19:18:19 +02:00
376531195e Add environment settings needed for Azure deployment 2023-04-19 19:17:33 +02:00
abf4ff71d7 re-add missing settings 2023-04-19 10:13:31 -07:00
d25debcea3 cleanup using 2023-04-19 10:11:16 -07:00
948c186cb5 fixed formatting 2023-04-19 10:09:00 -07:00
ba27e70fe3 Removed unnecessary cache comments. 2023-04-19 10:05:43 -07:00
c93d2576af Updates content-type to "application/xml"
removes sitemap cache from previous commits.
2023-04-19 10:00:33 -07:00
e0b0156640 allow module and theme dependencies setting to include .dll file extension, added testmode config setting for validating list of assemblies sent to client 2023-04-19 08:48:52 -07:00
a3ca6a3071 Merge pull request #2753 from sbwalker/dev
allow module and theme dependencies setting to include .dll file extension, added testmode config setting for validating list of assemblies sent to client
2023-04-19 08:46:12 -07:00
b20157450b Add caching and content-type 2023-04-14 18:08:39 -07:00
7e4f0923d7 Add support for new language translation values. 2023-04-13 07:42:04 -07:00
f8c1fde358 Merge remote-tracking branch 'oqtane/dev' into dev 2023-04-13 13:27:33 +02:00
e0c2b2982f improvements to #2736 to support scenarios where module is not explicitly assigned to a page 2023-04-11 13:01:34 -04:00
9a231c28af Merge pull request #2742 from sbwalker/dev
improvements to #2736 to support scenarios where module is not explicitly assigned to a page
2023-04-11 12:59:33 -04:00
94a02b7bf9 add filter to exclude orphaned permissions 2023-04-11 10:35:23 -04:00
22c8fb411a Merge pull request #2741 from sbwalker/dev
add filter to exclude orphaned permissions
2023-04-11 10:33:09 -04:00
cf46210ff8 Merge pull request #2725 from thabaum/patch-17
Fixes null reference permissions issue: Fixes #2724
2023-04-11 10:22:08 -04:00
f32d988297 Merge pull request #2740 from sbwalker/dev
Routes with Module ID and no Action can be displayed on any page regardless of whether a PageModule record exists (ie. Admin Dashboard)
2023-04-11 10:19:28 -04:00
7fe4577158 Routes with Module ID and no Action can be displayed on any page regardless of whether a PageModule record exists (ie. Admin Dashboard) 2023-04-11 10:21:37 -04:00
8985dcb4c0 fix #2736 - UI not loading correct module instance in scenarios where a module exists on multiple pages 2023-04-10 08:37:35 -04:00
d346444c51 Merge pull request #2739 from sbwalker/dev
fix #2736 - UI not loading correct module instance in scenarios where a module exists on multiple pages
2023-04-10 08:35:42 -04:00
113658f3f7 Merge remote-tracking branch 'oqtane/dev' into dev 2023-04-05 18:32:26 +02:00
ef27a0d6b0 Merge pull request #2727 from leigh-pointer/ModulePassSettings
Fix for #2718 SiteMap functionality missing Settings
2023-04-05 10:53:09 -04:00
627158cb5d Merge pull request #2729 from leigh-pointer/PwdControlCancelFocus
Fix for #2728 Toggle Password button focus
2023-04-05 10:51:27 -04:00
648edcabba Merge pull request #2731 from sbwalker/dev
fix #2720 - module definition permissions not being created properly for new sites
2023-04-05 10:27:44 -04:00
0f34c6efc5 fix #2720 - module definition permissions not being created properly for new sites 2023-04-05 10:29:51 -04:00
cc3cc55269 consolidated package installation so that it always occurs during startup and added logging in case of errors 2023-04-05 10:26:21 -04:00
a503a9d5bc Merge pull request #2730 from sbwalker/dev
consolidated package installation so that it always occurs during startup and added logging in case of errors
2023-04-05 10:24:15 -04:00
789baf99ff Fix for #2728 Toggle Password button focus
Added  tabindex="-1" to the Button control so that focus is not received and passed the the following control.
2023-04-05 07:51:32 +02:00
036279a54c Fix for #2718 SiteMap functionality missing Settings
Added the Module Settings to the Module parameter passed in the GetUrls interface call,
2023-04-05 07:31:57 +02:00
481f18cf1c Fixes null reference permissions issue 2023-04-04 11:19:25 -07:00
c54a48865b Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-31 12:42:51 +02:00
2f1e386554 Update README.md 2023-03-29 14:13:27 -04:00
4c4e94dba0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-29 08:38:41 +02:00
ec142cbbe1 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-24 10:42:07 +01:00
702ff964fe Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-14 16:52:08 +01:00
919439977c Merge remote-tracking branch 'origin/dev' into dev 2023-03-14 09:44:04 +01:00
a6478c5146 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-13 08:11:31 +01:00
524c4ab5a1 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-12 11:04:23 +01:00
3c795eec7c Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-11 22:41:21 +01:00
b9e352cbcb Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-10 16:42:44 +01:00
e23744a0d0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-09 19:04:26 +01:00
626528fa5e Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-08 19:08:55 +01:00
274cde1c21 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-06 18:55:42 +01:00
4c8cb31a6f Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-06 05:54:29 +01:00
bea89be67b Merge remote-tracking branch 'origin/dev' into dev 2023-03-04 08:07:48 +01:00
4cb17de82a Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-02 06:40:17 +01:00
da3a4d5855 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-01 10:34:47 +01:00
b0c5d1e991 Merge remote-tracking branch 'oqtane/dev' into dev 2023-02-24 10:33:52 +01:00
231fb80c2c Merge remote-tracking branch 'oqtane/dev' into dev 2023-02-20 16:14:42 +01:00
c49af06e57 Merge remote-tracking branch 'oqtane/dev' into dev 2023-02-16 12:05:02 +01:00
79fc03bb0f Merge remote-tracking branch 'oqtane/dev' into dev 2023-01-04 21:27:31 +01:00
422 changed files with 15338 additions and 8354 deletions

2
.gitattributes vendored Normal file
View File

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

2
.gitignore vendored
View File

@ -16,7 +16,7 @@ _ReSharper.Caches
Oqtane.Server/appsettings.json
Oqtane.Server/Data
/Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml
Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml
Oqtane.Server/Content
Oqtane.Server/Packages
Oqtane.Server/wwwroot/Content

View File

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

View File

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

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

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

View File

@ -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
@implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
@ -28,7 +29,10 @@
<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>
<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>
@ -38,6 +42,13 @@
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
private string _uid = 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()
{
@ -55,4 +66,18 @@
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
@implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<PostgreSQLConfig> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
@ -40,7 +41,10 @@
<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>
<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>
}
@ -52,6 +56,13 @@
private string _security = "integrated";
private string _uid = 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()
{
@ -80,4 +91,18 @@
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">
<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">
<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>
}
@ -67,9 +70,16 @@
private string _security = "integrated";
private string _uid = String.Empty;
private string _pwd = String.Empty;
private string _passwordType = "password";
private string _togglePassword = string.Empty;
private string _encryption = "false";
private string _trustservercertificate = "false";
protected override void OnInitialized()
{
_togglePassword = SharedLocalizer["ShowPassword"];
}
public string GetConnectionString()
{
var connectionString = String.Empty;
@ -92,4 +102,18 @@
return connectionString;
}
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -5,15 +5,17 @@
@inject ISiteService SiteService
@inject IUserService UserService
@inject IDatabaseService DatabaseService
@inject ISiteTemplateService SiteTemplateService
@inject IJSRuntime JSRuntime
@inject IStringLocalizer<Installer> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject SiteState SiteState
<div class="container">
<div class="row">
<div class="mx-auto text-center">
<img src="oqtane-black.png" />
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version</div>
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 8)</div>
</div>
</div>
<hr class="app-rule" />
@ -61,13 +63,13 @@
</div>
</div>
}
</div>
</div>
</div>
<div class="col text-center">
<h2>@Localizer["ApplicationAdmin"]</h2><br />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user accountt" ResourceKey="Username">Username:</Label>
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user account" ResourceKey="Username">Username:</Label>
<div class="col-sm-9">
<input id="username" type="text" class="form-control" @bind="@_hostUsername" />
</div>
@ -77,7 +79,7 @@
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglePassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div>
</div>
@ -86,7 +88,7 @@
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword">@_toggleConfirmPassword</button>
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
</div>
</div>
</div>
@ -96,6 +98,20 @@
<input type="text" class="form-control" @bind="@_hostEmail" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="template" HelpText="Select a site template" ResourceKey="Template">Template:</Label>
<div class="col-sm-9">
@if (_templates != null)
{
<select id="template" class="form-select" @bind="@_template" required>
@foreach (var template in _templates)
{
<option value="@template.TypeName">@template.Name</option>
}
</select>
}
</div>
</div>
</div>
</div>
</div>
@ -103,7 +119,13 @@
<div class="row">
<div class="mx-auto text-center">
<button type="button" class="btn btn-success" @onclick="Install">@Localizer["InstallNow"]</button><br /><br />
<ModuleMessage Message="@_message" Type="MessageType.Error"></ModuleMessage>
@if (!string.IsNullOrEmpty(_message))
{
<div class="alert alert-danger alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message)
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
</div>
}
</div>
<div class="app-progress-indicator" style="@_loadingDisplay"></div>
</div>
@ -115,29 +137,35 @@
</div>
@code {
private List<Database> _databases;
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private List<Database> _databases;
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty;
private string _passwordType = "password";
private string _confirmPasswordType = "password";
private string _togglePassword = string.Empty;
private string _toggleConfirmPassword = string.Empty;
private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty;
private bool _register = true;
private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty;
private string _passwordType = "password";
private string _confirmPasswordType = "password";
private string _togglePassword = string.Empty;
private string _toggleConfirmPassword = string.Empty;
private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty;
private List<SiteTemplate> _templates;
private string _template = Constants.DefaultSiteTemplate;
private bool _register = true;
private string _message = string.Empty;
private string _loadingDisplay = "display: none;";
protected override async Task OnInitializedAsync()
{
_togglePassword = SharedLocalizer["ShowPassword"];
// include CSS
var content = "<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css\" integrity=\"sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==\" crossorigin=\"anonymous\" type=\"text/css\"/>";
SiteState.AppendHeadContent(content);
_togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync();
@ -150,7 +178,9 @@
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
}
_templates = await SiteTemplateService.GetSiteTemplatesAsync();
}
private void DatabaseChanged(ChangeEventArgs eventArgs)
{
@ -185,9 +215,9 @@
{
if (firstRender)
{
// include JavaScript
var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css", "text/css", "sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q==", "anonymous", "");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", "anonymous", "", "head");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", "anonymous", "", "head");
}
}
@ -229,7 +259,8 @@
TenantName = TenantNames.Master,
IsNewTenant = true,
SiteName = Constants.DefaultSite,
Register = _register
Register = _register,
SiteTemplate = _template
};
var installation = await InstallationService.Install(config);
@ -291,4 +322,9 @@
_showConnectionString = !_showConnectionString;
}
private void DismissModal()
{
_message = "";
StateHasChanged();
}
}

View File

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

View File

@ -4,6 +4,7 @@
@inject NavigationManager NavigationManager
@inject IFileService FileService
@inject IFolderService FolderService
@inject ISettingService SettingService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -80,6 +81,7 @@
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (_url == string.Empty || _folderId == -1)
@ -93,7 +95,7 @@
_name = _url.Substring(_url.LastIndexOf("/", StringComparison.Ordinal) + 1);
}
if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(_name).ToLower().Replace(".", "")))
if (!PageState.Site.UploadableFiles.Split(',').Contains(Path.GetExtension(_name).ToLower().Replace(".", "")))
{
AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning);
return;

View File

@ -48,7 +48,7 @@
</div>
</div>
<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">
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
</div>
@ -67,6 +67,7 @@
</div>
</div>
</form>
<br /><br />
@if (!_isSystem)
{
@ -79,8 +80,7 @@
@((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" />
}
<br />
<br />
<br /><br />
@if (PageState.QueryString.ContainsKey("id"))
{
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
@ -110,7 +110,7 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override string Title => "Folder Management";
public override string Title => "Folder Management";
protected override async Task OnInitializedAsync()
{

View File

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

View File

@ -31,7 +31,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
<div class="col-sm-9">
<input id="runs-every" class="form-control" @bind="@_interval" maxlength="4" required />
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required />
<select id="runs-every" class="form-select" @bind="@_frequency" required>
<option value="m">@Localizer["Minute(s)"]</option>
<option value="H">@Localizer["Hour(s)"]</option>
@ -45,7 +45,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retentionHistory" maxlength="4" required />
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="4" required />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -56,7 +56,7 @@
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
</div>
<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>
@ -69,7 +69,7 @@
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
</div>
<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>
@ -82,7 +82,7 @@
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
</div>
<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>
@ -97,22 +97,22 @@
</form>
@code {
private ElementReference form;
private bool validated = false;
private int _jobId;
private string _name = string.Empty;
private string _jobType = string.Empty;
private string _isEnabled = "True";
private string _interval = string.Empty;
private string _frequency = string.Empty;
private DateTime? _startDate = null;
private string _startTime = string.Empty;
private DateTime? _endDate = null;
private string _endTime = string.Empty;
private string _retentionHistory = string.Empty;
private DateTime? _nextDate = null;
private string _nextTime = string.Empty;
private string createdby;
private ElementReference form;
private bool validated = false;
private int _jobId;
private string _name = string.Empty;
private string _jobType = string.Empty;
private string _isEnabled = "True";
private string _interval = string.Empty;
private string _frequency = string.Empty;
private DateTime? _startDate = null;
private DateTime? _startTime = null;
private DateTime? _endDate = null;
private DateTime? _endTime = null;
private string _retentionHistory = string.Empty;
private DateTime? _nextDate = null;
private DateTime? _nextTime = null;
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
@ -132,11 +132,14 @@
_isEnabled = job.IsEnabled.ToString();
_interval = job.Interval.ToString();
_frequency = job.Frequency;
(_startDate, _startTime) = Utilities.UtcAsLocalDateAndTime(job.StartDate);
(_endDate, _endTime) = Utilities.UtcAsLocalDateAndTime(job.EndDate);
_retentionHistory = job.RetentionHistory.ToString();
(_nextDate, _nextTime) = Utilities.UtcAsLocalDateAndTime(job.NextExecution);
createdby = job.CreatedBy;
_startDate = Utilities.UtcAsLocalDate(job.StartDate);
_startTime = Utilities.UtcAsLocalDateTime(job.StartDate);
_endDate = Utilities.UtcAsLocalDate(job.EndDate);
_endTime = Utilities.UtcAsLocalDateTime(job.EndDate);
_retentionHistory = job.RetentionHistory.ToString();
_nextDate = Utilities.UtcAsLocalDate(job.NextExecution);
_nextTime = Utilities.UtcAsLocalDateTime(job.NextExecution);
createdby = job.CreatedBy;
createdon = job.CreatedOn;
modifiedby = job.ModifiedBy;
modifiedon = job.ModifiedOn;
@ -151,6 +154,11 @@
private async Task SaveJob()
{
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate))
{
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
return;
}
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))

View File

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

View File

@ -16,7 +16,7 @@
else
{
<TabStrip>
<TabPanel Name="Manage" ResourceKey="Manage">
<TabPanel Name="Manage" ResourceKey="Manage" Heading="Manage">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
@ -45,16 +45,15 @@ else
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host" Heading="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Translation: </Label>
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install." ResourceKey="LanguageUpload">Translation: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
</TabStrip>
@ -119,24 +118,11 @@ else
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task SetCultureAsync(string culture)
{
@ -145,8 +131,11 @@ else
var interop = new Interop(JSRuntime);
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -0,0 +1,110 @@
@namespace Oqtane.Modules.Admin.Languages
@inherits ModuleBase
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@inject NavigationManager NavigationManager
@inject ILocalizationService LocalizationService
@inject ILanguageService LanguageService
@inject IPackageService PackageService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_code == null)
{
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<TabStrip>
<TabPanel Name="Manage" ResourceKey="Manage" Heading="Manage">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
<div class="col-sm-9">
<input id="code" class="form-control" @bind="@_code" readonly/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
<div class="col-sm-9">
<select id="default" class="form-select" @bind="@_default" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
</TabPanel>
</TabStrip>
}
@code {
private ElementReference form;
private bool validated = false;
private int _languageId = -1;
private string _code = string.Empty;
private string _cultureName = string.Empty;
private string _default = "False";
private List<Language> _languages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
{
_languageId = Int32.Parse(PageState.QueryString["id"]);
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
Language language = _languages.Where(x => x.LanguageId == _languageId).FirstOrDefault();
if (language != null)
{
_code = language.Code;
_cultureName = language.Name;
_default = language.IsDefault.ToString();
if (language.SiteId == null)
{
language.SiteId = PageState.Site.SiteId;
}
};
}
private async Task SaveLanguage()
{
Language language = _languages.Where(x => x.LanguageId == _languageId).FirstOrDefault();
if (language != null)
{
language.IsDefault = Boolean.Parse(_default);
try
{
await LanguageService.EditLanguageAsync(language);
if (language.IsDefault)
{
await SetCultureAsync(language.Code);
}
await logger.LogInformation("Language Edited {Language}", language);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Editing Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error.Language.Edit"], MessageType.Error);
}
};
}
private async Task SetCultureAsync(string culture)
{
if (culture != CultureInfo.CurrentUICulture.Name)
{
var interop = new Interop(JSRuntime);
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
}
}
}

View File

@ -14,26 +14,33 @@ else
{
<ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" />
<Pager Items="@_languages">
<Pager Items="@_languages" SearchProperties="Name,Code">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th>
<th>@Localizer["Default"]</th>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<th style="width: 1px;">@Localizer["Translation"]</th>
<th style="width: 1px;">@Localizer["Translation"]</th>
<th style="width: 1px;">&nbsp;</th>
}
</Header>
<Row>
<td>
@if (!context.IsDefault)
{
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.LanguageId.ToString())" ResourceKey="EditLanguage" />
}
</td>
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
<td>@context.Name</td>
<td>@context.Code</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td>
@{
var translation = TranslationAvailable(context.Code, context.Version);
@ -56,10 +63,6 @@ else
}
</Row>
</Pager>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
}
@if (_package != null)
@ -103,57 +106,64 @@ else
}
@code {
private List<Language> _languages;
private List<Package> _packages;
private Package _package;
private bool _install = false;
private List<Language> _languages;
private List<Package> _packages;
private Package _package;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
await GetLanguages();
protected override async Task OnParametersSetAsync()
{
await GetLanguages();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
}
}
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
}
}
private async Task GetLanguages()
{
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
}
private async Task GetLanguages()
{
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
}
private async Task DeleteLanguage(Language language)
{
try
{
await LanguageService.DeleteLanguageAsync(language.LanguageId);
await logger.LogInformation("Language Deleted {Language}", language);
await GetLanguages();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message);
private async Task DeleteLanguage(Language language)
{
try
{
await LanguageService.DeleteLanguageAsync(language.LanguageId);
await logger.LogInformation("Language Deleted {Language}", language);
await GetLanguages();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error.Language.Delete"], MessageType.Error);
}
}
AddModuleMessage(Localizer["Error.Language.Delete"], MessageType.Error);
}
}
private Package TranslationAvailable(string code, string version)
{
return _packages?.FirstOrDefault(item => item.PackageId == (Constants.PackageId + "." + code));
}
private Package TranslationAvailable(string code, string version)
{
return _packages?.FirstOrDefault(item => item.PackageId == (Constants.PackageId + "." + code));
}
private async Task GetPackage(string code, string version)
{
try
{
_package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, version);
StateHasChanged();
}
private async Task GetPackage(string code, string version)
{
try
{
_package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, version, false);
if (_package != null)
{
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
@ -165,11 +175,10 @@ else
{
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);
AddModuleMessage(Localizer["Success.Language.Download"], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
_package = null;
_install = true;
StateHasChanged();
}
catch (Exception ex)
@ -184,19 +193,4 @@ else
_package = null;
StateHasChanged();
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
}

View File

@ -3,6 +3,7 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject ISettingService SettingService
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -11,9 +12,6 @@
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<div>@Localizer["Info.SignedIn"]</div>
</Authorized>
<NotAuthorized>
@if (!twofactor)
{
@ -34,14 +32,17 @@
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
<div class="input-group">
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
<div class="form-group mt-2">
<div class="form-check">
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
</div>
@if (!_alwaysremember)
{
<div class="form-check">
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
</div>
}
</div>
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
@ -69,259 +70,260 @@
</AuthorizeView>
@code {
private bool _allowsitelogin = true;
private bool _allowexternallogin = false;
private ElementReference login;
private bool validated = false;
private bool twofactor = false;
private string _username = string.Empty;
private ElementReference username;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private bool _remember = false;
private string _code = string.Empty;
private bool _allowsitelogin = true;
private bool _allowexternallogin = false;
private ElementReference login;
private bool validated = false;
private bool twofactor = false;
private string _username = string.Empty;
private ElementReference username;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private bool _remember = false;
private bool _alwaysremember = false;
private string _code = string.Empty;
private string _returnUrl = string.Empty;
private string _returnUrl = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override List<Resource> Resources => new List<Resource>()
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
protected override async Task OnInitializedAsync()
{
try
{
_togglepassword = SharedLocalizer["ShowPassword"];
protected override async Task OnInitializedAsync()
{
try
{
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
{
_allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]);
}
_togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))
{
_allowexternallogin = true;
}
if (PageState.QueryString.ContainsKey("returnurl"))
{
_returnUrl = PageState.QueryString["returnurl"];
}
if (PageState.QueryString.ContainsKey("returnurl"))
{
_returnUrl = PageState.QueryString["returnurl"];
}
if (PageState.QueryString.ContainsKey("name"))
{
_username = PageState.QueryString["name"];
}
if (PageState.QueryString.ContainsKey("name"))
{
_username = PageState.QueryString["name"];
}
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
{
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
{
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
if (PageState.QueryString.ContainsKey("key"))
{
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);
}
_username = "";
}
else
{
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);
}
}
}
else
{
if (PageState.QueryString.ContainsKey("status"))
{
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error);
}
}
if (PageState.QueryString.ContainsKey("key"))
{
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);
}
_username = "";
}
else
{
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);
}
}
}
else
{
if (PageState.QueryString.ContainsKey("status"))
{
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error);
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && PageState.User == null && _allowsitelogin)
{
await username.FocusAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && PageState.User == null)
{
await username.FocusAsync();
}
}
// redirect logged in user to specified page
if (PageState.User != null)
{
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
}
private async Task Login()
{
try
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (!twofactor)
{
user = await UserService.LoginUserAsync(user, hybrid, _remember);
}
else
{
user = await UserService.VerifyTwoFactorAsync(user, _code);
}
private async Task Login()
{
try
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (user.IsAuthenticated)
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
if (!twofactor)
{
_remember = _alwaysremember || _remember;
user = await UserService.LoginUserAsync(user, hybrid, _remember);
}
else
{
user = await UserService.VerifyTwoFactorAsync(user, _code);
}
if (hybrid)
{
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider
.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
}
else
{
// post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields);
}
}
else
{
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired)
{
twofactor = true;
validated = false;
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
}
else
{
if (!twofactor)
{
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
}
else
{
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
}
}
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Performing Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Login"], MessageType.Error);
}
}
if (user.IsAuthenticated)
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
private void Cancel()
{
NavigationManager.NavigateTo(_returnUrl);
}
if (hybrid)
{
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
}
else
{
// post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields);
}
}
else
{
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired)
{
twofactor = true;
validated = false;
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
}
else
{
if (!twofactor)
{
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
}
else
{
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
}
}
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Performing Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Login"], MessageType.Error);
}
}
private async Task Forgot()
{
try
{
if (_username != string.Empty)
{
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
}
private void Cancel()
{
NavigationManager.NavigateTo(_returnUrl);
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
}
}
private async Task Forgot()
{
try
{
if (_username != string.Empty)
{
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
}
private void Reset()
{
twofactor = false;
_username = "";
_password = "";
ClearModuleMessage();
StateHasChanged();
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
}
}
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await Login();
}
}
private void Reset()
{
twofactor = false;
_username = "";
_password = "";
ClearModuleMessage();
StateHasChanged();
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await Login();
}
}
private void ExternalLogin()
{
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
private void ExternalLogin()
{
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true);
}
}
}

View File

@ -37,7 +37,7 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
<Label Class="col-sm-3" For="category" HelpText="The fully qualified type type that was affected" ResourceKey="Category">Type Name: </Label>
<div class="col-sm-9">
<input id="category" class="form-control" @bind="@_category" readonly />
</div>

View File

@ -81,7 +81,7 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of events to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retention" />
<input id="retention" type="number" min="0" step="1" class="form-control" @bind="@_retention" />
</div>
</div>
</div>
@ -97,7 +97,7 @@ else
private string _rows = "10";
private int _page = 1;
private List<Log> _logs;
private string _retention = "";
private int _retention = 30;
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -126,7 +126,7 @@ else
await GetLogs();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_retention = SettingService.GetSetting(settings, "LogRetention", "30");
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
}
catch (Exception ex)
{
@ -218,7 +218,7 @@ else
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "LogRetention", _retention, true);
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);

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;
}
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,70 +8,126 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
<TabPanel Name="Download" ResourceKey="Download" Heading="Download">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<div class="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>
@if (_packages != null)
{
if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group">
<span class="input-group-text">@Localizer["Product"]</span>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<button type="button" class="btn btn-primary ms-2" @onclick="Refresh"><span class="@Icons.Reload" aria-hidden="true"></span></button>
</div>
}
}
</div>
</div>
<div class="row mb-3">
<div class="col">
@if (_initialized)
{
<br />
<div class="row mb-3">
<div class="col-sm-4">
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
</div>
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-4">
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
@if (_price == "free")
{
<option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
}
<option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
@if (_price == "paid")
{
<option value="price">@SharedLocalizer["Search.Price"]</option>
}
</select>
</div>
</div>
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6" 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
{
<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>
}
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
</div>
</div>
</div>
</Row>
</Pager>
}
</div>
</div>
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>
</div>
</div>
@ -111,153 +167,149 @@
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
@code {
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _packageid = "";
private string _packagelicense = "";
private string _packageversion = "";
private bool _initialized = false;
private int _page = 1;
private List<Package> _packages;
private string _price = "free";
private string _sort = "popularity";
private string _search = "";
private string _productname = "";
private string _packageid = "";
private string _packagelicense = "";
private string _packageversion = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Package.Load"], MessageType.Error);
}
}
protected override async Task OnInitializedAsync()
{
try
{
await LoadModuleDefinitions();
_initialized = true;
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Package.Load"], MessageType.Error);
}
}
private async Task LoadModuleDefinitions()
{
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "");
private async Task LoadModuleDefinitions()
{
ShowProgressIndicator();
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
}
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadModuleDefinitions();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
private async Task Search()
{
try
{
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
HideProgressIndicator();
}
private async Task Reset()
{
try
{
_search = "";
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
}
private async void PriceChanged(string price)
{
_price = price;
_sort = "popularity";
await LoadModuleDefinitions();
StateHasChanged();
}
private void HideModal()
{
_productname = "";
_packagelicense = "";
StateHasChanged();
}
private async Task Search()
{
await LoadModuleDefinitions();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageversion = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task Reset()
{
_page = 1;
_search = "";
await LoadModuleDefinitions();
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _packageversion, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
AddModuleMessage(Localizer["Success.Module.Download"], MessageType.Success);
_productname = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task Refresh()
{
await LoadModuleDefinitions();
}
private async Task InstallModules()
{
try
{
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Modules");
}
}
private void OnPageChange(int page)
{
_page = page;
}
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadModuleDefinitions();
}
private void HideModal()
{
_productname = "";
_packagelicense = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version, false);
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageversion = package.Version;
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _packageversion);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
_productname = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -13,13 +13,13 @@
<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>
<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 or the word oqtane." 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>
<Label Class="col-sm-3" For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation or the word oqtane." ResourceKey="ModuleName">Module Name: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_module" required />
</div>
@ -89,7 +89,10 @@
protected override void OnInitialized()
{
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
if (!NavigationManager.BaseUri.Contains("localhost:"))
{
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
}
}
protected override async Task OnParametersSetAsync()
@ -115,10 +118,18 @@
{
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);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
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);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Require.ValidDescription"], MessageType.Warning);
}
}
else
{
@ -139,7 +150,13 @@
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_]*$");
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)
@ -151,7 +168,7 @@
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
}
GetLocation();
GetLocation();
}
private void GetLocation()
@ -160,8 +177,14 @@
if (_owner != "" && _module != "" && _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();
}

View File

@ -8,11 +8,13 @@
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IPageModuleService PageModuleService
@inject IModuleService ModuleService
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Definition" ResourceKey="Definition">
<TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
@ -33,7 +35,16 @@
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is module enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information">
<div class="container">
@ -50,9 +61,26 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label>
<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">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
@if (!string.IsNullOrEmpty(_packagename))
{
<div class="input-group">
<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 class="row mb-1 align-items-center">
@ -76,13 +104,14 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtimes" HelpText="The Blazor runtimes which this module supports" ResourceKey="Runtimes">Runtimes: </Label>
<div class="col-sm-9">
<input id="runtimes" class="form-control" @bind="@_runtimes" disabled />
@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>
}
</div>
</div>
</div>
@ -94,7 +123,7 @@
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
@ -104,10 +133,22 @@
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
<TabPanel Name="Translations" ResourceKey="Translations">
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
<Pager Items="@_pagesWithModules" RowClass="align-middle">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
<td>@(string.IsNullOrEmpty(context.Title) ? @context.Name : @context.Title )</td>
</Row>
</Pager>
</TabPanel>
<TabPanel Name="Translations" ResourceKey="Translations" Heading="Translations">
@if (_languages != null && _languages.Count > 0)
{
<Pager Items="@_languages">
<Pager Items="@_languages">
<Header>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th>
@ -131,10 +172,6 @@
</td>
</Row>
</Pager>
@if (_install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
}
else
{
@ -185,7 +222,7 @@
</p>
</div>
<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>
</div>
</div>
@ -196,200 +233,238 @@
}
@code {
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private int _moduleDefinitionId;
private string _name;
private string _description = "";
private string _categories;
private string _moduledefinitionname = "";
private string _version;
private string _packagename = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private string _runtimes = "";
private List<Permission> _permissions = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private int _moduleDefinitionId;
private string _name;
private string _description = "";
private string _categories;
private string _isenabled;
private string _moduledefinitionname = "";
private string _version;
private string _packagename = "";
private string _packageurl = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private List<Permission> _permissions = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private List<Page> _pagesWithModules;
#pragma warning disable 649
private PermissionGrid _permissionGrid;
private PermissionGrid _permissionGrid;
#pragma warning restore 649
private List<Package> _packages;
private List<Language> _languages;
private Package _package;
private bool _install = false;
private List<Package> _packages;
private List<Language> _languages;
private Package _package;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
{
try
{
_moduleDefinitionId = Int32.Parse(PageState.QueryString["id"]);
var moduleDefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduleDefinition != null)
{
_name = moduleDefinition.Name;
_description = moduleDefinition.Description;
_categories = moduleDefinition.Categories;
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_version = moduleDefinition.Version;
_packagename = moduleDefinition.PackageName;
_owner = moduleDefinition.Owner;
_url = moduleDefinition.Url;
_contact = moduleDefinition.Contact;
_license = moduleDefinition.License;
_runtimes = moduleDefinition.Runtimes;
_permissions = moduleDefinition.PermissionList;
_createdby = moduleDefinition.CreatedBy;
_createdon = moduleDefinition.CreatedOn;
_modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn;
protected override async Task OnInitializedAsync()
{
try
{
_moduleDefinitionId = Int32.Parse(PageState.QueryString["id"]);
var moduleDefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduleDefinition != null)
{
_name = moduleDefinition.Name;
_description = moduleDefinition.Description;
_categories = moduleDefinition.Categories;
_isenabled = moduleDefinition.IsEnabled.ToString();
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_version = moduleDefinition.Version;
_packagename = moduleDefinition.PackageName;
_owner = moduleDefinition.Owner;
_url = moduleDefinition.Url;
_contact = moduleDefinition.Contact;
_license = moduleDefinition.License;
_permissions = moduleDefinition.PermissionList;
_createdby = moduleDefinition.CreatedBy;
_createdon = moduleDefinition.CreatedOn;
_modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn;
if (!string.IsNullOrEmpty(_packagename))
{
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
foreach (var package in _packages)
{
var code = package.PackageId.Split('.').Last();
if (!_languages.Any(item => item.Code == code))
{
_languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true });
}
}
_languages = _languages.OrderBy(item => item.Name).ToList();
}
if (!string.IsNullOrEmpty(_packagename))
{
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
foreach (var package in _packages)
{
var code = package.PackageId.Split('.').Last();
if (!_languages.Any(item => item.Code == code))
{
_languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true });
}
}
_languages = _languages.OrderBy(item => item.Name).ToList();
}
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
// Group modules by PageId
// Get distinct PageIds where modules are present
var distinctPageIds = PageState.Modules
.Where(md => md.ModuleDefinition.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
.Select(md => md.PageId)
.Distinct();
private async Task SaveModuleDefinition()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduledefinition.Name != _name)
{
moduledefinition.Name = _name;
}
if (moduledefinition.Description != _description)
{
moduledefinition.Description = _description;
}
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.PermissionList = _permissionGrid.GetPermissionList();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
// Filter and retrieve the corresponding pages
_pagesWithModules = PageState.Pages
.Where(pg => distinctPageIds.Contains(pg.PageId) && pg.IsDeleted == false)
.ToList();
private void HideModal()
{
_package = null;
StateHasChanged();
}
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
private string TranslationAvailable(string packagename, string version)
{
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
if (string.IsNullOrEmpty(version))
{
return "install";
}
else
{
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
return "upgrade";
}
}
}
}
return "";
}
private async Task SaveModuleDefinition()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
var moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
if (!moduleDefinitions.Any(item => item.Name.ToLower() == _name.ToLower() && item.ModuleDefinitionId != _moduleDefinitionId))
{
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduledefinition.Name != _name)
{
moduledefinition.Name = _name;
}
if (moduledefinition.Description != _description)
{
moduledefinition.Description = _description;
}
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
moduledefinition.PermissionList = _permissionGrid.GetPermissionList();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.DuplicateName"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task GetPackage(string packagename)
{
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
try
{
_package = await PackageService.GetPackageAsync(packagename, version);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packagename, version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private void HideModal()
{
_package = null;
StateHasChanged();
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success);
_package = null;
_install = true;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private string TranslationAvailable(string packagename, string version)
{
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
if (string.IsNullOrEmpty(version))
{
return "install";
}
else
{
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
return "upgrade";
}
}
}
}
return "";
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
private async Task GetPackage(string packagename)
{
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
try
{
_package = await PackageService.GetPackageAsync(packagename, version, false);
if (_package != null)
{
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", packagename, version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packagename, version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task DownloadTranslation()
{
try
{
await PackageService.DownloadPackageAsync(_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);
_package = null;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
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);
}
}
private string Browse(Page page) => string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url;
}

View File

@ -37,13 +37,15 @@ else
</div>
</div>
<Pager Items="@_moduleDefinitions.Where(item => item.Categories.Contains(_category))">
<Pager Items="@_moduleDefinitions">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@Localizer["InUse"]</th>
<th>@SharedLocalizer["Support"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
@ -57,6 +59,16 @@ else
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
{
@ -67,6 +79,9 @@ else
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@((MarkupString)SupportLink(context.PackageName, context.Version))
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>
@ -84,52 +99,76 @@ else
}
@code {
private List<ModuleDefinition> _moduleDefinitions;
private List<Package> _packages;
private List<string> _categories = new List<string>();
private string _category = "Common";
private List<ModuleDefinition> _allModuleDefinitions;
private List<ModuleDefinition> _moduleDefinitions;
private List<Package> _packages;
private List<string> _categories = new List<string>();
private string _category = "Common";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnParametersSetAsync()
{
try
{
_moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module");
_categories = _moduleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
catch (Exception ex)
{
if (_moduleDefinitions == null)
{
await logger.LogError(ex, "Error Loading Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
}
protected override async Task OnParametersSetAsync()
{
try
{
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
await LoadModuleDefinitions();
}
catch (Exception ex)
{
if (_moduleDefinitions == null)
{
await logger.LogError(ex, "Error Loading Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
}
private string PurchaseLink(string packagename)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
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>";
}
}
private async Task LoadModuleDefinitions()
{
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
_packages = await PackageService.GetPackageUpdatesAsync("module");
}
private string PurchaseLink(string packagename)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{
if (string.IsNullOrEmpty(package.PaymentUrl))
{
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>";
}
}
}
}
return link;
}
private string SupportLink(string packagename, string version)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{
link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
}
}
return link;
}
private string UpgradeAvailable(string packagename, string version)
{
if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -147,9 +186,8 @@ else
{
try
{
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(packagename, version);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
@ -174,9 +212,9 @@ else
}
}
private void CategoryChanged(ChangeEventArgs e)
private async Task CategoryChanged(ChangeEventArgs e)
{
_category = (string)e.Value;
StateHasChanged();
await LoadModuleDefinitions();
}
}

View File

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

View File

@ -14,10 +14,27 @@
@if (_containers != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="module" HelpText="The name of the module" ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<input id="module" type="text" class="form-control" @bind="@_module" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" type="text" name="Title" class="form-control" @bind="@_title" required />
<input id="title" type="text" class="form-control" @bind="@_title" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<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">
@ -31,6 +48,18 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this module is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this module expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
<div class="col-sm-9">
@ -44,12 +73,20 @@
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
<div class="col-sm-9">
<select id="page" class="form-select" @bind="@_pageId" required>
@foreach (Page p in PageState.Pages)
@if (PageState.Page.UserId != null)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
}
else
{
foreach (Page p in PageState.Pages)
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
}
}
}
</select>
</div>
@ -89,111 +126,125 @@
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
</form>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
private ElementReference form;
private bool validated = false;
private List<Theme> _themes;
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _title;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private List<Permission> _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
private RenderFragment ContainerSettingsComponent { get; set; }
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private ElementReference form;
private bool validated = false;
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _module;
private string _title;
private string _pane;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private List<Permission> _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
private RenderFragment ContainerSettingsComponent { get; set; }
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private DateTime? _effectivedate = null;
private DateTime? _expirydate = null;
protected override void OnInitialized()
{
_module = ModuleState.ModuleDefinition.Name;
_title = ModuleState.Title;
_pane = ModuleState.Pane;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.PermissionList;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
_effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate);
_expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate);
protected override async Task OnInitializedAsync()
{
_title = ModuleState.Title;
_themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.PermissionList;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
if (ModuleState.ModuleDefinition != null)
{
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
if (ModuleState.ModuleDefinition != null)
{
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
}
else
{
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
}
if (_moduleSettingsType != null)
{
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
if (!string.IsNullOrEmpty(moduleobject.Title))
{
_moduleSettingsTitle = moduleobject.Title;
}
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
}
else
{
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
}
if (_moduleSettingsType != null)
{
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
if (!string.IsNullOrEmpty(moduleobject.Title))
{
_moduleSettingsTitle = moduleobject.Title;
}
ModuleSettingsComponent = builder =>
{
builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent();
};
}
}
else
{
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
}
ModuleSettingsComponent = builder =>
{
builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent();
};
}
}
else
{
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
}
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);
if (_containerSettingsType != null)
{
ContainerSettingsComponent = builder =>
{
builder.OpenComponent(0, _containerSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
builder.CloseComponent();
};
}
}
}
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);
if (_containerSettingsType != null)
{
ContainerSettingsComponent = builder =>
{
builder.OpenComponent(0, _containerSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
builder.CloseComponent();
};
}
}
}
private async Task SaveModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
private async Task SaveModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (!string.IsNullOrEmpty(_title))
{
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
if (!string.IsNullOrEmpty(_title))
{
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
{
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
return;
}
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.Pane = _pane;
pagemodule.EffectiveDate = Utilities.LocalDateAndTimeAsUtc(_effectivedate);
pagemodule.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
@ -241,5 +292,4 @@
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@ -3,15 +3,15 @@
@inject NavigationManager NavigationManager
@inject IPageService PageService
@inject IThemeService ThemeService
@inject ISystemService SystemService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null)
{
@if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
@ -19,42 +19,67 @@
<input id="name" class="form-control" @bind="@_name" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in PageState.Pages)
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in PageState.Pages)
{
<option value="@(page.PageId)">@(page.Name)</option>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
}
</select>
}
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
@if (_children != null && _children.Count > 0)
{
<option value="<<">@Localizer["AtBeginning"]</option>
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
{
<option value="@(page.PageId)">@(page.Name)</option>
}
</select>
}
</div>
</div>
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<option value="@(_parent.PageId)">@(new string('-', _parent.Level * 2))@(_parent.Name)</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value=">>">@Localizer["AtEnd"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<div class="col-sm-9">
@ -85,9 +110,39 @@
<input id="url" class="form-control" @bind="@_url" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-8">
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
</div>
<div class="col-sm-1">
<i class="@_icon"></i>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<Section Name="Appearance" ResourceKey="Appearance">
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
@ -95,12 +150,6 @@
<input id="title" class="form-control" @bind="@_title" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="meta" HelpText="Optionally enter meta tags (in exactly the form you want them to be included in the page output)." ResourceKey="Meta">Meta: </Label>
<div class="col-sm-9">
<textarea id="meta" class="form-control" @bind="@_meta" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
@ -116,7 +165,6 @@
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
@ -124,275 +172,312 @@
</select>
</div>
</div>
</div>
</Section>
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
<div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" />
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
</div>
</div>
</div>
</Section>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading=@Localizer["Permissions.Heading"]>
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
}
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name;
private string _title;
private string _meta;
private string _path = string.Empty;
private string _parentid = "-1";
private string _insert = ">>";
private List<Page> _children;
private int _childid = -1;
private string _isnavigation = "True";
private string _isclickable = "True";
private string _url;
private string _ispersonalizable = "False";
private string _themetype = string.Empty;
private string _containertype = string.Empty;
private string _icon = string.Empty;
private string _permissions = null;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private int _pageId;
private string _name;
private string _parentid = "-1";
private string _insert = ">>";
private List<Page> _children;
private int _childid = -1;
private string _isnavigation = "True";
private string _isclickable = "True";
private string _path = string.Empty;
private string _url;
private string _ispersonalizable = "False";
private string _title;
private string _icon = string.Empty;
private string _themetype = string.Empty;
private string _containertype = string.Empty;
private string _headcontent;
private string _bodycontent;
private string _permissions = null;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page _parent = null;
protected Dictionary<string, string> _icons;
private string _iconresources = "";
private DateTime? _effectivedate = null;
private DateTime? _expirydate = null;
protected override async Task OnInitializedAsync()
{
try
{
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = PageState.Site.DefaultThemeType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
ThemeSettings();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error);
}
}
protected override async Task OnInitializedAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id"))
{
_pageId = Int32.Parse(PageState.QueryString["id"]);
_parent = await PageService.GetPageAsync(_pageId);
if (_parent != null)
{
_parentid = _parent.PageId.ToString();
}
}
_icons = await SystemService.GetIconsAsync();
_iconresources = typeof(IconResources).FullName;
private async void ParentChanged(ChangeEventArgs e)
{
try
{
_parentid = (string)e.Value;
_children = new List<Page>();
if (_parentid == "-1")
{
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_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();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
}
}
// if admin or user has edit access to parent page
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
{
_themetype = PageState.Site.DefaultThemeType;
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_children = 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);
}
}
_effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate);
_expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate);
ThemeSettings();
_initialized = true;
}
else
{
await logger.LogWarning("Error Loading Page {ParentId}", _parentid);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = "-";
ThemeSettings();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error);
}
}
private async void ParentChanged(ChangeEventArgs e)
{
try
{
_parentid = (string)e.Value;
_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);
}
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
}
}
private void ThemeSettings()
{
_themeSettingsType = null;
var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
if (_themeSettingsType != null)
{
ThemeSettingsComponent = builder =>
{
builder.OpenComponent(0, _themeSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
builder.CloseComponent();
};
}
_refresh = true;
}
}
private void ThemeChanged(ChangeEventArgs e)
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _containers.First().TypeName;
ThemeSettings();
StateHasChanged();
private async Task SavePage()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
Page page = null;
try
{
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
{
page = new Page();
page.SiteId = PageState.Page.SiteId;
page.Name = _name;
page.Title = _title;
// if theme chosen is different than default site theme, display warning message to user
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
}
}
if (string.IsNullOrEmpty(_path))
{
_path = _name;
}
if (_path.Contains("/"))
{
if (_path.EndsWith("/") && _path != "/")
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
private void ThemeSettings()
{
_themeSettingsType = null;
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
if (_themeSettingsType != null)
{
ThemeSettingsComponent = builder =>
{
builder.OpenComponent(0, _themeSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
builder.CloseComponent();
};
}
_refresh = true;
}
}
if (_parentid == "-1")
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
page.ParentId = Int32.Parse(_parentid);
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault();
if (parent.Path == string.Empty)
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
}
private async Task SavePage()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
Page page = null;
try
{
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
{
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
return;
}
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
{
page = new Page();
page.SiteId = PageState.Page.SiteId;
page.Name = _name;
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (_pages.Any(item => item.Path == page.Path))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
return;
}
if (string.IsNullOrEmpty(_path))
{
_path = _name;
}
if (_path.Contains("/"))
{
if (_path.EndsWith("/") && _path != "/")
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
return;
}
if (_parentid == "-1")
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
page.ParentId = Int32.Parse(_parentid);
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault();
if (parent.Path == string.Empty)
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
}
Page child;
switch (_insert)
{
case "<<":
page.Order = 0;
break;
case "<":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order - 1;
break;
case ">":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order + 1;
break;
case ">>":
page.Order = int.MaxValue;
break;
}
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (_pages.Any(item => item.Path == page.Path))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
return;
}
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
return;
}
Page child;
switch (_insert)
{
case "<<":
page.Order = 0;
break;
case "<":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order - 1;
break;
case ">":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order + 1;
break;
case ">>":
page.Order = int.MaxValue;
break;
}
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url;
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.EffectiveDate = Utilities.LocalDateAndTimeAsUtc(_effectivedate);
page.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
page.UserId = null;
// appearance
page.Title = _title;
page.Icon = (_icon == null ? string.Empty : _icon);
page.ThemeType = _themetype;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
page.DefaultContainerType = _containertype;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = (_icon == null ? string.Empty : _icon);
// page content
page.HeadContent = _headcontent;
page.BodyContent = _bodycontent;
// permissions
page.PermissionList = _permissionGrid.GetPermissionList();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
page.Meta = _meta;
page = await PageService.AddPageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
await logger.LogInformation("Page Added {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
NavigationManager.NavigateTo(page.Path); // redirect to new page
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
}
}
else
@ -415,13 +500,17 @@
private void Cancel()
{
if (PageState.QueryString.ContainsKey("cp"))
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
else
{
NavigationManager.NavigateTo(NavigateUrl());
}
}
private void IconChanged(string NewIcon)
{
_icon = NewIcon;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,11 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.Pages != null)
@if (PageState.Pages != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)">
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)" SearchProperties="Name">
<Header>
<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">
<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">
<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 class="row mb-1 align-items-center">
@ -34,19 +34,25 @@
<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>
<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="9999" type="number" required />
</div>
</div>
<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>
<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 class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label>
<div class="col-sm-9">
<input id="defaultVal" class="form-control" @bind="@_defaultvalue" maxlength="2000"/>
<input id="defaultVal" class="form-control" @bind="@_defaultvalue" maxlength="2000" />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -64,6 +70,18 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="validation" HelpText="Optionally provide a regular expression (RegExp) for validating the value entered" ResourceKey="Validation">Validation: </Label>
<div class="col-sm-9">
<input id="validation" class="form-control" @bind="@_validation" maxlength="200" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="autocomplete" HelpText="The HTML autocomplete attribute allows you to specify browser behavior for automated user assistance in filling out form field values. Allowable values are blank (default), 'on', 'off', or any value from the standardized taxonomy defined for this attribute." ResourceKey="Autocomplete">Autocomplete: </Label>
<div class="col-sm-9">
<input id="autocomplete" class="form-control" @bind="@_autocomplete" maxlength="30" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
<div class="col-sm-9">
@ -77,7 +95,7 @@
<br />
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@if (PageState.QueryString.ContainsKey("id"))
@if (PageState.QueryString.ContainsKey("id"))
{
<br />
<br />
@ -95,8 +113,11 @@
private string _category = string.Empty;
private string _vieworder = "0";
private string _maxlength = "0";
private string _rows = "1";
private string _defaultvalue = string.Empty;
private string _options = string.Empty;
private string _validation = string.Empty;
private string _autocomplete = string.Empty;
private string _isrequired = "False";
private string _isprivate = "False";
private string createdby;
@ -124,8 +145,11 @@
_category = profile.Category;
_vieworder = profile.ViewOrder.ToString();
_maxlength = profile.MaxLength.ToString();
_rows = profile.Rows.ToString();
_defaultvalue = profile.DefaultValue;
_options = profile.Options;
_validation = profile.Validation;
_autocomplete = profile.Autocomplete;
_isrequired = profile.IsRequired.ToString();
_isprivate = profile.IsPrivate.ToString();
createdby = profile.CreatedBy;
@ -167,10 +191,14 @@
profile.Category = _category;
profile.ViewOrder = int.Parse(_vieworder);
profile.MaxLength = int.Parse(_maxlength);
profile.Rows = int.Parse(_rows);
profile.DefaultValue = _defaultvalue;
profile.Options = _options;
profile.Validation = _validation;
profile.Autocomplete = _autocomplete;
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
if (_profileid != -1)
{
profile = await ProfileService.UpdateProfileAsync(profile);

View File

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

View File

@ -14,7 +14,7 @@
else
{
<TabStrip>
<TabPanel Name="Pages" ResourceKey="Pages">
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
@if (!_pages.Where(item => item.IsDeleted).Any())
{
<br />
@ -31,7 +31,7 @@ else
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td>@context.Name</td>
<td>@context.DeletedBy</td>
@ -42,7 +42,7 @@ else
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
}
</TabPanel>
<TabPanel Name="Modules" ResourceKey="Modules">
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
@if (!_modules.Where(item => item.IsDeleted).Any())
{
<br />
@ -76,8 +76,8 @@ else
}
@code {
private List<Page> _pages;
private List<Module> _modules;
private List<Page> _pages;
private List<Module> _modules;
private int _pagePage = 1;
private int _pageModule = 1;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -184,13 +184,6 @@ else
try
{
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// check if there are any remaining module instances in the site
if (!_modules.Exists (item => item.ModuleId == module.ModuleId && item.PageModuleId != module.PageModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
await logger.LogInformation("Module Permanently Deleted {Module}", module);
await Load();
StateHasChanged();
@ -210,16 +203,7 @@ else
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
{
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// DeletePageModuleAsync does not update _modules so remove it.
_modules.Remove(module);
// check if there are any remaining module instances in the site
if (!_modules.Exists(item => item.ModuleId == module.ModuleId && item.PageModuleId != module.PageModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
}
await logger.LogInformation("Modules Permanently Deleted");
await Load();
ModuleInstance.HideProgressIndicator();

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Register
@using System.Net
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -16,7 +17,7 @@
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized>
<ModuleMessage Message="@_passwordconstruction" Type="MessageType.Info" />
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
@ -30,7 +31,7 @@
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -39,7 +40,7 @@
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -69,6 +70,7 @@ else
}
@code {
private string _passwordrequirements;
private string _username = string.Empty;
private ElementReference form;
private bool validated = false;
@ -79,45 +81,17 @@ else
private string _email = 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;
protected override async Task OnInitializedAsync()
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
_requiredigit = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireDigit", "true"));
_requireupper = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireUppercase", "true"));
_requirelower = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireLowercase", "true"));
_requirepunctuation = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true"));
// Replace the placeholders with the actual values of the variables
string digitRequirement = _requiredigit ? Localizer["Password.DigitRequirement"] + ", " : "";
string uppercaseRequirement = _requireupper ? Localizer["Password.UppercaseRequirement"] + ", " : "";
string lowercaseRequirement = _requirelower ? Localizer["Password.LowercaseRequirement"] + ", " : "";
string punctuationRequirement = _requirepunctuation ? Localizer["Password.PunctuationRequirement"] + ", " : "";
// Replace the placeholders with the actual values of the variables
string passwordValidationCriteriaTemplate = Localizer["Password.ValidationCriteria"];
_passwordconstruction = Localizer["Info.Registration.InvalidEmail"] + ". " + string.Format(passwordValidationCriteriaTemplate,
_minimumlength, _uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement);
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
}
protected override void OnParametersSet()
{
_togglepassword = SharedLocalizer["ShowPassword"];
}
{
_togglepassword = SharedLocalizer["ShowPassword"];
}
private async Task Register()
{
@ -147,7 +121,14 @@ else
if (user != null)
{
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
if (PageState.QueryString.ContainsKey("returnurl"))
{
NavigationManager.NavigateTo(WebUtility.UrlDecode(PageState.QueryString["returnurl"]));
}
else // legacy behavior
{
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
}
}
else
{

View File

@ -6,6 +6,7 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Your username will be populated from the link you received in the password reset notification" ResourceKey="Username">Username: </Label>
@ -18,7 +19,7 @@
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -27,7 +28,7 @@
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -45,12 +46,14 @@
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
private string _passwordrequirements;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override async Task OnInitializedAsync()
{
_togglepassword = SharedLocalizer["ShowPassword"];
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
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" />
<Pager Items="@_roles">
<Pager Items="@_roles" SearchProperties="Name">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>

View File

@ -22,60 +22,11 @@
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label>
<div class="col-sm-9">
<select id="homepage" class="form-select" @bind="@_homepageid" required>
<option value="-">&lt;@Localizer["Not Specified"]&gt;</option>
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (Page page in PageState.Pages)
{
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
@ -98,10 +49,108 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing" ResourceKey="SiteMap">Site Map: </Label>
<div class="col-sm-9">
<input id="sitemap" class="form-control" @bind="@_sitemap" required disabled />
</div>
<div class="input-group">
<input id="sitemap" class="form-control" @bind="@_sitemap" disabled />
<a href="@_sitemap" class="btn btn-secondary" target="_new">@Localizer["Browse"]</a>
</div>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="siteguid" HelpText="The Unique Identifier For The Site" ResourceKey="SiteGuid">ID: </Label>
<div class="col-sm-9">
<input id="siteguid" class="form-control" @bind="@_siteguid" required disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The site version (for site content migrations)" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" required disabled />
</div>
</div>
</div>
<br />
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@_ImageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
</div>
</Section>
<Section Name="FileExtensions" Heading="File Extensions" ResourceKey="FileExtensions">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
<div class="col-sm-9">
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_ImageFiles" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
<div class="col-sm-9">
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_UploadableFiles" />
</div>
</div>
</div>
</Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
<div class="col-sm-9">
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
<div class="col-sm-9">
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
</div>
</div>
</div>
</Section>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<div class="container">
<div class="row mb-1 align-items-center">
@ -124,9 +173,9 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
<div class="col-sm-9">
<select id="enabledSSl" class="form-select" @bind="@_smtpssl" >
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
@ -143,7 +192,7 @@
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword">@_togglesmtppassword</button>
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
</div>
</div>
</div>
@ -162,10 +211,19 @@
</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="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SMTPEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="smtpenabled" class="form-select" @bind="@_smtpenabled">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retention" />
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
</div>
</div>
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
@ -201,64 +259,63 @@
{
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliases" HelpText="The list of aliases for this site" ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
<Pager Items="@_aliases">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["AliasName"]</th>
<th>@Localizer["AliasDefault"]</th>
</Header>
<Row>
@if (context.AliasId != _aliasid)
{
<td>
@if (_aliasid == -1)
{
<button type="button" class="btn btn-primary" @onclick="@(() => EditAlias(context))">@SharedLocalizer["Edit"]</button>
}
</td>
<td>
@if (_aliasid == -1)
{
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteModule" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
}
</td>
<td>@context.Name</td>
<td>@context.IsDefault</td>
}
else
{
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SaveAlias())">@SharedLocalizer["Save"]</button></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelAlias())">@SharedLocalizer["Cancel"]</button></td>
<td>
<input id="aliasname" class="form-control" @bind="@_aliasname" />
</td>
<td>
<select id="defaultaias" class="form-select" @bind="@_defaultalias" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
}
</Row>
</Pager>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliases" HelpText="The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
<Pager Items="@_aliases">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["AliasName"]</th>
<th>@Localizer["AliasDefault"]</th>
</Header>
<Row>
@if (context.AliasId != _aliasid)
{
<td>
@if (_aliasid == -1)
{
<button type="button" class="btn btn-primary" @onclick="@(() => EditAlias(context))">@SharedLocalizer["Edit"]</button>
}
</td>
<td>
@if (_aliasid == -1)
{
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
}
</td>
<td>@context.Name</td>
<td>@context.IsDefault</td>
}
else
{
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SaveAlias())">@SharedLocalizer["Save"]</button></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelAlias())">@SharedLocalizer["Cancel"]</button></td>
<td>
<input id="aliasname" class="form-control" @bind="@_aliasname" />
</td>
<td>
<select id="defaultalias" class="form-select" @bind="@_defaultalias" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
}
</Row>
</Pager>
</div>
</div>
</div>
</Section>
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtime" HelpText="The Blazor runtime hosting model" ResourceKey="Runtime">Runtime: </Label>
<Label Class="col-sm-3" For="runtime" HelpText="The Blazor runtime hosting model for the site" ResourceKey="Runtime">Runtime: </Label>
<div class="col-sm-9">
<select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
<option value="Hybrid">@SharedLocalizer["BlazorHybrid"]</option>
</select>
</div>
</div>
@ -271,6 +328,15 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hybridenabled" HelpText="Specifies if the site can be integrated with an external .NET MAUI hybrid application" ResourceKey="HybridEnabled">Hybrid Enabled? </Label>
<div class="col-sm-9">
<select id="hybridenabled" class="form-select" @bind="@_hybridenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</Section>
<Section Name="TenantInformation" Heading="Database" ResourceKey="TenantInformation">
@ -306,253 +372,302 @@
}
@code {
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
private List<Alias> _aliases;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _runtime = "";
private string _prerender = "";
private int _logofileid = -1;
private FileManager _logofilemanager;
private int _faviconfileid = -1;
private FileManager _faviconfilemanager;
private string _themetype = "-";
private string _containertype = "-";
private string _admincontainertype = "-";
private string _homepageid = "-";
private string _sitemap = "";
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = "False";
private string _smtpusername = string.Empty;
private string _smtppassword = string.Empty;
private string _smtppasswordtype = "password";
private string _togglesmtppassword = string.Empty;
private string _smtpsender = string.Empty;
private string _smtprelay = "False";
private string _retention = string.Empty;
private string _pwaisenabled;
private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager;
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
private string _isdeleted;
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
private string _homepageid = "-";
private string _isdeleted;
private string _sitemap = "";
private string _siteguid = "";
private string _version = "";
private int _logofileid = -1;
private FileManager _logofilemanager;
private int _faviconfileid = -1;
private FileManager _faviconfilemanager;
private string _themetype = "";
private string _containertype = "";
private string _admincontainertype = "";
private string _headcontent = string.Empty;
private string _bodycontent = string.Empty;
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = "False";
private string _smtpusername = string.Empty;
private string _smtppassword = string.Empty;
private string _smtppasswordtype = "password";
private string _togglesmtppassword = string.Empty;
private string _smtpsender = string.Empty;
private string _smtprelay = "False";
private string _smtpenabled = "True";
private string _ImageFiles = string.Empty;
private string _UploadableFiles = string.Empty;
private int _retention = 30;
private string _pwaisenabled;
private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager;
private List<Alias> _aliases;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _runtime = "";
private string _prerender = "";
private string _hybridenabled = "";
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
{
try
{
_themeList = await ThemeService.GetThemesAsync();
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
_name = site.Name;
_runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, "");
_isdeleted = site.IsDeleted.ToString();
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml";
protected override async Task OnInitializedAsync()
{
try
{
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
_name = site.Name;
if (site.HomePageId != null)
{
_homepageid = site.HomePageId.Value.ToString();
}
_isdeleted = site.IsDeleted.ToString();
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/sitemap.xml";
_siteguid = site.SiteGuid;
_version = site.Version;
await GetAliases();
// appearance
if (site.LogoFileId != null)
{
_logofileid = site.LogoFileId.Value;
}
if (site.LogoFileId != null)
{
_logofileid = site.LogoFileId.Value;
}
if (site.FaviconFileId != null)
{
_faviconfileid = site.FaviconFileId.Value;
}
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
if (site.FaviconFileId != null)
{
_faviconfileid = site.FaviconFileId.Value;
}
// page content
_headcontent = site.HeadContent;
_bodycontent = site.BodyContent;
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
// PWA
_pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null)
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
if (site.HomePageId != null)
{
_homepageid = site.HomePageId.Value.ToString();
}
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
_pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null)
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
// file extensions
_ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
_UploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
_UploadableFiles = (string.IsNullOrEmpty(_UploadableFiles)) ? Constants.UploadableFiles : _UploadableFiles;
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
// aliases
await GetAliases();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var tenants = await TenantService.GetTenantsAsync();
var _databases = await DatabaseService.GetDatabasesAsync();
var tenant = tenants.Find(item => item.TenantId == site.TenantId);
if (tenant != null)
{
_tenant = tenant.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_connectionstring = tenant.DBConnectionString;
}
}
// hosting model
_runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, "");
_hybridenabled = site.HybridEnabled.ToString();
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy;
_modifiedon = site.ModifiedOn;
_deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
// database
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var tenants = await TenantService.GetTenantsAsync();
var _databases = await DatabaseService.GetDatabasesAsync();
var tenant = tenants.Find(item => item.TenantId == site.TenantId);
if (tenant != null)
{
_tenant = tenant.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_connectionstring = tenant.DBConnectionString;
}
}
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
// audit
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy;
_modifiedon = site.ModifiedOn;
_deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_admincontainertype = Constants.DefaultAdminContainer;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
}
}
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (_name != string.Empty && _themetype != "-" && _containertype != "-")
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
bool refresh = false;
bool reload = false;
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _containers.First().TypeName;
_admincontainertype = Constants.DefaultAdminContainer;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
}
}
site.Name = _name;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
reload = true; // needs to be reloaded on server
}
}
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (_name != string.Empty && _themetype != "-" && _containertype != "-")
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
bool refresh = false;
bool reload = false;
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{
site.LogoFileId = logofileid;
}
int? faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId == -1) faviconFieldId = null;
if (site.FaviconFileId != faviconFieldId)
{
site.FaviconFileId = faviconFieldId;
reload = true; // needs to be reloaded on server
}
if (site.DefaultThemeType != _themetype)
{
site.DefaultThemeType = _themetype;
refresh = true; // needs to be refreshed on client
}
if (site.DefaultContainerType != _containertype)
{
site.DefaultContainerType = _containertype;
refresh = true; // needs to be refreshed on client
}
site.AdminContainerType = _admincontainertype;
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
site.Name = _name;
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
reload = true; // needs to be reloaded on server
}
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid == -1) pwaappiconfileid = null;
if (site.PwaAppIconFileId != pwaappiconfileid)
{
site.PwaAppIconFileId = pwaappiconfileid;
reload = true; // needs to be reloaded on server
}
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
if (site.PwaSplashIconFileId != pwasplashiconfileid)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
reload = true; // needs to be reloaded on server
}
// appearance
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{
site.LogoFileId = logofileid;
if (logofileid != _logofileid)
{
_logofileid = logofileid;
refresh = true; // needs to be refreshed on client
}
}
int? faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId == -1) faviconFieldId = null;
if (site.FaviconFileId != faviconFieldId)
{
site.FaviconFileId = faviconFieldId;
reload = true; // needs to be reloaded on server
}
if (site.DefaultThemeType != _themetype)
{
site.DefaultThemeType = _themetype;
refresh = true; // needs to be refreshed on client
}
if (site.DefaultContainerType != _containertype)
{
site.DefaultContainerType = _containertype;
refresh = true; // needs to be refreshed on client
}
site.AdminContainerType = _admincontainertype;
// page content
if (site.HeadContent != _headcontent)
{
site.HeadContent = _headcontent;
reload = true;
}
if (site.BodyContent != _bodycontent)
{
site.BodyContent = _bodycontent;
reload = true;
}
// PWA
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
reload = true; // needs to be reloaded on server
}
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid == -1) pwaappiconfileid = null;
if (site.PwaAppIconFileId != pwaappiconfileid)
{
site.PwaAppIconFileId = pwaappiconfileid;
reload = true; // needs to be reloaded on server
}
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
if (site.PwaSplashIconFileId != pwasplashiconfileid)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
reload = true; // needs to be reloaded on server
}
// hosting model
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender || site.HybridEnabled != bool.Parse(_hybridenabled))
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
site.HybridEnabled = bool.Parse(_hybridenabled);
reload = true; // needs to be reloaded on serve
}
}
site = await SiteService.UpdateSiteAsync(site);
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
// file extensions
settings = SettingService.SetSetting(settings, "ImageFiles", (_ImageFiles != Constants.ImageFiles) ? _ImageFiles.Replace(" ", "") : "", false);
settings = SettingService.SetSetting(settings, "UploadableFiles", (_UploadableFiles != Constants.UploadableFiles) ? _UploadableFiles.Replace(" ", "") : "", false);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Settings Saved {Site}", site);
@ -564,8 +679,8 @@
else
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await interop.ScrollTo(0, 0, "smooth");
}
await ScrollToPageTop();
}
}
}
else
@ -633,9 +748,8 @@
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
var interop = new Interop(JSRuntime);
await interop.ScrollTo(0, 0, "smooth");
}
await ScrollToPageTop();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Testing SMTP Configuration");
@ -698,41 +812,55 @@
}
}
private async Task SaveAlias()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (!string.IsNullOrEmpty(_aliasname))
{
var aliases = await AliasService.GetAliasesAsync();
var alias = aliases.Where(item => item.Name == _aliasname).FirstOrDefault();
bool unique = (alias == null || alias.AliasId == _aliasid);
if (unique)
{
if (_aliasid == 0)
{
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Site.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
await AliasService.AddAliasAsync(alias);
}
else
{
alias = _aliases.Single(item => item.AliasId == _aliasid);
alias.Name = _aliasname;
alias.IsDefault = bool.Parse(_defaultalias);
await AliasService.UpdateAliasAsync(alias);
}
}
else // duplicate alias
{
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
}
}
await GetAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
}
private async Task SaveAlias()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (!string.IsNullOrEmpty(_aliasname))
{
var aliases = await AliasService.GetAliasesAsync();
int protocolIndex = _aliasname.IndexOf("://", StringComparison.OrdinalIgnoreCase);
if (protocolIndex != -1)
{
_aliasname = _aliasname.Substring(protocolIndex + 3);
}
var alias = aliases.FirstOrDefault(item => item.Name == _aliasname);
bool unique = (alias == null || alias.AliasId == _aliasid);
if (unique)
{
if (_aliasid == 0)
{
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Site.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
await AliasService.AddAliasAsync(alias);
}
else
{
alias = _aliases.SingleOrDefault(item => item.AliasId == _aliasid);
if (alias != null)
{
alias.Name = _aliasname;
alias.IsDefault = bool.Parse(_defaultalias);
await AliasService.UpdateAliasAsync(alias);
}
}
await GetAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
else // Duplicate alias
{
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
await ScrollToPageTop();
}
}
}
}
private async Task CancelAlias()
{

View File

@ -29,7 +29,7 @@ else
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder)." ResourceKey="Aliases">Aliases: </Label>
<Label Class="col-sm-3" For="alias" HelpText="The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
@ -37,7 +37,7 @@ else
<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>
<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>
@foreach (var theme in _themes)
{
@ -58,19 +58,6 @@ else
</select>
</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">
<Label Class="col-sm-3" For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
<div class="col-sm-9">
@ -89,7 +76,6 @@ else
<select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
<option value="Hybrid">@SharedLocalizer["BlazorHybrid"]</option>
</select>
</div>
</div>
@ -105,7 +91,7 @@ else
<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>
<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.Add"]&gt;</option>
@foreach (Tenant tenant in _tenants)
@ -188,46 +174,59 @@ else
}
@code {
private List<Database> _databases;
private ElementReference form;
private bool validated = false;
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private List<Database> _databases;
private ElementReference form;
private bool validated = false;
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<SiteTemplate> _siteTemplates;
private List<Tenant> _tenants;
private string _tenantid = "-";
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<SiteTemplate> _siteTemplates;
private List<Tenant> _tenants;
private string _tenantid = "-";
private string _tenantName = string.Empty;
private string _tenantName = string.Empty;
private string _hostusername = string.Empty;
private string _hostpassword = string.Empty;
private string _hostusername = string.Empty;
private string _hostpassword = string.Empty;
private string _name = string.Empty;
private string _urls = string.Empty;
private string _themetype = "-";
private string _containertype = "-";
private string _admincontainertype = "";
private string _sitetemplatetype = "-";
private string _runtime = "Server";
private string _prerender = "Prerendered";
private string _name = string.Empty;
private string _urls = string.Empty;
private string _themetype = "-";
private string _containertype = "-";
private string _sitetemplatetype = "-";
private string _runtime = "Server";
private string _prerender = "Prerendered";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
_tenants = await TenantService.GetTenantsAsync();
_urls = PageState.Alias.Name;
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
protected override async Task OnInitializedAsync()
{
_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;
_themeList = await ThemeService.GetThemesAsync();
_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();
if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate))
{
_sitetemplatetype = Constants.DefaultSiteTemplate;
}
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
@ -288,13 +287,13 @@ else
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
_containertype = _containers.First().TypeName;
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_admincontainertype = "";
_containertype = "-";
}
StateHasChanged();
}
catch (Exception ex)
@ -398,7 +397,7 @@ else
config.Aliases = _urls;
config.DefaultTheme = _themetype;
config.DefaultContainer = _containertype;
config.DefaultAdminContainer = _admincontainertype;
config.DefaultAdminContainer = "";
config.SiteTemplate = _sitetemplatetype;
config.Runtime = _runtime;
config.RenderMode = _runtime + _prerender;

View File

@ -8,17 +8,17 @@
@if (_sites == null)
{
<p><em>Loading...</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" />
<Pager Items="@_sites">
<Pager Items="@_sites" SearchProperties="Name">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["AliasName"]</th>
</Header>
<Row>
<td><button type="button" class="btn btn-primary" @onclick="@(async () => Edit(context.Name))">@SharedLocalizer["Edit"]</button></td>
@ -48,11 +48,25 @@ else
private void Edit(string name)
{
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name + "/admin/site", true);
if (PageState.Alias.Name == name)
{
NavigationManager.NavigateTo("/admin/site");
}
else
{
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name + "/admin/site", true);
}
}
private void Browse(string name)
{
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name, true);
if (PageState.Alias.Name == name)
{
NavigationManager.NavigateTo("/");
}
else
{
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name, true);
}
}
}

View File

@ -133,12 +133,9 @@
</div>
</div>
<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">
<select id="packageservice" class="form-select" @bind="@_packageservice">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
</div>
</div>
</div>
@ -182,7 +179,7 @@
private string _logginglevel = string.Empty;
private string _notificationlevel = string.Empty;
private string _swagger = string.Empty;
private string _packageservice = string.Empty;
private string _packageregistryurl = string.Empty;
private string _log = string.Empty;
@ -213,7 +210,7 @@
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
_swagger = systeminfo["UseSwagger"].ToString();
_packageservice = systeminfo["PackageService"].ToString();
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
}
systeminfo = await SystemService.GetSystemInfoAsync("log");
@ -232,7 +229,7 @@
settings.Add("Logging:LogLevel:Default", _logginglevel);
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
settings.Add("UseSwagger", _swagger);
settings.Add("PackageService", _packageservice);
settings.Add("PackageRegistryUrl", _packageregistryurl);
await SystemService.UpdateSystemInfoAsync(settings);
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
}

View File

@ -10,68 +10,124 @@
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<div class="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>
@if (_packages != null)
{
if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;@SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group">
<span class="input-group-text">@Localizer["Product"]</span>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<button type="button" class="btn btn-primary ms-2" @onclick="Refresh"><span class="@Icons.Reload" aria-hidden="true"></span></button>
</div>
}
}
</div>
</div>
<div class="row mb-3">
<div class="col">
@if (_initialized)
{
<br />
<div class="row mb-3">
<div class="col-sm-4">
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
</div>
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-4">
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
@if (_price == "free")
{
<option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
}
<option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
@if (_price == "paid")
{
<option value="price">@SharedLocalizer["Search.Price"]</option>
}
</select>
</div>
</div>
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6">
<Row>
<div class="m-2 p-2 d-flex justify-content-center">
<div class="container-fluid px-0">
<div class="row g-0 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
{
<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>
}
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
</div>
</div>
</div>
</Row>
</Pager>
}
</div>
</div>
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation." ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>
</div>
</div>
@ -111,16 +167,14 @@
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
@code {
private bool _initialized = false;
private int _page = 1;
private List<Package> _packages;
private string _price = "free";
private string _sort = "popularity";
private string _search = "";
private string _productname = "";
private string _license = "";
@ -134,6 +188,7 @@
try
{
await LoadThemes();
_initialized = true;
}
catch (Exception ex)
{
@ -144,8 +199,10 @@
private async Task LoadThemes()
{
ShowProgressIndicator();
var themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "");
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
if (_packages != null)
{
@ -157,46 +214,44 @@
}
}
}
HideProgressIndicator();
}
private async void PriceChanged(ChangeEventArgs e)
private async void PriceChanged(string price)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadThemes();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
_price = price;
_sort = "popularity";
await LoadThemes();
StateHasChanged();
}
private async Task Search()
{
try
{
await LoadThemes();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
await LoadThemes();
}
private async Task Reset()
{
try
{
_search = "";
await LoadThemes();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
_page = 1;
_search = "";
await LoadThemes();
}
private async Task Refresh()
{
await LoadThemes();
}
private void OnPageChange(int page)
{
_page = page;
}
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadThemes();
}
private void HideModal()
@ -210,7 +265,7 @@
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
var package = await PackageService.GetPackageAsync(packageid, version, false);
if (package != null)
{
_productname = package.Name;
@ -220,8 +275,13 @@
}
_packageid = package.PackageId;
_version = package.Version;
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
}
StateHasChanged();
}
catch (Exception ex)
{
@ -234,9 +294,9 @@
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(_packageid, _version);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Theme.Download"], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
_productname = "";
_license = "";
StateHasChanged();
@ -248,16 +308,8 @@
}
}
private async Task InstallThemes()
private void OnUpload()
{
try
{
await ThemeService.InstallThemesAsync();
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Theme");
}
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -80,7 +80,10 @@
protected override void OnInitialized()
{
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
if (!NavigationManager.BaseUri.Contains("localhost:"))
{
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
}
}
protected override async Task OnParametersSetAsync()
@ -102,7 +105,8 @@
{
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);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
@ -142,8 +146,14 @@
if (_owner != "" && _theme != "" && _template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _theme;
if (!string.IsNullOrEmpty(template.Namespace))
{
_location = template.Location + template.Namespace.Replace("[Owner]", _owner).Replace("[Theme]", _theme);
}
else
{
_location = template.Location + _owner + ".Theme." + _theme;
}
}
StateHasChanged();
}

View File

@ -0,0 +1,211 @@
@namespace Oqtane.Modules.Admin.Themes
@using System.Net
@inherits ModuleBase
@inject IThemeService ThemeService
@inject IPackageService PackageService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information" Heading="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="themename" class="form-control" @bind="@_themeName" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this 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">
@if (!string.IsNullOrEmpty(_packagename))
{
<div class="input-group">
<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 class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9">
@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>
}
</div>
</div>
</div>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
}
@code {
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private int _themeId;
private string _themeName = "";
private string _isenabled;
private string _name;
private string _version;
private string _packagename = "";
private string _packageurl = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeId = Int32.Parse(PageState.QueryString["id"]);
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
if (theme != null)
{
_name = theme.Name;
_isenabled =theme.IsEnabled.ToString();
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
_createdby = theme.CreatedBy;
_createdon = theme.CreatedOn;
_modifiedby = theme.ModifiedBy;
_modifiedon = theme.ModifiedOn;
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
}
}
private async Task SaveTheme()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
theme.Name = _name;
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
await ThemeService.UpdateThemeAsync(theme);
await logger.LogInformation("Theme Saved {Theme}", theme);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Theme {ThemeId} {Error}", _themeId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
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
{
<ActionLink Action="Add" Text="Install Theme" />
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" />
@ -23,11 +23,13 @@ else
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@SharedLocalizer["Support"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th>&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
<td>
@if (context.AssemblyName != Constants.ClientId)
{
@ -36,6 +38,19 @@ else
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@((MarkupString)SupportLink(context.PackageName, context.Version))
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>
@ -64,7 +79,7 @@ else
try
{
_themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme");
_packages = await PackageService.GetPackageUpdatesAsync("theme");
}
catch (Exception ex)
{
@ -85,11 +100,14 @@ else
if (package != null)
{
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
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>";
}
}
}
@ -97,6 +115,20 @@ else
return link;
}
private string SupportLink(string packagename, string version)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{
link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
}
}
return link;
}
private string UpgradeAvailable(string packagename, string version)
{
if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -114,9 +146,8 @@ else
{
try
{
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(packagename, version);
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version);
await ThemeService.InstallThemesAsync();
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)

View File

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

View File

@ -7,34 +7,38 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
@if (_package != null && _upgradeavailable)
{
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage>
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
}
else
{
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage>
}
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<ModuleMessage Type="MessageType.Info" Message="Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade"></ModuleMessage>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" />
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
@if (_package != null && _upgradeavailable)
{
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage>
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
}
else
{
<ModuleMessage Type="MessageType.Info" Message=@Localizer["Message.Text"]></ModuleMessage>
}
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<ModuleMessage Type="MessageType.Info" Message=@Localizer["MessageUpgrade.Text"]></ModuleMessage>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" />
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
</TabPanel>
</TabStrip>
}
@code {
private bool _initialized = false;
private Package _package;
private bool _upgradeavailable = false;
@ -44,18 +48,26 @@
{
try
{
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
if (packages != null)
if (NavigationManager.BaseUri.Contains("localhost:"))
{
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
if (_package != null)
AddModuleMessage(Localizer["Localhost.Text"], MessageType.Info);
}
else
{
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
if (packages != null)
{
_upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0);
}
else
{
_package = new Package { Name = Constants.PackageId, Version = Constants.Version };
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
if (_package != null)
{
_upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0);
}
else
{
_package = new Package { Name = Constants.PackageId, Version = Constants.Version };
}
}
_initialized = true;
}
}
catch
@ -85,8 +97,8 @@
{
try
{
await PackageService.DownloadPackageAsync(packageid, version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(packageid, version);
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
}
catch (Exception ex)

View File

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

View File

@ -2,6 +2,7 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IUserRoleService UserRoleService
@inject INotificationService NotificationService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -10,10 +11,10 @@
{
<div class="container">
<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">
<input id="to" class="form-control" @bind="@username" />
</div>
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="username" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label>
@ -30,11 +31,11 @@
</div>
<br/>
<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 {
private string username = "";
private AutoComplete username;
private string subject = "";
private string body = "";
@ -42,21 +43,35 @@
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()
{
try
{
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user != null)
if (!string.IsNullOrEmpty(username.Key) && !string.IsNullOrEmpty(subject))
{
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body);
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
NavigationManager.NavigateTo(NavigateUrl());
var user = await UserService.GetUserAsync(int.Parse(username.Key), ModuleState.SiteId);
if (user != null)
{
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body);
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
else
{
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required"], MessageType.Warning);
}
}
catch (Exception ex)

View File

@ -1,4 +1,6 @@
@namespace Oqtane.Modules.Admin.UserProfile
@using System.Net
@using System.Text.RegularExpressions;
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -10,18 +12,19 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null && photo != null)
@if (_initialized)
{
<img src="@ImageUrl(photofileid, 400, 400)" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
}
else
{
<br />
}
<TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null && settings != 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">
}
else
{
<br />
}
<TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity">
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
@ -31,34 +34,34 @@ else
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@if (allowtwofactor)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
<div class="col-sm-9">
<select id="twofactor" class="form-select" @bind="@twofactor" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
@if (allowtwofactor)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
<div class="col-sm-9">
<select id="twofactor" class="form-select" @bind="@twofactor" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
@ -74,18 +77,15 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
<div class="col-sm-9">
<FileManager FileId="@photofileid" Filter="@Constants.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@folderid" @ref="filemanager" />
<FileManager FileId="@photofileid" Filter="@PageState.Site.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@folderid" @ref="filemanager" />
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
}
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null && settings != null)
{
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
<div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles)
@ -102,32 +102,93 @@ else
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9">
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
{
<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
{
<option value="@option">@option</option>
}
}
</select>
}
else
{
@if (p.IsRequired)
@if (!string.IsNullOrEmpty(p.Autocomplete))
{
<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))" autocomplete="@p.Autocomplete">
@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
{
<option value="@option">@option</option>
}
}
</select>
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @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
{
<option value="@option">@option</option>
}
}
</select>
}
}
else
{
@if (p.Rows == 1)
{
if (!string.IsNullOrEmpty(p.Autocomplete))
{
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" />
}
}
else
{
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
}
}
}
else
{
if (!string.IsNullOrEmpty(p.Autocomplete))
{
@if (p.IsRequired)
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea>
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea>
}
}
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>
@ -138,229 +199,306 @@ else
</div>
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
}
</TabPanel>
<TabPanel Name="Notifications" ResourceKey="Notifications">
@if (notifications != null)
{
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" />
<br /><br />
</TabPanel>
<TabPanel Name="Notifications" ResourceKey="Notifications">
<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))">
<option value="to">@Localizer["Inbox"]</option>
<option value="from">@Localizer["Items.Sent"]</option>
</select>
<br />
@if (filter == "to")
{
<Pager Items="@notifications">
<Header>
@if (notifications.Any())
{
<Pager Items="@notifications">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["From"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Received"]</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.FromDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
} }
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
</td>
</Detail>
</Pager>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
<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)
{
<td>@context.FromDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
}
else
{
<td><b>@context.FromDisplayName</b></td>
<td><b>@context.Subject</b></td>
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
}
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
}
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
}
@if (context.IsRead)
{
@notificationSummary
}
else
{
<b>@notificationSummary</b>
}
</td>
</Detail>
</Pager>
<br />
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
}
else
{
<div class="no-notifications-text">
@Localizer["NoNotificationsReceived.Text"]
</div>
}
}
else
{
<Pager Items="@notifications">
<Header>
<th>&nbsp;</th>
<th>&nbsp;</th>
@if (notifications.Any())
{
<Pager Items="@notifications">
<Header>
<th style="width: 1px;"></th>
<th style="width: 1px;"></th>
<th>@Localizer["To"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Sent"]</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.ToDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
} }
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
</td>
</Detail>
</Pager>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
<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)
{
<td>@context.ToDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
}
else
{
<td><b>@context.ToDisplayName</b></td>
<td><b>@context.Subject</b></td>
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
}
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
}
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
}
@if (context.IsRead)
{
@notificationSummary
}
else
{
<b>@notificationSummary</b>
}
</td>
</Detail>
</Pager>
<br />
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
}
else
{
<div class="no-notifications-text">
@Localizer["NoNotificationsSent.Text"]
</div>
}
}
@if (notifications.Any())
{
<br />
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
}
<br /><hr />
<select class="form-select" @onchange="(e => FilterChanged(e))">
<option value="to">@Localizer["Inbox"]</option>
<option value="from">@Localizer["Items.Sent"]</option>
</select>
}
</TabPanel>
</TabStrip>
<br /><br />
</TabPanel>
</TabStrip>
<br />
<br />
}
@code {
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private bool allowtwofactor = false;
private string twofactor = "False";
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int folderid = -1;
private int photofileid = -1;
private File photo = null;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
private string filter = "to";
private List<Notification> notifications;
private bool _initialized = false;
private string _passwordrequirements;
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private bool allowtwofactor = false;
private string twofactor = "False";
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int folderid = -1;
private int photofileid = -1;
private File photo = null;
private string _ImageFiles = string.Empty;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private string filter = "to";
private List<Notification> notifications;
private string notificationSummary = string.Empty;
protected override async Task OnParametersSetAsync()
{
try
{
_togglepassword = SharedLocalizer["ShowPassword"];
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
{
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
}
protected override async Task OnInitializedAsync()
{
try
{
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
if (PageState.User != null)
{
username = PageState.User.Username;
twofactor = PageState.User.TwoFactorRequired.ToString();
email = PageState.User.Email;
displayname = PageState.User.DisplayName;
if (PageState.User != null)
{
username = PageState.User.Username;
twofactor = PageState.User.TwoFactorRequired.ToString();
email = PageState.User.Email;
displayname = PageState.User.DisplayName;
// get user folder
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
if (folder != null)
{
folderid = folder.FolderId;
}
if (string.IsNullOrEmpty(email))
{
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
}
if (PageState.User.PhotoFileId != null)
{
photofileid = PageState.User.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
// get user folder
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
if (folder != null)
{
folderid = folder.FolderId;
}
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
if (PageState.User.PhotoFileId != null)
{
photofileid = PageState.User.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
await LoadNotificationsAsync();
}
else
{
AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
}
}
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
private async Task LoadNotificationsAsync()
{
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
}
await LoadNotificationsAsync();
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
_initialized = true;
}
else
{
AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
}
}
private async Task Save()
{
try
{
if (username != string.Empty && email != string.Empty && ValidateProfiles())
{
if (_password == confirm)
{
var user = PageState.User;
user.Username = username;
user.Password = _password;
user.TwoFactorRequired = bool.Parse(twofactor);
user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname);
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
private async Task LoadNotificationsAsync()
{
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
}
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged();
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
}
private async Task Save()
{
try
{
if (username != string.Empty && email != string.Empty)
{
if (_password == confirm)
{
if (ValidateProfiles())
{
var user = PageState.User;
user.Username = username;
user.Password = _password;
user.TwoFactorRequired = bool.Parse(twofactor);
user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname);
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
if (PageState.QueryString.ContainsKey("returnurl"))
{
NavigationManager.NavigateTo(WebUtility.UrlDecode(PageState.QueryString["returnurl"]));
}
else // legacy behavior
{
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged();
}
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
}
}
else
{
AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);
@ -370,6 +508,8 @@ else
{
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
}
await ScrollToPageTop();
}
catch (Exception ex)
{
@ -380,22 +520,33 @@ else
private bool ValidateProfiles()
{
bool valid = true;
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);
}
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
if (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 (!string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
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()
@ -437,7 +588,6 @@ else
private async void FilterChanged(ChangeEventArgs e)
{
filter = (string)e.Value;
await LoadNotificationsAsync();
StateHasChanged();
}
@ -459,12 +609,12 @@ else
await NotificationService.DeleteNotificationAsync(Notification.NotificationId);
}
await logger.LogInformation("Notification Deleted {Notification}", Notification);
}
}
await logger.LogInformation("Notifications Permanently Deleted");
await LoadNotificationsAsync();
ModuleInstance.HideProgressIndicator();
StateHasChanged();
StateHasChanged();
}
catch (Exception ex)
{
@ -472,21 +622,19 @@ else
AddModuleMessage(ex.Message, MessageType.Error);
ModuleInstance.HideProgressIndicator();
}
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

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

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Users
@using System.Text.RegularExpressions;
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -7,54 +8,63 @@
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@username" />
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null)
{
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_username" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@_email" />
</div>
</div>
<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>
<div class="col-sm-9">
<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 class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@email" />
</div>
</div>
<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>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@displayname" />
</div>
</div>
</div>
}
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null)
{
}
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
<div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles)
@ -69,37 +79,59 @@
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9">
@if (p.IsRequired)
<div class="col-sm-9">
@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
{
<option value="@option">@option</option>
}
}
</select>
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
@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))" />
}
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>
}
</TabPanel>
</TabStrip>
<br />
<br />
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
</TabStrip>
<br />
<br />
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
}
@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 _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
private string _email = string.Empty;
private string _displayname = string.Empty;
private string _notify = "True";
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
@ -110,9 +142,11 @@
{
try
{
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = new Dictionary<string, string>();
_initialized = true;
}
catch (Exception ex)
{
@ -121,34 +155,34 @@
}
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser()
{
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 (user == null)
if (ValidateProfiles())
{
user = new User();
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Username = _username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.Email = _email;
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
user.PhotoFileId = null;
user.SuppressNotification = !bool.Parse(_notify);
user = await UserService.AddUserAsync(user);
@ -160,14 +194,10 @@
}
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);
}
}
else
{
AddModuleMessage(Localizer["Message.Username.Exists"], MessageType.Warning);
}
}
else
{
@ -181,26 +211,40 @@
}
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);
}
}
private bool ValidateProfiles()
{
bool valid = true;
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);
}
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
valid = false;
if (profile.IsRequired && string.IsNullOrEmpty(value))
{
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
return false;
}
if (!string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
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)

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Users
@using System.Text.RegularExpressions;
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -8,18 +9,11 @@
@inject IStringLocalizer<Edit> Localizer
@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>
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null)
{
<TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity">
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label>
@ -29,20 +23,20 @@ else
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
@ -57,12 +51,6 @@ else
<input id="displayname" class="form-control" @bind="@displayname" />
</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">
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
<div class="col-sm-9">
@ -85,11 +73,8 @@ else
</div>
</div>
</div>
}
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null)
{
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
<div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles)
@ -104,8 +89,8 @@ else
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
{
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
@ -123,13 +108,13 @@ 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))" />
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @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))" />
<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>
@ -137,135 +122,130 @@ else
}
</div>
</div>
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
}
@code {
private bool _initialized = false;
private string _passwordrequirements;
private int userid;
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private string isdeleted;
private string lastlogin;
private string lastipaddress;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private string deletedby;
private DateTime? deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync()
{
try
{
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
{
userid = UserId;
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null)
{
username = user.Username;
email = user.Email;
displayname = user.DisplayName;
isdeleted = user.IsDeleted.ToString();
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy;
deletedon = user.DeletedOn;
}
}
_initialized = true;
}
</TabPanel>
</TabStrip>
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
}
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
@code {
private int userid;
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int photofileid = -1;
private File photo = null;
private string isdeleted;
private string lastlogin;
private string lastipaddress;
private async Task SaveUser()
{
try
{
if (username != string.Empty && email != string.Empty)
{
if (_password == confirm)
{
if (ValidateProfiles())
{
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private string deletedby;
private DateTime? deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnParametersSetAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id"))
{
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
userid = Int32.Parse(PageState.QueryString["id"]);
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null)
{
username = user.Username;
email = user.Email;
displayname = user.DisplayName;
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
isdeleted = user.IsDeleted.ToString();
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy;
deletedon = user.DeletedOn;
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser()
{
try
{
if (username != string.Empty && email != string.Empty && ValidateProfiles())
{
if (_password == confirm)
{
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
}
}
else
{
@ -286,19 +266,33 @@ else
private bool ValidateProfiles()
{
bool valid = true;
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);
}
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
valid = false;
if (profile.IsRequired && string.IsNullOrEmpty(value))
{
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
return false;
}
if (!string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
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)

View File

@ -17,27 +17,18 @@ else
{
<TabStrip>
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
</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">
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />&nbsp;
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Username"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["LastLoginOn"]</th>
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Username"))">@Localizer["Username"]<i class="@(SetSortIcon("Username"))"></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 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>
<Row>
<td>
@ -50,11 +41,12 @@ else
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
</td>
<td>@context.User.Username</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td>
<td>@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>
</Row>
</Pager>
</TabPanel>
</TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
<div class="container">
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
@ -106,6 +98,21 @@ else
<input id="cookiename" class="form-control" @bind="@_cookiename" />
</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>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@ -188,7 +195,7 @@ else
@if (_providertype != "")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="providername" HelpText="The external login provider name which will be displayed on the login page" ResourceKey="ProviderName">Provider Name:</Label>
<Label Class="col-sm-3" For="providername" HelpText="Specify a friendly name for the external login provider which will be displayed on the Login page" ResourceKey="ProviderName">Provider Name:</Label>
<div class="col-sm-9">
<input id="providername" class="form-control" @bind="@_providername" />
</div>
@ -247,7 +254,25 @@ else
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="authresponsetype" HelpText="Specify the authorization response type. The default is Authorization Code which is considered to be the most secure option based on the latest OAuth specification." 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">
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
<div class="col-sm-9">
<input id="scopes" class="form-control" @bind="@_scopes" />
@ -259,7 +284,7 @@ else
<input id="parameters" class="form-control" @bind="@_parameters" />
</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>
<div class="col-sm-9">
<select id="pkce" class="form-select" @bind="@_pkce" required>
@ -274,33 +299,51 @@ else
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="The name of the unique user identifier claim provided by the provider" ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="reviewclaims" HelpText="This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled." ResourceKey="ReviewClaims">Review Claims?</Label>
<div class="col-sm-9">
<div class="input-group">
<select id="reviewclaims" class="form-select" @bind="@_reviewclaims" required>
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
@if (_reviewclaims == "true")
{
<a href="@_externalloginurl" target="_blank" class="btn btn-secondary">@SharedLocalizer["Test"]</a>
}
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="Specify the type name of the unique user identifier claim provided by the provider. The default value is 'sub'." ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
<div class="col-sm-9">
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="emailclaimtype" HelpText="The name of the email address claim provided by the provider" ResourceKey="EmailClaimType">Email Claim:</Label>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="nameclaimtype" HelpText="Optionally specify the type name of the user's name claim provided by the provider. The typical value is 'name'." ResourceKey="NameClaimType">Name Claim:</Label>
<div class="col-sm-9">
<input id="nameclaimtype" class="form-control" @bind="@_nameclaimtype" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="emailclaimtype" HelpText="Optionally specify the type name of the email address claim provided by the provider. The typical value is 'email'," ResourceKey="EmailClaimType">Email Claim:</Label>
<div class="col-sm-9">
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
</div>
</div>
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the role claim provided by the provider" ResourceKey="RoleClaimType">Role Claim:</Label>
<div class="col-sm-9">
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the role claim provided by the provider" ResourceKey="RoleClaimType">Role Claim:</Label>
<div class="col-sm-9">
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
<div class="col-sm-9">
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
<div class="col-sm-9">
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
</div>
}
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
<div class="col-sm-9">
@ -316,14 +359,23 @@ else
</select>
</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 Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="secret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
<Label Class="col-sm-3" For="jwtsecret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
<div class="col-sm-9">
<div class="input-group">
<input type="@_secrettype" id="secret" class="form-control" @bind="@_secret" />
<input type="@_secrettype" id="jwtsecret" class="form-control" @bind="@_secret" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
</div>
</div>
@ -360,144 +412,140 @@ else
</div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
</TabPanel>
</TabPanel>
</TabStrip>
}
@code {
private List<UserRole> allusers;
private List<UserRole> users;
private string _search = "";
private List<UserRole> users;
private string _allowregistration;
private string _allowsitelogin;
private string _twofactor;
private string _cookiename;
private string _allowregistration;
private string _allowsitelogin;
private string _twofactor;
private string _cookiename;
private string _cookieexpiration;
private string _alwaysremember;
private string _minimumlength;
private string _uniquecharacters;
private string _requiredigit;
private string _requireupper;
private string _requirelower;
private string _requirepunctuation;
private string _maximumfailures;
private string _lockoutduration;
private string _minimumlength;
private string _uniquecharacters;
private string _requiredigit;
private string _requireupper;
private string _requirelower;
private string _requirepunctuation;
private string _maximumfailures;
private string _lockoutduration;
private string _providertype;
private string _providername;
private string _authority;
private string _metadataurl;
private string _authorizationurl;
private string _tokenurl;
private string _userinfourl;
private string _clientid;
private string _clientsecret;
private string _clientsecrettype = "password";
private string _toggleclientsecret = string.Empty;
private string _scopes;
private string _parameters;
private string _pkce;
private string _redirecturl;
private string _identifierclaimtype;
private string _emailclaimtype;
private string _roleclaimtype;
private string _profileclaimtypes;
private string _domainfilter;
private string _createusers;
private string _providertype;
private string _providername;
private string _authority;
private string _metadataurl;
private string _authorizationurl;
private string _tokenurl;
private string _userinfourl;
private string _clientid;
private string _clientsecret;
private string _clientsecrettype = "password";
private string _toggleclientsecret = string.Empty;
private string _authresponsetype;
private string _scopes;
private string _parameters;
private string _pkce;
private string _redirecturl;
private string _reviewclaims;
private string _externalloginurl;
private string _identifierclaimtype;
private string _nameclaimtype;
private string _emailclaimtype;
private string _roleclaimtype;
private string _profileclaimtypes;
private string _domainfilter;
private string _createusers;
private string _verifyusers;
private string _secret;
private string _secrettype = "password";
private string _togglesecret = string.Empty;
private string _issuer;
private string _audience;
private string _lifetime;
private string _token;
private string _secret;
private string _secrettype = "password";
private string _togglesecret = string.Empty;
private string _issuer;
private string _audience;
private string _lifetime;
private string _token;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private bool isSortedAscending;
private string activeSortColumn;
protected override async Task OnInitializedAsync()
{
await LoadUserSettingsAsync();
await LoadUsersAsync(true);
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_allowregistration = PageState.Site.AllowRegistration.ToString();
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
protected override async Task OnInitializedAsync()
{
await LoadUsersAsync(true);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_allowregistration = PageState.Site.AllowRegistration.ToString();
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
_requiredigit = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireDigit", "true");
_requireupper = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireUppercase", "true");
_requirelower = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireLowercase", "true");
_requirepunctuation = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true");
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
_requiredigit = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireDigit", "true");
_requireupper = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireUppercase", "true");
_requirelower = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireLowercase", "true");
_requirepunctuation = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true");
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = SharedLocalizer["ShowPassword"];
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
_togglesecret = SharedLocalizer["ShowPassword"];
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
}
}
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = SharedLocalizer["ShowPassword"];
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false");
_externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
private async Task LoadUsersAsync(bool load)
{
if (load)
{
allusers = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
allusers.AddRange(hosts);
allusers = allusers.OrderBy(u => u.User.DisplayName).ToList();
}
}
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
_togglesecret = SharedLocalizer["ShowPassword"];
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
}
}
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 LoadUsersAsync(bool load)
{
if (load)
{
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
users.AddRange(hosts);
users = users.OrderBy(u => u.User.DisplayName).ToList();
}
}
}
private async Task DeleteUser(UserRole UserRole)
@ -520,21 +568,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()
{
try
@ -550,6 +583,8 @@ else
{
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
@ -570,17 +605,20 @@ else
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ReviewClaims", _reviewclaims, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:NameClaimType", _nameclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
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:Issuer", _issuer, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
@ -654,4 +692,43 @@ else
_togglesecret = SharedLocalizer["ShowPassword"];
}
}
private void SortTable(string columnName)
{
if (columnName != activeSortColumn)
{
users = users.OrderBy(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
isSortedAscending = true;
activeSortColumn = columnName;
}
else
{
if (isSortedAscending)
{
users = users.OrderByDescending(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
}
else
{
users = users.OrderBy(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
}
isSortedAscending = !isSortedAscending;
}
}
private string SetSortIcon(string columnName)
{
if (activeSortColumn != columnName)
{
return "app-fas pe-3 ";
}
if (isSortedAscending)
{
return "app-fas oi oi-sort-ascending";
}
else
{
return "app-fas oi oi-sort-descending";
}
}
}

View File

@ -34,13 +34,13 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input id="effectiveDate" class="form-control" @bind="@effectivedate" />
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input id="expiryDate" class="form-control" @bind="@expirydate" />
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
</div>
</div>
@ -60,8 +60,8 @@ else
</Header>
<Row>
<td>@context.Role.Name</td>
<td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td>
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</td>
<td>
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
</td>
@ -75,8 +75,8 @@ else
private string name = string.Empty;
private List<Role> roles;
private int roleid = -1;
private string effectivedate = string.Empty;
private string expirydate = string.Empty;
private DateTime? _effectivedate = null;
private DateTime? _expirydate = null;
private List<UserRole> userroles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
@ -92,7 +92,7 @@ else
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
}
else
{
@ -113,6 +113,7 @@ else
try
{
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, userid);
}
catch (Exception ex)
{
@ -127,26 +128,16 @@ else
{
if (roleid != -1)
{
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
{
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
return;
}
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null)
{
if (string.IsNullOrEmpty(effectivedate))
{
userrole.EffectiveDate = null;
}
else
{
userrole.EffectiveDate = DateTime.Parse(effectivedate);
}
if (string.IsNullOrEmpty(expirydate))
{
userrole.ExpiryDate = null;
}
else
{
userrole.ExpiryDate = DateTime.Parse(expirydate);
}
userrole.EffectiveDate = _effectivedate;
userrole.ExpiryDate = _expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
}
else
@ -154,32 +145,15 @@ else
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
if (string.IsNullOrEmpty(effectivedate))
{
userrole.EffectiveDate = null;
}
else
{
userrole.EffectiveDate = DateTime.Parse(effectivedate);
}
if (string.IsNullOrEmpty(expirydate))
{
userrole.ExpiryDate = null;
}
else
{
userrole.ExpiryDate = DateTime.Parse(expirydate);
}
userrole.EffectiveDate = Utilities.UtcAsLocalDate(_effectivedate);
userrole.ExpiryDate = Utilities.UtcAsLocalDate(_expirydate);
await UserRoleService.AddUserRoleAsync(userrole);
}
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
AddModuleMessage(Localizer["Success.User.AssignRole"], MessageType.Success);
await GetUserRoles();
StateHasChanged();
StateHasChanged();
}
else
{

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

@ -78,7 +78,7 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retention" />
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -104,7 +104,7 @@ else
private List<Visitor> _visitors;
private string _tracking;
private string _filter = "";
private string _retention = "";
private int _retention = 30;
private string _correlation = "true";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -129,7 +129,7 @@ else
_tracking = PageState.Site.VisitorTracking.ToString();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
_retention = SettingService.GetSetting(settings, "VisitorRetention", "30");
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
}
@ -180,7 +180,7 @@ else
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention, true);
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true);
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Modules.Controls
@using System.Text.Json
@inherits LocalizableComponent
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_visible)
{
@ -20,7 +21,7 @@
{
<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>

View File

@ -24,116 +24,135 @@
}
@code {
private string _text = string.Empty;
private string _parameters = string.Empty;
private string _url = string.Empty;
private List<Permission> _permissions;
private bool _editmode = false;
private bool _authorized = false;
private string _classname = "btn btn-primary";
private string _style = string.Empty;
private string _iconSpan = string.Empty;
private string _text = string.Empty;
private int _moduleId = -1;
private string _path = string.Empty;
private string _parameters = string.Empty;
private string _url = string.Empty;
private List<Permission> _permissions;
private bool _editmode = false;
private bool _authorized = false;
private string _classname = "btn btn-primary";
private string _style = string.Empty;
private string _iconSpan = string.Empty;
[Parameter]
public string Action { get; set; } // required
[Parameter]
public string Action { get; set; } // required
[Parameter]
public string Text { get; set; } // optional - defaults to Action if not specified
[Parameter]
public string Text { get; set; } // optional - defaults to Action if not specified
[Parameter]
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y"
[Parameter]
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
[Parameter]
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
[Parameter]
public string Path { get; set; } = null; // optional - allows the link to target a specific page
[Parameter]
public Action OnClick { get; set; } = null; // optional - executes a method in the calling component
[Parameter]
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y"
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public Action OnClick { get; set; } = null; // optional - executes a method in the calling component
[Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
[Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public bool Disabled { get; set; } // optional
[Parameter]
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
[Parameter]
public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false.
[Parameter]
public bool Disabled { get; set; } // optional
[Parameter]
public string Class { get; set; } // optional - defaults to primary if not specified
[Parameter]
public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false.
[Parameter]
public string Style { get; set; } // optional
[Parameter]
public string Class { get; set; } // optional - defaults to primary if not specified
[Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
[Parameter]
public string Style { get; set; } // optional
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in link
[Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
[Parameter]
public string ReturnUrl { get; set; } // optional - used to set a url to redirect to
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in link
protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
[Parameter]
public string ReturnUrl { get; set; } // optional - used to set a url to redirect to
protected override void OnParametersSet()
{
base.OnParametersSet();
_text = Action;
if (!string.IsNullOrEmpty(Text))
{
_text = Text;
}
protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
if (IconOnly && !string.IsNullOrEmpty(IconName))
{
_text = string.Empty;
}
protected override void OnParametersSet()
{
base.OnParametersSet();
if (!string.IsNullOrEmpty(Parameters))
{
_parameters = Parameters;
}
_text = Action;
if (!string.IsNullOrEmpty(Text))
{
_text = Text;
}
if (!string.IsNullOrEmpty(Class))
{
_classname = Class;
}
if (IconOnly && !string.IsNullOrEmpty(IconName))
{
_text = string.Empty;
}
if (!string.IsNullOrEmpty(Style))
{
_style = Style;
}
_moduleId = ModuleState.ModuleId;
if (ModuleId != -1)
{
_moduleId = ModuleId;
}
if (!string.IsNullOrEmpty(EditMode))
{
_editmode = bool.Parse(EditMode);
}
_path = PageState.Page.Path;
if (Path != null)
{
_path = Path;
}
if (!string.IsNullOrEmpty(IconName))
{
if (!IconName.Contains(" "))
{
IconName = "oi oi-" + IconName;
}
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
}
if (!string.IsNullOrEmpty(Parameters))
{
_parameters = Parameters;
}
if (!string.IsNullOrEmpty(Class))
{
_classname = Class;
}
if (!string.IsNullOrEmpty(Style))
{
_style = Style;
}
if (!string.IsNullOrEmpty(EditMode))
{
_editmode = bool.Parse(EditMode);
}
if (!string.IsNullOrEmpty(IconName))
{
if (!IconName.Contains(" "))
{
IconName = "oi oi-" + IconName;
}
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
}
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_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))
{
_url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}";

View File

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

View File

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

View File

@ -3,6 +3,8 @@
@inherits ModuleControlBase
@inject IFolderService FolderService
@inject IFileService FileService
@inject ISettingService SettingService
@inject IUserService UserService
@inject IStringLocalizer<FileManager> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -40,10 +42,17 @@
</div>
</div>
}
else
{
if (FileId != -1 && _file != null && !UploadMultiple)
{
<input class="form-control" @bind="@_file.Name" disabled />
}
}
@if (ShowUpload && _haseditpermission)
{
<div class="row">
<div class="col mt-2">
<div class="row mt-2">
<div class="col">
@if (UploadMultiple)
{
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
@ -53,17 +62,35 @@
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
}
</div>
<div class="col mt-2 text-end">
<div class="col-auto">
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
@if (ShowFiles && GetFileId() != -1)
@if (FileId != -1 && !UploadMultiple)
{
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
}
</div>
</div>
<div class="row">
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
<div class="col text-center mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
@if (ShowProgress)
{
<div class="row">
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
<div class="col mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
</div>
}
else
{
if (_uploading)
{
<div class="app-progress-indicator"></div>
}
}
}
@if (!string.IsNullOrEmpty(_message))
{
<div class="row mt-1">
<div class="col">
<ModuleMessage Message="@_message" Type="@_messagetype" />
</div>
</div>
}
</div>
@ -75,348 +102,415 @@
</div>
}
</div>
@if (!string.IsNullOrEmpty(_message))
{
<div class="row mt-1">
<div class="col">
<ModuleMessage Message="@_message" Type="@_messagetype" />
</div>
</div>
}
</div>
}
@code {
private bool _initialized = false;
private List<Folder> _folders;
private List<File> _files = new List<File>();
private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty;
private string _progressbarid = string.Empty;
private string _filter = "*";
private bool _haseditpermission = false;
private string _image = string.Empty;
private File _file = null;
private string _guid;
private string _message = string.Empty;
private MessageType _messagetype;
private bool _initialized = false;
private List<Folder> _folders;
private List<File> _files = new List<File>();
private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty;
private string _progressbarid = string.Empty;
private string _filter = "*";
private bool _haseditpermission = false;
private string _image = string.Empty;
private File _file = null;
private string _guid;
private string _message = string.Empty;
private MessageType _messagetype;
private bool _uploading = false;
[Parameter]
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
[Parameter]
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
[Parameter]
public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid
[Parameter]
public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid
[Parameter]
public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path
[Parameter]
public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path
[Parameter]
public int FileId { get; set; } = -1; // optional - for selecting a specific file by default
[Parameter]
public int FileId { get; set; } = -1; // optional - for selecting a specific file by default
[Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
[Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
[Parameter]
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
[Parameter]
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
[Parameter]
public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true
[Parameter]
public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true
[Parameter]
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter]
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter]
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
[Parameter]
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
[Parameter]
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
[Parameter]
public bool ShowProgress { get; set; } = true; // optional - for indicating whether progress info should be displayed during upload - default is true
[Parameter]
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter]
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
[Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
[Parameter]
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter]
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
[Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
[Parameter]
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
[Parameter]
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
protected override async Task OnInitializedAsync()
{
// packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder)
{
ShowFiles = false;
ShowFolders = false;
Filter = "nupkg";
ShowSuccess = true;
}
[Parameter]
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
if (!ShowFiles)
{
ShowImage = false;
}
protected override void OnInitialized()
{
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid;
}
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
protected override async Task OnParametersSetAsync()
{
// packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder)
{
ShowFiles = false;
ShowFolders = false;
Filter = "nupkg";
ShowSuccess = true;
}
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
{
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
if (folder != null)
{
FolderId = folder.FolderId;
}
else
{
FolderId = -1;
_message = "Folder Path " + Folder + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
{
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
if (folder != null)
{
FolderId = folder.FolderId;
}
else
{
FolderId = -1;
_message = "Folder Path " + Folder + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
if (FileId != -1)
{
File file = await FileService.GetFileAsync(FileId);
if (file != null)
{
FolderId = file.FolderId;
await OnSelect.InvokeAsync(FileId);
}
else
{
FileId = -1; // file does not exist
_message = "FileId " + FileId.ToString() + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
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 };
}
}
}
await SetImage();
if (FileId != -1)
{
File file = await FileService.GetFileAsync(FileId);
if (file != null)
{
FolderId = file.FolderId;
}
else
{
FileId = -1; // file does not exist
_message = "FileId " + FileId.ToString() + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
if (!string.IsNullOrEmpty(Filter))
{
_filter = "." + Filter.Replace(",", ",.");
}
await SetImage();
await GetFiles();
if (!string.IsNullOrEmpty(Filter))
{
_filter = "." + Filter.Replace(",", ",.");
}
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid;
await GetFiles();
_initialized = true;
}
_initialized = true;
}
private async Task GetFiles()
{
_haseditpermission = false;
if (Folder == Constants.PackagesFolder)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = new List<File>();
}
else
{
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
_haseditpermission = false;
_files = new List<File>();
}
}
if (_filter != "*")
{
List<File> filtered = new List<File>();
foreach (File file in _files)
{
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
{
filtered.Add(file);
}
}
_files = filtered;
}
}
private async Task GetFiles()
{
_haseditpermission = false;
if (Folder == Constants.PackagesFolder)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = new List<File>();
}
else
{
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
{
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
_files = new List<File>();
}
}
else
{
_haseditpermission = false;
_files = new List<File>();
}
if (_filter != "*")
{
List<File> filtered = new List<File>();
foreach (File file in _files)
{
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
{
filtered.Add(file);
}
}
_files = filtered;
}
}
}
private async Task FolderChanged(ChangeEventArgs e)
{
_message = string.Empty;
try
{
FolderId = int.Parse((string)e.Value);
await GetFiles();
FileId = -1;
_file = null;
_image = string.Empty;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error;
}
}
private async Task FolderChanged(ChangeEventArgs e)
{
_message = string.Empty;
try
{
FolderId = int.Parse((string)e.Value);
await GetFiles();
FileId = -1;
_file = null;
_image = string.Empty;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error;
}
}
private async Task FileChanged(ChangeEventArgs e)
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
if (FileId != -1)
{
await OnSelect.InvokeAsync(FileId);
}
private async Task FileChanged(ChangeEventArgs e)
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
await SetImage();
await OnSelect.InvokeAsync(FileId);
StateHasChanged();
await SetImage();
StateHasChanged();
}
}
private async Task SetImage()
{
_image = string.Empty;
_file = null;
if (FileId != -1)
{
_file = await FileService.GetFileAsync(FileId);
if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
{
var maxwidth = 200;
var maxheight = 200;
private async Task SetImage()
{
_image = string.Empty;
_file = null;
if (FileId != -1)
{
_file = await FileService.GetFileAsync(FileId);
if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
{
var maxwidth = 200;
var maxheight = 200;
var ratioX = (double)maxwidth / (double)_file.ImageWidth;
var ratioY = (double)maxheight / (double)_file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY;
var ratioX = (double)maxwidth / (double)_file.ImageWidth;
var ratioY = (double)maxheight / (double)_file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY;
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
}
}
}
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
}
}
}
private async Task UploadFiles()
{
_message = string.Empty;
var interop = new Interop(JSRuntime);
var uploads = await interop.GetFiles(_fileinputid);
if (uploads.Length > 0)
{
string restricted = "";
foreach (var upload in uploads)
{
var extension = (upload.LastIndexOf(".") != -1) ? upload.Substring(upload.LastIndexOf(".") + 1) : "";
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{
restricted += (restricted == "" ? "" : ",") + extension;
}
}
if (restricted == "")
{
try
{
// upload the files
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken);
private async Task UploadFiles()
{
_message = string.Empty;
var interop = new Interop(JSRuntime);
var uploads = await interop.GetFiles(_fileinputid);
// uploading is asynchronous so we need to wait for the uploads to complete
// note that this will only wait a maximum of 15 seconds which may not be long enough for very large file uploads
bool success = false;
int attempts = 0;
while (attempts < 5 && !success)
{
attempts += 1;
Thread.Sleep(1000 * attempts); // progressive retry
if (uploads.Length > 0)
{
string restricted = "";
foreach (var upload in uploads)
{
var filename = upload.Split(':')[0];
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
{
restricted += (restricted == "" ? "" : ",") + extension;
}
}
if (restricted == "")
{
if (!ShowProgress)
{
_uploading = true;
StateHasChanged();
}
success = true;
List<File> files = await FileService.GetFilesAsync(folder);
if (files.Count > 0)
{
foreach (string upload in uploads)
{
if (!files.Exists(item => item.Name == upload))
{
success = false;
}
}
}
}
try
{
// upload the files
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
var jwt = "";
if (PageState.Runtime == Shared.Runtime.Hybrid)
{
jwt = await UserService.GetTokenAsync();
}
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt);
// reset progress indicators
await interop.SetElementAttribute(_guid + "ProgressInfo", "style", "display: none;");
await interop.SetElementAttribute(_guid + "ProgressBar", "style", "display: none;");
// uploading is asynchronous so we need to poll to determine if uploads are completed
var success = true;
int upload = 0;
while (upload < uploads.Length && success)
{
success = false;
var filename = uploads[upload].Split(':')[0];
if (success)
{
await logger.LogInformation("File Upload Succeeded {Files}", uploads);
if (ShowSuccess)
{
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
}
else
{
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
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
// set FileId to first file in upload collection
await GetFiles();
var file = _files.Where(item => item.Name == uploads[0]).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
}
else
{
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
_messagetype = MessageType.Warning;
}
}
else
{
_message = Localizer["Message.File.NotSelected"];
_messagetype = MessageType.Warning;
}
}
int attempts = 0;
while (attempts < maxattempts && !success)
{
attempts += 1;
Thread.Sleep(sleep);
private async Task DeleteFile()
{
_message = string.Empty;
try
{
await FileService.DeleteFileAsync(FileId);
await logger.LogInformation("File Deleted {File}", FileId);
await OnDelete.InvokeAsync(FileId);
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)
{
success = true;
}
}
}
if (success)
{
upload++;
}
}
_message = Localizer["Success.File.Delete"];
_messagetype = MessageType.Success;
// reset progress indicators
if (ShowProgress)
{
await interop.SetElementAttribute(_progressinfoid, "style", "display: none;");
await interop.SetElementAttribute(_progressbarid, "style", "display: none;");
}
else
{
_uploading = false;
StateHasChanged();
}
if (success)
{
await logger.LogInformation("File Upload Succeeded {Files}", uploads);
if (ShowSuccess)
{
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
}
else
{
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
if (Folder == Constants.PackagesFolder)
{
await OnUpload.InvokeAsync(-1);
}
else
{
// set FileId to first file in upload collection
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnSelect.InvokeAsync(FileId);
await OnUpload.InvokeAsync(FileId);
}
await GetFiles();
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
_uploading = false;
}
}
else
{
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
_messagetype = MessageType.Warning;
}
}
else
{
_message = Localizer["Message.File.NotSelected"];
_messagetype = MessageType.Warning;
}
}
private async Task DeleteFile()
{
_message = string.Empty;
try
{
await FileService.DeleteFileAsync(FileId);
await logger.LogInformation("File Deleted {File}", FileId);
await OnDelete.InvokeAsync(FileId);
if (ShowSuccess)
{
_message = Localizer["Success.File.Delete"];
_messagetype = MessageType.Success;
}
await GetFiles();
FileId = -1;
await SetImage();
StateHasChanged();
await OnSelect.InvokeAsync(FileId);
StateHasChanged();
}
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
@inherits ModuleControlBase
@inject IStringLocalizerFactory LocalizerFactory
@inject IStringLocalizer<SharedResources> SharedLocalizer
@typeparam TableItem
@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)
{
<ul class="pagination justify-content-center my-2">
@ -175,6 +185,9 @@
private int _startPage = 0;
private int _endPage = 0;
private int _columns = 0;
private string _search = "";
private IEnumerable<TableItem> AllItems;
[Parameter]
public string Format { get; set; } // Table or Grid
@ -221,6 +234,9 @@
[Parameter]
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; }
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))
{
_maxItems = int.Parse(PageSize);
@ -323,10 +348,10 @@
{
_endPage = _pages;
}
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
StateHasChanged();
OnPageChange?.Invoke(_page);
}
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
StateHasChanged();
OnPageChange?.Invoke(_page);
}
public void UpdateList(int page)
{
@ -369,4 +394,75 @@
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

@ -1,79 +1,86 @@
@using System.Text.RegularExpressions
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@inject ISettingService SettingService
@inject IStringLocalizer<RichTextEditor> Localizer
<div class="row" style="margin-bottom: 50px;">
<div class="col">
<TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor">
@if (_richfilemanager)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
@if (AllowRawHtml)
{
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>@((MarkupString)"&nbsp;&nbsp;")
}
@if (AllowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
}
@if (_richfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
}
</div>
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@if (ToolbarContent != null)
{
@ToolbarContent
}
else
{
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
}
</div>
<div @ref="@_editorElement">
</div>
</div>
</div>
</TabPanel>
@if (AllowRichText)
{
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@if (_richfilemanager)
{
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
@if (AllowRawHtml)
{
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>
@((MarkupString)"&nbsp;&nbsp;")
}
@if (AllowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
}
@if (_richfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
}
</div>
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@if (ToolbarContent != null)
{
@ToolbarContent
}
else
{
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
}
</div>
<div @ref="@_editorElement">
</div>
</div>
</div>
</TabPanel>
}
@if (AllowRawHtml)
{
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
@if (_rawfilemanager)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
@ -104,119 +111,134 @@
</div>
@code {
private ElementReference _editorElement;
private ElementReference _toolBar;
private bool _richfilemanager = false;
private FileManager _fileManager;
private string _richhtml = string.Empty;
private string _originalrichhtml = string.Empty;
private bool _rawfilemanager = false;
private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty;
private string _message = string.Empty;
private ElementReference _editorElement;
private ElementReference _toolBar;
private bool _richfilemanager = false;
private FileManager _fileManager;
private string _richhtml = string.Empty;
private string _originalrichhtml = string.Empty;
private bool _rawfilemanager = false;
private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty;
private string _message = string.Empty;
[Parameter]
public string Content { get; set; }
[Parameter]
public string Content { get; set; }
[Parameter]
public bool ReadOnly { get; set; } = false;
[Parameter]
public bool ReadOnly { get; set; } = false;
[Parameter]
public string Placeholder { get; set; } = "Enter Your Content...";
[Parameter]
public string Placeholder { get; set; } = "Enter Your Content...";
[Parameter]
public bool AllowFileManagement { get; set; } = true;
[Parameter]
public bool AllowFileManagement { get; set; } = true;
[Parameter]
public bool AllowRawHtml { get; set; } = true;
// parameters only applicable to rich text editor
[Parameter]
public RenderFragment ToolbarContent { get; set; }
[Parameter]
public bool AllowRichText { get; set; } = true;
[Parameter]
public string Theme { get; set; } = "snow";
[Parameter]
public bool AllowRawHtml { get; set; } = true;
[Parameter]
public string DebugLevel { get; set; } = "info";
// parameters only applicable to rich text editor
[Parameter]
public RenderFragment ToolbarContent { get; set; }
public override List<Resource> Resources => new List<Resource>()
[Parameter]
public string Theme { get; set; } = "snow";
[Parameter]
public string DebugLevel { get; set; } = "info";
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" }
};
protected override void OnParametersSet()
{
_richhtml = Content;
_rawhtml = Content;
_originalrawhtml = _rawhtml; // preserve for comparison later
}
protected override void OnParametersSet()
{
_richhtml = Content;
_rawhtml = Content;
_originalrawhtml = _rawhtml; // preserve for comparison later
_originalrichhtml = "";
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
var interop = new RichTextEditorInterop(JSRuntime);
if (AllowRichText)
{
var interop = new RichTextEditorInterop(JSRuntime);
if (firstRender)
{
await interop.CreateEditor(
_editorElement,
_toolBar,
ReadOnly,
Placeholder,
Theme,
DebugLevel);
if (firstRender)
{
await interop.CreateEditor(
_editorElement,
_toolBar,
ReadOnly,
Placeholder,
Theme,
DebugLevel);
}
await interop.LoadEditorContent(_editorElement, _richhtml);
await interop.LoadEditorContent(_editorElement, _richhtml);
// preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
_originalrichhtml = await interop.GetHtml(_editorElement);
}
}
if (string.IsNullOrEmpty(_originalrichhtml))
{
// preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
_originalrichhtml = await interop.GetHtml(_editorElement);
}
}
}
public void CloseRichFileManager()
{
_richfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseRichFileManager()
{
_richfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseRawFileManager()
{
_rawfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseRawFileManager()
{
_rawfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void RefreshRichText()
{
_richhtml = _rawhtml;
StateHasChanged();
}
public void RefreshRichText()
{
_richhtml = _rawhtml;
StateHasChanged();
}
public async Task RefreshRawHtml()
{
var interop = new RichTextEditorInterop(JSRuntime);
_rawhtml = await interop.GetHtml(_editorElement);
StateHasChanged();
}
public async Task RefreshRawHtml()
{
var interop = new RichTextEditorInterop(JSRuntime);
_rawhtml = await interop.GetHtml(_editorElement);
StateHasChanged();
}
public async Task<string> GetHtml()
{
// evaluate raw html content as first priority
if (_rawhtml != _originalrawhtml)
{
return _rawhtml;
}
else
{
// return rich text content if it has changed
var interop = new RichTextEditorInterop(JSRuntime);
var richhtml = await interop.GetHtml(_editorElement);
if (richhtml != _originalrichhtml)
public async Task<string> GetHtml()
{
// evaluate raw html content as first priority
if (_rawhtml != _originalrawhtml)
{
return _rawhtml;
}
else
{
var richhtml = "";
if (AllowRichText)
{
// return rich text content if it has changed
var interop = new RichTextEditorInterop(JSRuntime);
richhtml = await interop.GetHtml(_editorElement);
}
// rich text value will only be blank if AllowRichText is disabled or the JS Interop method failed
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml) && !string.IsNullOrEmpty(_originalrichhtml))
{
return richhtml;
}
@ -238,7 +260,8 @@
{
var interop = new RichTextEditorInterop(JSRuntime);
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
_richfilemanager = false;
_richhtml = await interop.GetHtml(_editorElement);
_richfilemanager = false;
}
else
{

View File

@ -1,24 +1,30 @@
@namespace Oqtane.Modules.Controls
@inherits LocalizableComponent
<div class="d-flex mt-2">
<div>
<a data-bs-toggle="collapse" class="app-link-unstyled" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
<h5>@_heading</h5>
</a>
@if (IsVisible)
{
<div class="d-flex mt-2">
<div>
<a data-bs-toggle="collapse" class="app-link-unstyled" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
<h5>@_heading</h5>
</a>
</div>
<div class="ms-auto">
<a data-bs-toggle="collapse" class="app-link-unstyled float-right" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
<i class="oi oi-chevron-bottom"></i>&nbsp;
</a>
</div>
</div>
<div class="ms-auto">
<a data-bs-toggle="collapse" class="app-link-unstyled float-right" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
<i class="oi oi-chevron-bottom"></i>&nbsp;
</a>
<div class="d-flex">
<hr class="app-rule" />
</div>
</div>
<div class="d-flex">
<hr class="app-rule" />
</div>
<div class="collapse @_show" id="@Name">
@ChildContent
</div>
<div class="collapse @_show" id="@Name">
@if (ChildContent != null)
{
@ChildContent
}
</div>
}
@code {
private string _heading = string.Empty;
@ -26,7 +32,7 @@
private string _show = string.Empty;
[Parameter]
public RenderFragment ChildContent { get; set; }
public RenderFragment ChildContent { get; set; } = null;
[Parameter]
public string Name { get; set; } // required - the name of the section
@ -37,19 +43,15 @@
[Parameter]
public string Expanded { get; set; } // optional - will default to false if not provided
protected override void OnInitialized()
{
_heading = (!string.IsNullOrEmpty(Heading)) ? Heading : Name;
_expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded : "false";
if (_expanded == "true") { _show = "show"; }
}
[Parameter]
public bool IsVisible { get; set; } = true;
protected override void OnParametersSet()
{
base.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";
if (_expanded == "true") { _show = "show"; }
}
}

View File

@ -8,7 +8,7 @@
@foreach (TabPanel tabPanel in _tabPanels)
{
<li class="nav-item" @key="tabPanel.Name">
@if (tabPanel.Name == ActiveTab)
@if (tabPanel.Name.ToLower() == ActiveTab.ToLower())
{
<a class="nav-link active" data-bs-toggle="tab" href="#@(Id + tabPanel.Name)" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()

View File

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

View File

@ -15,22 +15,20 @@
}
@code {
public override List<Resource> Resources => new List<Resource>()
private string content = "";
protected override async Task OnParametersSetAsync()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
private string content = "";
protected override async Task OnParametersSetAsync()
{
try
try
{
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
if (ShouldRender())
{
content = htmltext.Content;
content = Utilities.FormatContent(content, PageState.Alias, "render");
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
content = htmltext.Content;
content = Utilities.FormatContent(content, PageState.Alias, "render");
}
}
}
catch (Exception ex)

View File

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

View File

@ -9,6 +9,7 @@ using Oqtane.UI;
using System.Collections.Generic;
using Microsoft.JSInterop;
using System.Linq;
using System.Dynamic;
namespace Oqtane.Modules
{
@ -70,17 +71,33 @@ namespace Oqtane.Modules
{
if (firstRender)
{
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script))
List<Resource> resources = null;
var type = GetType();
if (type.BaseType == typeof(ModuleBase))
{
if (PageState.Page.Resources != null)
{
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList();
}
}
else // modulecontrolbase
{
if (Resources != null)
{
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
}
}
if (resources != null &&resources.Any())
{
var interop = new Interop(JSRuntime);
var scripts = new List<object>();
var inline = 0;
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
foreach (Resource resource in resources)
{
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
}
else
{
@ -96,6 +113,11 @@ namespace Oqtane.Modules
}
}
protected override bool ShouldRender()
{
return PageState?.RenderId == ModuleState?.RenderId;
}
// path method
public string ModulePath()
@ -104,6 +126,7 @@ namespace Oqtane.Modules
}
// url methods
public string NavigateUrl()
{
return NavigateUrl(PageState.Page.Path);
@ -243,7 +266,13 @@ namespace Oqtane.Modules
// UI methods
public void AddModuleMessage(string message, MessageType type)
{
ModuleInstance.AddModuleMessage(message, type);
AddModuleMessage(message, type, "top");
}
public void AddModuleMessage(string message, MessageType type, string position)
{
ClearModuleMessage();
ModuleInstance.AddModuleMessage(message, type, position);
}
public void ClearModuleMessage()
@ -263,16 +292,47 @@ namespace Oqtane.Modules
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;
}
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;
}
public void SetPageTitle(string title)
{
SiteState.Properties.PageTitle = title;
}
// note - only supports links and meta tags - not scripts
public void AddHeadContent(string content)
{
SiteState.AppendHeadContent(content);
}
public void AddScript(Resource resource)
{
resource.ResourceType = ResourceType.Script;
if (Resources == null) Resources = new List<Resource>();
if (!Resources.Any(item => (!string.IsNullOrEmpty(resource.Url) && item.Url == resource.Url) || (!string.IsNullOrEmpty(resource.Content) && item.Content == resource.Content)))
{
Resources.Add(resource);
}
}
public async Task ScrollToPageTop()
{
var interop = new Interop(JSRuntime);
await interop.ScrollTo(0, 0, "smooth");
}
// logging methods
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
{

View File

@ -1,19 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations>
<Version>3.4.2</Version>
<Version>5.0.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
<Description>Modular Application Framework for Blazor and MAUI</Description>
<Description>CMS and Application Framework for Blazor and .NET MAUI</Description>
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -22,13 +21,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
</ItemGroup>
<ItemGroup>

View File

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

View File

@ -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

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

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -150,4 +150,7 @@
<data name="Description.Text" xml:space="preserve">
<value>Description:</value>
</data>
<data name="File Management" xml:space="preserve">
<value>File Management</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -178,9 +178,21 @@
<value>Capacity:</value>
</data>
<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 name="ImageSizes.Text" xml:space="preserve">
<value>Image Sizes:</value>
</data>
<data name="FolderManagement.Title" xml:space="preserve">
<value>Folder Management!</value>
</data>
<data name="Private" xml:space="preserve">
<value>Private</value>
</data>
<data name="Public" xml:space="preserve">
<value>Public</value>
</data>
<data name="Folder Management" xml:space="preserve">
<value>Folder Management</value>
</data>
</root>

View File

@ -192,4 +192,7 @@
<data name="Once" xml:space="preserve">
<value>Execute Once</value>
</data>
<data name="Message.StartEndDateError" xml:space="preserve">
<value>Start Date cannot be after End Date.</value>
</data>
</root>

View File

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

View File

@ -132,11 +132,11 @@
<data name="IsDefault.Text" xml:space="preserve">
<value>Default?</value>
</data>
<data name="Success.Language.Install" xml:space="preserve">
<value>Translations Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
<data name="Success.Language.Download" xml:space="preserve">
<value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; To Complete The Installation.</value>
</data>
<data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translation packages. Once they are uploaded click Install to complete the installation.</value>
<value>Upload one or more translation packages.</value>
</data>
<data name="LanguageUpload.Text" xml:space="preserve">
<value>Translation</value>

View File

@ -117,61 +117,37 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Template.Text" xml:space="preserve">
<value>Template: </value>
<data name="Error.Language.Edit" xml:space="preserve">
<value>Error Updating Language</value>
</data>
<data name="Template.Select" xml:space="preserve">
<value>Select Template</value>
<data name="Name.HelpText" xml:space="preserve">
<value>Name Of The Langauage</value>
</data>
<data name="Module.Create" xml:space="preserve">
<value>Create Module</value>
<data name="IsDefault.HelpText" xml:space="preserve">
<value>Indicates Whether Or Not This Language Is The Default For The Site</value>
</data>
<data name="Module.Activate" xml:space="preserve">
<value>Activate Module</value>
<data name="Name.Text" xml:space="preserve">
<value>Name:</value>
</data>
<data name="Info.Module.Creator" xml:space="preserve">
<value>Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment</value>
<data name="IsDefault.Text" xml:space="preserve">
<value>Default?</value>
</data>
<data name="Info.Module.Activate" xml:space="preserve">
<value>Once You Have Compiled The Module And Restarted The Application You Can Activate The Module Below</value>
<data name="Success.Language.Download" xml:space="preserve">
<value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; To Complete The Installation.</value>
</data>
<data name="Success.Module.Create" 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>
<data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translation packages.</value>
</data>
<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>
<data name="LanguageUpload.Text" xml:space="preserve">
<value>Translation</value>
</data>
<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>
<data name="Manage.Heading" xml:space="preserve">
<value>Manage</value>
</data>
<data name="ModuleName.HelpText" xml:space="preserve">
<value>Enter a name for this module. It should not contain spaces or punctuation.</value>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</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 name="Error.Language.Load" xml:space="preserve">
<value>Error Loading Language</value>
</data>
</root>

View File

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

View File

@ -204,8 +204,8 @@
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
<value>Multiple User Accounts Already Exist With The Email Address Of Your External Login. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.InvalidEmail" xml:space="preserve">
<value>The External Login Provider Did Not Provide A Valid Email Address For Your Account. Please Contact Your Administrator For Further Instructions.</value>
<data name="ExternalLoginStatus.MissingClaims" xml:space="preserve">
<value>The External Login Provider Did Not Provide All Of The Required Information. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.ProviderKeyMismatch" xml:space="preserve">
<value>An Error Occurred Verifying Your External Login. Please Contact Your Administrator For Further Instructions.</value>
@ -225,4 +225,7 @@
<data name="ExternalLoginStatus.RemoteFailure" xml:space="preserve">
<value>Your External Login Failed. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.ReviewClaims" xml:space="preserve">
<value>The Review Claims Option Was Enabled In External Login Settings. Please Visit The Event Log To View The Claims Returned By The Provider.</value>
</data>
</root>

View File

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

View File

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

View File

@ -130,13 +130,16 @@
<value>Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment</value>
</data>
<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 Or Contain The Word "Oqtane" ) And Choose A Template</value>
</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">
<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 or contain the word "oqtane".</value>
</data>
<data name="ModuleName.HelpText" xml:space="preserve">
<value>Enter a name for this module. It should not contain spaces or punctuation.</value>
<value>Enter a name for this module. It should not contain spaces or punctuation or contain the word "oqtane".</value>
</data>
<data name="Description.HelpText" xml:space="preserve">
<value>Enter a short description for the module</value>
@ -168,4 +171,4 @@
<data name="Success.Module.Create" 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 Activate The Module.</value>
</data>
</root>
</root>

View File

@ -196,7 +196,7 @@
<value>Information</value>
</data>
<data name="PackageName.HelpText" xml:space="preserve">
<value>The unique name of the package from which this module was installed</value>
<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 name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value>
@ -211,12 +211,39 @@
<value>No Translations Exist For This Module Or Package Service Is Disabled</value>
</data>
<data name="Success.Translation.Download" xml:space="preserve">
<value>Translation Downloaded Successfully. Click Install To Complete Installation.</value>
</data>
<data name="Success.Translation.Install" xml:space="preserve">
<value>Translation Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
<value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Translations.Heading" xml:space="preserve">
<value>Translations</value>
</data>
<data name="Message.DuplicateName" xml:space="preserve">
<value>A Module With The Name Specified Already Exists</value>
</data>
<data name="IsEnabled.HelpText" xml:space="preserve">
<value>Is module enabled for this site?</value>
</data>
<data name="IsEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
<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>
<data name="Browse" xml:space="preserve">
<value>Browse</value>
</data>
<data name="Pages.Heading" xml:space="preserve">
<value>Pages</value>
</data>
</root>

View File

@ -127,7 +127,7 @@
<value>Error Downloading Module</value>
</data>
<data name="Confirm.Module.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete The {0} Module?</value>
<value>Are You Sure You Wish To Uninstall The {0} Module?</value>
</data>
<data name="Error.Module.Load" xml:space="preserve">
<value>Error Loading Modules</value>
@ -142,10 +142,13 @@
<value>Install Module</value>
</data>
<data name="DeleteModule.Header" xml:space="preserve">
<value>Delete Module</value>
<value>Uninstall Module</value>
</data>
<data name="DeleteModule.Text" xml:space="preserve">
<value>Uninstall</value>
</data>
<data name="InUse" xml:space="preserve">
<value>In Use</value>
<value>In Use?</value>
</data>
<data name="EditModule.Text" xml:space="preserve">
<value>Edit</value>
@ -153,4 +156,7 @@
<data name="Modules" xml:space="preserve">
<value>Modules</value>
</data>
<data name="Enabled" xml:space="preserve">
<value>Enabled?</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -132,4 +132,7 @@
<data name="Success.Content.Export" xml:space="preserve">
<value>Content Exported Successfully</value>
</data>
<data name="Export Content" xml:space="preserve">
<value>Export Content</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -138,4 +138,7 @@
<data name="Message.Required.ImportContent" xml:space="preserve">
<value>You Must Enter Some Content To Import</value>
</data>
<data name="Import Content" xml:space="preserve">
<value>Import Content</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -150,4 +150,31 @@
<data name="Error.Module.Load" xml:space="preserve">
<value>A Problem Was Encountered Loading Module {0}. The Module Is Either Invalid Or Does Not Exist.</value>
</data>
<data name="Module.HelpText" xml:space="preserve">
<value>The name of the module</value>
</data>
<data name="Module.Text" xml:space="preserve">
<value>Module:</value>
</data>
<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>
<data name="EffectiveDate.HelpText" xml:space="preserve">
<value>The date that this module is active</value>
</data>
<data name="EffectiveDate.Text" xml:space="preserve">
<value>Effective Date: </value>
</data>
<data name="ExpiryDate.HelpText" xml:space="preserve">
<value>The date that this module expires</value>
</data>
<data name="ExpiryDate.Text" xml:space="preserve">
<value>Expiry Date: </value>
</data>
</root>

View File

@ -150,8 +150,8 @@
<data name="Container.Select" xml:space="preserve">
<value>Select Container</value>
</data>
<data name="Error.Page.Initialize" xml:space="preserve">
<value>Error Initializing Page</value>
<data name="Error.Page.Load" xml:space="preserve">
<value>Error Loading Page</value>
</data>
<data name="Error.ChildPage.Load" xml:space="preserve">
<value>Error Loading Child Pages For Parent</value>
@ -228,13 +228,43 @@
<data name="Appearance.Name" xml:space="preserve">
<value>Appearance</value>
</data>
<data name="Meta.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value>
<data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
<data name="HeadContent.Text" xml:space="preserve">
<value>Head Content:</value>
</data>
<data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="ThemeChanged.Message" xml:space="preserve">
<value>Please Note That Overriding The Default Site Theme With An Unrelated Page Theme May Result In Compatibility Issues For Your Site</value>
</data>
<data name="Permissions.Heading" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="Theme.Heading" xml:space="preserve">
<value>Theme Settings</value>
</data>
<data name="EffectiveDate.HelpText" xml:space="preserve">
<value>The date that this page is active</value>
</data>
<data name="EffectiveDate.Text" xml:space="preserve">
<value>Effective Date: </value>
</data>
<data name="ExpiryDate.HelpText" xml:space="preserve">
<value>The date that this page expires</value>
</data>
<data name="ExpiryDate.Text" xml:space="preserve">
<value>Expiry Date: </value>
</data>
</root>

View File

@ -264,13 +264,37 @@
<data name="Clickable.Text" xml:space="preserve">
<value>Clickable?</value>
</data>
<data name="Meta.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value>
<data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
<data name="HeadContent.Text" xml:space="preserve">
<value>Head Content:</value>
</data>
<data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="ThemeChanged.Message" xml:space="preserve">
<value>Please Note That Overriding The Default Site Theme With An Unrelated Page Theme May Result In Compatibility Issues For Your Site</value>
</data>
<data name="EffectiveDate.HelpText" xml:space="preserve">
<value>The date that this page is active</value>
</data>
<data name="EffectiveDate.Text" xml:space="preserve">
<value>Effective Date: </value>
</data>
<data name="ExpiryDate.HelpText" xml:space="preserve">
<value>The date that this page expires</value>
</data>
<data name="ExpiryDate.Text" xml:space="preserve">
<value>Expiry Date: </value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -183,4 +183,22 @@
<data name="Private.Text" xml:space="preserve">
<value>Private? </value>
</data>
</root>
<data name="Validation.HelpText" xml:space="preserve">
<value>Optionally provide a regular expression (RegExp) for validating the value entered</value>
</data>
<data name="Validation.Text" xml:space="preserve">
<value>Validation: </value>
</data>
<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>
<data name="Autocomplete.HelpText" xml:space="preserve">
<value>The HTML autocomplete attribute allows you to specify browser behavior for automated user assistance in filling out form field values. Allowable values are blank (default), 'on', 'off', or any value from the standardized taxonomy defined for this attribute.</value>
</data>
<data name="Autocomplete.Text" xml:space="preserve">
<value>Autocomplete: </value>
</data>
</root>

View File

@ -138,4 +138,13 @@
<data name="EditProfile.Text" xml:space="preserve">
<value>Edit</value>
</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>

View File

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

View File

@ -136,7 +136,7 @@
<value>User Account Created. Please Check Your Email For Verification Instructions.</value>
</data>
<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 name="Message.Password.NoMatch" xml:space="preserve">
<value>Passwords Entered Do Not Match</value>
@ -177,19 +177,4 @@
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="Password.ValidationCriteria" xml:space="preserve">
<value>Passwords Must Have A Minimum Length Of {0} Characters, Including At Least {1} Unique Character(s), {2}{3}{4}{5} To Satisfy Password Compexity Requirements For This Site.</value>
</data>
<data name="Password.DigitRequirement" xml:space="preserve">
<value>At Least One Digit</value>
</data>
<data name="Password.LowercaseRequirement" xml:space="preserve">
<value>At Least One Lowercase Letter</value>
</data>
<data name="Password.PunctuationRequirement" xml:space="preserve">
<value>At Least One Punctuation Mark</value>
</data>
<data name="Password.UppercaseRequirement" xml:space="preserve">
<value>At Least One Uppercase Letter</value>
</data>
</root>

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