Compare commits

..

772 Commits

Author SHA1 Message Date
b7bbfe2a46 Merge pull request #1789 from sbwalker/dev
updated database provider references
2021-11-11 09:12:49 -05:00
69783b0709 updated database provider references 2021-11-11 09:21:01 -05:00
614041d55e update Blazor theme with bootstrap bundle js 2021-11-11 07:50:24 -05:00
856f61c2ee Merge pull request #1787 from sbwalker/dev
update Blazor theme with bootstrap bundle js
2021-11-11 07:42:10 -05:00
b0b196a522 Merge pull request #1786 from leigh-pointer/ActionMenu
Fix for Action Menus not displaying. #1785
2021-11-11 07:38:25 -05:00
62f04d239f Fix for Action Menus not displaying. #1785
Dropdowns are built on a third party library, Popper, which provides dynamic positioning and viewport detection.  / bootstrap.bundle.js which contains Popper. Popper isn’t used to position dropdowns in navbars though as dynamic positioning isn’t required.

updated the Bootstrap to reference the ../5.1.3/js/bootstrap.bundle.min.js
2021-11-11 10:27:09 +01:00
4210d10fca Merge pull request #1782 from leigh-pointer/Bootstrap
Upgrade to 5.1.3 Bootstrap and Bootswatch Cyborg
2021-11-10 17:25:27 -05:00
d02842f0ea Merge branch 'dev' into Bootstrap 2021-11-10 17:25:16 -05:00
2543b7db79 Upgrade to 5.1.3 Bootstrap and Bootswatch Cyborg
Fixed issue with OffCanvas not rendering properly.
2021-11-10 22:34:19 +01:00
41f430429b Merge pull request #1781 from sbwalker/dev
fix UX in module/theme creators
2021-11-10 15:48:36 -05:00
4ed4f8d942 fix UX in module/theme creators 2021-11-10 15:56:51 -05:00
cc5396801b update Test project dependencies 2021-11-10 13:12:38 -05:00
a72dc36d67 Merge pull request #1780 from sbwalker/dev
update Test project dependencies
2021-11-10 13:04:23 -05:00
50989e4e1e Merge pull request #1778 from sbwalker/dev
use Cloudflare CDN for static resources
2021-11-10 08:52:38 -05:00
41487440e3 use Cloudflare CDN for static resources 2021-11-10 09:00:48 -05:00
af72750354 Merge pull request #1776 from leigh-pointer/ReplaceRoot
[RootFolder] was missing from Release.cmd
2021-11-10 08:44:15 -05:00
a58dc49acc [RootFolder] was missing from Release.cmd
One of the replace tokens was not added to the second command line.
2021-11-10 14:26:26 +01:00
6dbb493d10 updating module and theme templates 2021-11-08 15:12:41 -05:00
b8c37ff5d7 Merge pull request #1772 from sbwalker/dev
updating module and theme templates
2021-11-08 15:04:29 -05:00
04319195c6 update to official .NET 6 release 2021-11-08 14:55:24 -05:00
aadd78b7ac Merge pull request #1771 from sbwalker/dev
update to official .NET 6 release
2021-11-08 14:47:12 -05:00
e23da1f5fb Update README.md 2021-11-06 07:53:54 -04:00
50eeaf8497 add support for TrustServerCertificate connection string option for SQL Server 2021-11-05 16:01:00 -04:00
039202559f Merge pull request #1764 from sbwalker/dev
add support for TrustServerCertificate connection string option for SQL Server
2021-11-05 15:53:00 -04:00
5419032e8d upgrade module and theme templates to .NET6 2021-11-05 12:53:13 -04:00
017a92c4bc Merge pull request #1763 from sbwalker/dev
upgrade module and theme templates to .NET6
2021-11-05 12:45:10 -04:00
a16040a595 remove unnecessary cascading parameter to improve efficiency 2021-11-05 09:03:12 -04:00
3f6936a999 Merge pull request #1762 from sbwalker/dev
remove unnecessary cascading parameter to improve efficiency
2021-11-05 08:55:03 -04:00
d3f3359f66 fix #1745 - error on WebAssembly when logging out 2021-11-04 08:06:28 -04:00
3f110aaabd Merge pull request #1761 from sbwalker/dev
fix #1745 - error on WebAssembly when logging out
2021-11-04 07:58:22 -04:00
4e884d57ca Merge pull request #1760 from leigh-pointer/Navigation
Navigation
2021-11-04 07:34:10 -04:00
efbe0562f9 Navigation was not completed 2021-11-04 06:09:19 +01:00
096dfea1a6 Merge remote-tracking branch 'oqtane/dev' into dev 2021-11-04 05:53:42 +01:00
bd5a827593 fix #1746 - SQL Server installation needs to allow configuration of encryption setting on .NET 6 2021-11-03 16:37:37 -04:00
404bcaddd4 Merge pull request #1759 from sbwalker/dev
fix #1746 - SQL Server installation needs to allow configuration of encryption setting on .NET 6
2021-11-03 16:29:45 -04:00
d2d52a7eb3 update SqlClient to latest preview version 2021-11-03 14:42:24 -04:00
1761c47713 Merge pull request #1758 from sbwalker/dev
update SqlClient to latest preview version
2021-11-03 14:34:28 -04:00
82e97aa4fa Merge pull request #1750 from leigh-pointer/BlazorTheme
Fix for #1736 Blazor theme not rendering correctly
2021-11-03 12:32:45 -04:00
e598178869 Merge pull request #1752 from leigh-pointer/PView
Fix for #1749 navigate to sub sub pages
2021-11-03 12:32:34 -04:00
b6f89195ab Merge pull request #1754 from leigh-pointer/1753
Update for #1753 Date format for the Audit
2021-11-03 12:31:43 -04:00
7aa92c039a Merge remote-tracking branch 'oqtane/dev' into dev 2021-11-02 20:06:37 +01:00
fff36949b7 Fix for #1749 navigate to sub sub pages
Also added missing "Browse" localization from site/index,resx
2021-11-02 19:59:59 +01:00
c524f17978 Merge pull request #1757 from sbwalker/dev
Fix #1751 - error when creating site with new tenant
2021-11-02 14:41:21 -04:00
e0a0497dd2 Fix #1751 - error when creating site with new tenant 2021-11-02 14:49:06 -04:00
fce9220dcb Update for #1753 Date format for the Audit
Added Parameter DateTimeFormat with default value of  "MMM dd yyyy HH:mm:ss"
2021-11-02 07:01:24 +01:00
a8ddb64b49 Fix for #1749 navigate to sub sub pages
Added Open button that will navigate to sub pages
2021-11-02 05:57:05 +01:00
6d8df2661c modification for responsive theme
small modification to ensure theme is responsive
2021-10-31 07:08:19 +01:00
1659de3a2b Fix for #1736 Server Css
Update to the Server file Theme.css
2021-10-28 20:35:48 +02:00
9752c72998 Fix for #1736 Blazor theme not rendering correctly
Fix to the Default theme and container
2021-10-28 19:43:51 +02:00
db2e3a518d Update README.md 2021-10-27 10:04:00 -04:00
94b20662a4 Update README.md 2021-10-26 08:37:09 -04:00
7bfc0998fd fix #1713 - link to home path displays login page 2021-10-26 08:30:50 -04:00
6707f0efdf Update README.md 2021-10-26 08:27:18 -04:00
c7fe5a538f Merge pull request #1735 from sbwalker/dev
fix #1713 - link to home path displays login page
2021-10-26 08:23:12 -04:00
9b20006938 removed hidden form fields which are unnecessary as a result of recent changes 2021-10-22 10:33:09 -04:00
0b258bd384 Merge pull request #1730 from sbwalker/dev
removed hidden form fields which are unnecessary as a result of recent changes
2021-10-22 10:25:41 -04:00
2302c17273 Update README.md 2021-10-19 16:21:13 -04:00
b619699637 Update README.md 2021-10-19 16:19:43 -04:00
34434e03fe Merge pull request #1725 from sbwalker/dev
upgrade to .NET 6 and increment version to 3.0.0
2021-10-19 15:27:19 -04:00
29bd31f609 upgrade to .NET 6 and increment version to 3.0.0 2021-10-19 15:33:03 -04:00
cf69f9e4c4 Add proper help text to aliases field in default resource file for Site Settings. Set default value for new ShowLogin parameter in Login theme component. 2021-10-17 13:27:12 -04:00
028c9bf0a8 Merge pull request #1720 from sbwalker/dev
Add proper help text to aliases field in default resource file for Site Settings. Set default value for new ShowLogin parameter in Login theme component.
2021-10-17 13:20:24 -04:00
3e9a4f2c1a Fixed validation issue in Role Managment - Users. Modified FileManager component to allow Folder parameter to contain a folder path which is translated to a FolderId internally and refactored Packages folder logic. 2021-10-06 17:20:44 -04:00
960543d14d Merge pull request #1709 from sbwalker/dev
Fixed validation issue in Role Managment - Users. Modified FileManager component to allow Folder parameter to contain a folder path which is translated to a FolderId internally and refactored Packages folder logic.
2021-10-06 17:14:45 -04:00
299674f53b Update README.md 2021-10-06 09:55:47 -04:00
306b78b526 Added ability for Runtime and RenderMode to be set per Site - enabling the framework to support multiple hosting models concurrently in the same installation. Fixed WebAssembly Prerendering issue (this also resolved the issue where the component taghelper was not passing parameters correctly to the app when running on WebAssembly). Fix #1702 - remove web,config from upgrade package. 2021-10-05 14:32:05 -04:00
f369382a54 Merge pull request #1708 from sbwalker/dev
Added ability for Runtime and RenderMode to be set per Site - enabling the framework to support multiple hosting models concurrently in the same installation. Fixed WebAssembly Prerendering issue (this also resolved the issue where the component taghelper was not passing parameters correctly to the app when running on WebAssembly). Fix #1702 - remove web,config from upgrade package.
2021-10-05 14:25:12 -04:00
ac67d88e74 fix logic which sometimes results in System.InvalidOperationException: The value of IsFixed cannot be changed dynamically 2021-10-01 15:58:17 -04:00
1f4f70009c Merge pull request #1704 from sbwalker/dev
fix logic which sometimes results in  System.InvalidOperationException: The value of IsFixed cannot be changed dynamically
2021-10-01 15:51:26 -04:00
838d918451 Merge pull request #1701 from leigh-pointer/1690-1
1690 User Management Tab needs clicking to render UI when language is not default.
2021-10-01 11:23:01 -04:00
8e6c73d2bc Merge pull request #1703 from sbwalker/dev
Allow root page paths (rather than specifying a magic "home" string). More UX improvements to FileManager and Pager.
2021-10-01 11:22:02 -04:00
aeb599867c Allow root page paths (rather than specifying a magic "home" string). More UX improvements to FileManager and Pager. 2021-10-01 11:28:48 -04:00
2fe93d4e64 Fix for #1690 Tab needs clicking to render UI
User Management Tab needs clicking to render UI when language is not default.  Modification to the TabPanel fixes the issue without  forcing the Heading property to be populated.
2021-09-29 18:05:59 +02:00
3e789e0642 UX improvements to FileManager and Pager components 2021-09-29 10:46:23 -04:00
a762f206a1 Merge pull request #1699 from sbwalker/dev
UX improvements to FileManager and Pager components
2021-09-29 10:39:43 -04:00
c0b13a1f09 2.3.1 database provider packages 2021-09-27 14:22:23 -04:00
070ddff1b4 Merge pull request #1697 from sbwalker/dev
2.3.1 database provider packages
2021-09-27 14:15:37 -04:00
9d0770e360 Merge pull request #1696 from oqtane/master
Merge pull request #1695 from oqtane/dev
2021-09-27 14:04:23 -04:00
088cb2a30e Merge pull request #1695 from oqtane/dev
2.3.1 release
2021-09-27 14:03:55 -04:00
c2be84a367 increment version to 2.3.1 2021-09-27 11:43:57 -04:00
4f61dd7bb3 Merge pull request #1694 from sbwalker/dev
increment version to 2.3.1
2021-09-27 11:37:34 -04:00
30fb6fd8e2 Merge pull request #1693 from sbwalker/dev
fix #1691 - AntiForgeryToken header not being set during startup
2021-09-27 08:37:27 -04:00
4bfb5d9f34 fix #1691 - AntiForgeryToken header not being set during startup 2021-09-27 08:44:16 -04:00
023f29491d Update README.md 2021-09-25 09:08:20 -04:00
5166fe2b41 Update README.md 2021-09-25 09:04:36 -04:00
71aa41d55e Update README.md 2021-09-25 09:03:44 -04:00
f6fd50f449 Update README.md 2021-09-25 09:02:06 -04:00
81e2f7c288 Update README.md 2021-09-25 09:01:40 -04:00
895d5e50de Merge pull request #1689 from oqtane/master
Merge pull request #1688 from oqtane/dev
2021-09-24 17:12:22 -04:00
6cd1b1ceaa Merge pull request #1688 from oqtane/dev
2.3.0 release
2021-09-24 17:11:32 -04:00
82ae9409f1 Merge branch 'dev' of https://github.com/sbwalker/oqtane.framework into dev 2021-09-24 17:07:04 -04:00
764b879a77 changes to build/publish params for WebAssembly 2021-09-24 17:06:45 -04:00
d3e71b6a7e Merge pull request #1687 from sbwalker/dev
changes to build/publish params for WebAssembly
2021-09-24 17:00:39 -04:00
80d23d1c95 Add paging to SQL Manager results 2021-09-23 18:02:15 -04:00
531e89346e Merge pull request #1685 from sbwalker/dev
Add paging to SQL Manager results
2021-09-23 17:55:44 -04:00
f220cb52bb Merge pull request #1682 from gjwalk/dev
Sites Validation
2021-09-23 17:12:36 -04:00
58ff42f813 Merge pull request #1683 from nicpitsch/pr_user-management-profile
Profile properties as dropdown in User Management
2021-09-23 17:10:33 -04:00
6b82b03bf1 Merge pull request #1684 from sbwalker/dev
Use ComponentTagHelper parameters on Blazor Server for passing state to allow pre-rendering to function properly ( ComponentTagHelper parameters do not work on Blazor WebAssembly - likely a .NET 5 bug )
2021-09-23 17:10:24 -04:00
005843ef2d Use ComponentTagHelper parameters on Blazor Server for passing state to allow pre-rendering to function properly ( ComponentTagHelper parameters do not work on Blazor WebAssembly - likely a .NET 5 bug ) 2021-09-23 17:16:51 -04:00
10917644ab Profile properties as dropdown in User Management (same as User Profile). 2021-09-23 10:05:24 +02:00
9fa3ade832 Sites Validation 2021-09-22 18:22:30 -04:00
e10135271d Update README.md 2021-09-22 16:37:57 -04:00
c1b482e0c0 check in latest database provider packages 2021-09-22 14:47:14 -04:00
a3d7760a09 Merge pull request #1681 from sbwalker/dev
check in latest database provider packages
2021-09-22 14:40:54 -04:00
57db7c1efc update version to 2.3.0 in preparation for release 2021-09-22 11:55:01 -04:00
586c9b6db6 Merge pull request #1680 from sbwalker/dev
update version to 2.3.0 in preparation for release
2021-09-22 11:48:23 -04:00
58a86b67ee fix #1672 - releases need to be published with IL trimming disabled or else dynamic methods will be stripped. Unfortunately compression needs to be disabled as well as if it is not, a "None of the “sha256” hashes in the integrity attribute match the content of the subresource." error is thrown. This seems to be a bug - which I will pursue with Microsoft. 2021-09-22 10:24:13 -04:00
43ebf50b61 Merge pull request #1679 from sbwalker/dev
fix #1672 - releases need to be published with IL trimming disabled or else dynamic methods will be stripped. Unfortunately compression needs to be disabled as well as if it is not, a "None of the “sha256” hashes in the integrity attribute match the content of the subresource." error is thrown. This seems to be a bug - which I will pursue with Microsoft.
2021-09-22 10:17:48 -04:00
5071cf4752 modify method for determining Runtime in SiteRouter as ComponentTagHelper "param-" appears to only work on Blazor Server - not on WebAssembly 2021-09-21 12:48:15 -04:00
00e2e79fc5 Merge pull request #1676 from sbwalker/dev
modify method for determining Runtime in SiteRouter as ComponentTagHelper "param-" appears to only work on Blazor Server - not on WebAssembly
2021-09-21 12:41:31 -04:00
8d37444755 improve validation for public site settings 2021-09-21 07:45:43 -04:00
20d81bee00 Merge pull request #1675 from sbwalker/dev
improve validation for public site settings
2021-09-21 07:39:35 -04:00
ca387d7b26 fix Oqtane theme settings for page scope 2021-09-20 17:23:56 -04:00
a0580f6861 Merge pull request #1674 from sbwalker/dev
fix Oqtane theme settings for page scope
2021-09-20 17:17:09 -04:00
f739db1e42 Enhance Settings API for public Site Settings. Added Settings to Site model by default. Added new parameters to Login and UserProfile components. Enhanced Oqtane Theme settings to use new component parameters. Enhanced image download and resizing logic. 2021-09-20 17:15:52 -04:00
d5bfe7cfbd Merge pull request #1673 from sbwalker/dev
Enhance Settings API for public Site Settings. Added Settings to Site model by default. Added new parameters to Login and UserProfile components. Enhanced Oqtane Theme settings to use new component parameters. Enhanced image download and resizing logic.
2021-09-20 17:09:52 -04:00
db85e088bf fix #1659 installation issue on PostgreSQL by ntroducing a new RewriteValue method which can be overridden in a database provider to provide custom behavior. Updated PostgreSQL provide to utilize new method. Also added an Oqtane.Server project reference to the module and theme external templates to streamline the development experience (credit @leighpointer). 2021-09-17 13:56:19 -04:00
d053901b32 Merge pull request #1670 from 2sic-forks/dev
Documentation only
2021-09-17 13:50:02 -04:00
2957c7d6a9 Merge pull request #1671 from sbwalker/dev
fix #1659 installation issue on PostgreSQL by ntroducing a new RewriteValue method which can be overridden in a database provider to provide custom behavior. Updated PostgreSQL provide to utilize new method. Also added an Oqtane.Server project reference to the module and theme external templates to streamline the development experience (credit @leighpointer).
2021-09-17 13:49:52 -04:00
a69d52a389 undo accidental / internal commit 2021-09-17 15:40:05 +02:00
7b5dbbd7ed undo change by 2sic 2021-09-17 15:36:23 +02:00
6d0fb6ca80 undo change by 2sxc 2021-09-17 15:35:43 +02:00
b4f7344ae4 removed unnecessary message from top of module, theme, language installation pages 2021-09-17 09:28:27 -04:00
a5cecb7354 Merge pull request #1669 from sbwalker/dev
removed unnecessary message from top of module, theme, language installation pages
2021-09-17 09:21:57 -04:00
406a15c5bd constrain file logger size 2021-09-17 09:17:42 -04:00
ed0408de95 Merge pull request #1668 from sbwalker/dev
constrain file logger size
2021-09-17 09:11:15 -04:00
b5bba1fd11 improved method for determining Runtime in SiteRouter 2021-09-17 09:06:27 -04:00
9065bad2bb Merge pull request #1667 from sbwalker/dev
improved method for determining Runtime in SiteRouter
2021-09-17 09:00:06 -04:00
289034fd4f fix #1640 - prevent UX "flicker" which was caused by pre-rendering 2021-09-17 08:48:01 -04:00
f052f6696f Merge pull request #1666 from sbwalker/dev
fix #1640 - prevent UX "flicker" which was caused by pre-rendering
2021-09-17 08:41:44 -04:00
267ca178ed added basic xml comments to all Oqtane.Services interfaces 2021-09-17 10:13:26 +02:00
c01c16c7bc Merge pull request #1660 from gjwalk/dev
site validation
2021-09-16 18:00:50 -04:00
2ac069082d Merge pull request #1663 from sbwalker/dev
file manager component improvements
2021-09-16 17:58:20 -04:00
f00ea09b6e file manager component improvements 2021-09-16 18:04:50 -04:00
b9259ce6ca added optional event callback delegates to FileManager component to allow calling components to be notified on upload, change, or delete 2021-09-16 07:59:36 -04:00
0490638657 Merge pull request #1662 from sbwalker/dev
added optional event callback delegates to FileManager component to allow calling components to be notified on upload, change, or delete
2021-09-16 07:53:31 -04:00
467b6ba9da site validation 2021-09-15 19:10:14 -04:00
423a42d25b fixed invalid XML comment 2021-09-15 17:07:40 +02:00
b496dc488c Merge branch 'oqtane-dev' into dev 2021-09-15 15:23:28 +02:00
9750723035 Merge branch 'dev' of https://github.com/oqtane/oqtane.framework into oqtane-dev
# Conflicts:
#	Oqtane.Client/App.razor
2021-09-15 15:23:16 +02:00
a4f147b547 Merge pull request #1655 from gjwalk/dev
roles validation
2021-09-15 07:58:21 -04:00
0827fedb74 Merge pull request #1658 from sbwalker/dev
Added support for File descriptions, Folder capacity and image sizes. Added image resizing capability using ImageSharp - implemented in user profile. Added parameter to disable image preview in FileManager component. Overhauled Pager component and added Columns parameter for Grid mode. Populated PageState.User.IsAuthenticated in SiteRouter. Added support for zero price commercial extentions.
2021-09-15 07:57:06 -04:00
898b908c1b Added support for File descriptions, Folder capacity and image sizes. Added image resizing capability using ImageSharp - implemented in user profile. Added parameter to disable image preview in FileManager component. Overhauled Pager component and added Columns parameter for Grid mode. Populated PageState.User.IsAuthenticated in SiteRouter. Added support for zero price commercial extentions. 2021-09-15 08:02:55 -04:00
f21b70a51e roles validation 2021-09-11 18:18:23 -04:00
ba7524b754 Merge pull request #1651 from leigh-pointer/RecycleCheck
Validate if Page in Recycle Bin During Creation
2021-09-10 13:06:59 -04:00
eb068a8d53 Merge pull request #1652 from sbwalker/dev
fix #1647 - module reordering on page issue
2021-09-10 13:05:35 -04:00
14fbc3a5b4 fix #1647 - module reordering on page issue 2021-09-10 13:12:00 -04:00
d2fa8902f9 Auto stash before rebase of "origin/RecycleCheck"
correction
2021-09-10 18:59:23 +02:00
53e5728ad2 fix #1640 to resolve issue with server prerendering, upgrade Installer to Bootstrap5, add more defensive logic and logging to DatabaseManager, fix file logger issue, update Pager to use Bootstrap5 pagination, add expiry date support for commercial extensions 2021-09-10 08:24:05 -04:00
dd7de055f6 Merge pull request #1650 from sbwalker/dev
fix #1640 to resolve issue with server prerendering,  upgrade Installer to Bootstrap5, add more defensive logic and logging to DatabaseManager, fix file logger issue, update Pager to use Bootstrap5 pagination, add expiry date support for commercial extensions
2021-09-10 08:17:52 -04:00
3cd7249750 Page create - Recycle Bin Check
After Delete Page, Cant create page of same name #1645 issue. Added check and message if the page is in the recycle bin.
2021-09-08 08:08:24 +02:00
07165ce68d add support for trial periods 2021-09-03 15:24:51 -04:00
2c0fe71f14 Merge pull request #1642 from sbwalker/dev
add support for trial periods
2021-09-03 15:18:58 -04:00
c74a53b301 update fix https://github.com/oqtane/oqtane.framework/issues/1640 2021-09-02 20:50:16 +02:00
3663f723e7 fix https://github.com/oqtane/oqtane.framework/issues/1640 2021-09-02 20:32:04 +02:00
233da1508b Replacing dependency on System.Drawing with SixLabors.ImageSharp based on cross platform guidance from Microsoft 9b4520703c/accepted/2021/system-drawing-win-only/system-drawing-win-only.md 2021-09-02 11:58:31 -04:00
ad34e9aeb8 Merge pull request #1639 from sbwalker/dev
Replacing dependency on System.Drawing with SixLabors.ImageSharp based on cross platform guidance from Microsoft 9b4520703c/accepted/2021/system-drawing-win-only/system-drawing-win-only.md
2021-09-02 11:54:40 -04:00
d29dc9d036 make containing class overridable in Control Panel ( header and body are already overridable ) 2021-09-01 09:13:09 -04:00
d71030fdef Merge pull request #1638 from sbwalker/dev
make containing class overridable in Control Panel ( header and body are already overridable )
2021-09-01 09:07:02 -04:00
bb5ca475d3 fix #1628 - make DBContext Transient, modify Control Panel to use standard Bootstrap 5 offcanvas classes, add auto trimming to file logger, fix issue in File Repository related to populating Url on Add/Update. 2021-09-01 09:01:11 -04:00
01c7a8fcdb Merge pull request #1637 from sbwalker/dev
fix #1628 - make DBContext Transient, modify Control Panel to use standard Bootstrap 5 offcanvas classes, add auto trimming to file logger, fix issue in File Repository related to populating Url on Add/Update.
2021-09-01 08:55:06 -04:00
f6c46878c6 add new overloads to client-side logging methods to include LogFunction enum parameter so that it can be specified explicitly rather than only being inferred from the page action 2021-08-30 08:46:53 -04:00
acda6bba74 Merge pull request #1634 from gjwalk/dev
reset validation
2021-08-30 08:42:23 -04:00
c8ee066608 Merge pull request #1635 from sbwalker/dev
add new overloads to client-side logging methods to include LogFunction enum parameter so that it can be specified explicitly rather than only being inferred from the page action
2021-08-30 08:40:55 -04:00
ca9fffaa71 reset validation 2021-08-29 21:03:35 -04:00
e00b7c9be9 add some missing localization keys 2021-08-27 17:29:45 -04:00
ee1a2b1810 Merge pull request #1631 from sbwalker/dev
add some missing localization keys
2021-08-27 17:23:37 -04:00
14266a99b3 Merge pull request #1614 from gjwalk/dev
register validation
2021-08-27 12:57:09 -04:00
7b105107cc Merge pull request #1630 from sbwalker/dev
fix #1617 convert line break to comma when saving aliases, improve license acceptance user experience
2021-08-27 08:11:54 -04:00
bb75242a4f fix #1617 convert line break to comma when saving aliases, improve license acceptance user experience 2021-08-27 08:17:48 -04:00
39ccc30680 fix Type label in Add Folder UI, make Profile description required, fix misc Bootstrap 5 cosmetic issues, fix #1618 Alias case sensitivity in router, fix File add and update methods so they return Url, fix UrlCombine helper method to use proper slash, enhance package installation to support commercial options 2021-08-26 18:20:58 -04:00
41651075e6 Merge pull request #1627 from horacioj/typo-Message.Required.Smtp
Fix typo for RESX message Message.Required.Smtp
2021-08-26 18:15:08 -04:00
e3af463e65 Merge pull request #1629 from sbwalker/dev
fix Type label in Add Folder UI, make Profile description required, fix misc Bootstrap 5 cosmetic issues, fix #1618 Alias case sensitivity in router, fix File add and update methods so they return Url, fix UrlCombine helper method to use proper slash, enhance package installation to support commercial options
2021-08-26 18:14:59 -04:00
5cbb8b1fa3 Fix typo for RESX message Message.Required.Smtp 2021-08-24 17:05:08 -03:00
2a7b74a0e1 Merge pull request #1622 from oqtane/revert-1603-dev
Revert "EntityBuilder UpdateColumn to dynamic type of value"
2021-08-20 16:32:18 -04:00
21fc493322 Revert "EntityBuilder UpdateColumn to dynamic type of value" 2021-08-20 16:32:04 -04:00
ea85eae4ce register validation 2021-08-18 10:55:34 -04:00
35ee97c145 Update LICENSE 2021-08-17 10:02:00 -04:00
097318cf9e make profile category optional 2021-08-17 08:13:17 -04:00
31d4f3d1b3 Merge pull request #1613 from sbwalker/dev
make profile category optional
2021-08-17 08:07:29 -04:00
83b3235a6b Merge pull request #1611 from gjwalk/dev
profiles validation
2021-08-17 08:02:57 -04:00
e47fc64c33 profiles validation 2021-08-16 14:37:11 -04:00
151a49f1ec Merge pull request #1610 from sbwalker/dev
fix #1607 - issue with setting Site Root when adding/editing a page
2021-08-16 11:33:05 -04:00
b78644f7e2 fix #1607 - issue with setting Site Root when adding/editing a page 2021-08-16 11:39:00 -04:00
d744e36dde Merge pull request #1603 from tomsync/dev
EntityBuilder UpdateColumn to dynamic type of value
2021-08-16 09:42:04 -04:00
90f4bd5120 Merge pull request #1605 from gjwalk/dev
pages validation
2021-08-16 09:41:09 -04:00
b436fcf749 Merge pull request #1609 from sbwalker/dev
support for commercial modules, themes, translations
2021-08-16 09:40:58 -04:00
ffcc229c78 support for commercial modules, themes, translations 2021-08-16 09:46:02 -04:00
ffe724b32d add support for free/paid in module, theme, translation installation 2021-08-13 15:56:22 -04:00
154d32cd31 Merge pull request #1606 from sbwalker/dev
add support for free/paid in module, theme, translation installation
2021-08-13 15:50:39 -04:00
7f056277ae pages validation 2021-08-12 14:48:23 -04:00
b19cbf54e0 add error handling to module export 2021-08-12 14:47:51 -04:00
aef0e363d8 Merge pull request #1604 from sbwalker/dev
add error handling to module export
2021-08-12 14:42:35 -04:00
6324034259 Merge pull request #1600 from gjwalk/dev
modules validation
2021-08-12 14:21:20 -04:00
5a1bada10c EntityBuilder UpdateColumn to dynamic type of value 2021-08-12 16:59:15 +07:00
ef90305bd7 modules validation 2021-08-09 12:38:12 -04:00
fe06a29ad2 Merge pull request #1594 from gjwalk/dev
moduleDefinitions validation
2021-08-09 11:24:00 -04:00
f6ae9822f3 Merge pull request #1599 from sbwalker/dev
fix #1593 - remove duplicate form tag from edit.razor
2021-08-09 11:21:56 -04:00
eb3e4916a8 fix #1593 - remove duplicate form tag from edit.razor 2021-08-09 11:27:39 -04:00
5a5535ea98 format license terms 2021-08-06 15:28:48 -04:00
4f8d0b1e7a Merge pull request #1596 from sbwalker/dev
format license terms
2021-08-06 15:23:22 -04:00
b1d64eac88 moduleDefinitions validation 2021-08-06 13:32:40 -04:00
04673a4804 Merge pull request #1591 from gjwalk/dev
moduleCreator validation
2021-08-06 12:55:17 -04:00
4d2f9d038a Merge pull request #1592 from sbwalker/dev
allow host username to be specified during installation, allow user to be added to host role, refresh user list after delete, improve date/time entry in scheduled jobs, require license acceptance during module and theme install
2021-08-06 12:54:39 -04:00
e4201c1a4d allow host username to be specified during installation, allow user to be added to host role, refresh user list after delete, improve date/time entry in scheduled jobs, require license acceptance during module and theme install 2021-08-06 12:59:56 -04:00
030c001371 moduleCreator validation 2021-08-06 11:44:52 -04:00
bd351f770b Merge pull request #1588 from gjwalk/dev
langauges validation
2021-08-06 08:56:47 -04:00
63093cca3b langauges validation 2021-08-05 12:24:11 -04:00
5c42e8e5bc Merge pull request #1587 from gjwalk/dev
jobs validation
2021-08-05 08:38:18 -04:00
153934b311 Merge pull request #1582 from leigh-pointer/1579
Fixes #1579 Exception when browsing to /login when you are already logged in
2021-08-05 08:38:05 -04:00
4b1ead1a36 jobs validation 2021-08-04 19:15:52 -04:00
ddafd21706 Fixes #1579 Exception when browsing to /login when you are already logged in
Added PageState.User check
2021-08-04 19:33:54 +02:00
6059a944bf Merge pull request #1577 from gjwalk/dev
files validation
2021-08-03 08:45:48 -04:00
1cc26c3902 files validation 2021-07-31 16:59:03 -04:00
00ca3d856b reset admin 2021-07-31 15:09:14 -04:00
9af8ab92c9 themes - users validation changes 2021-07-29 16:54:32 -04:00
46fcfcc321 reset - systemInfo validation changes 2021-07-29 16:52:27 -04:00
cf40462531 moduleDefintions - profiles validation changes 2021-07-29 16:46:58 -04:00
2dbf9671d9 dashboard - moduleCretaor changes 2021-07-29 16:38:36 -04:00
e42a687c9b fixing spacing 2021-07-29 15:28:15 -04:00
72c154b048 fix appsettings 2021-07-27 21:17:13 -04:00
b0921513c1 fix to theme 2021-07-27 21:12:18 -04:00
33a76c61ca updated modules for input requirements 2021-07-27 16:24:01 -04:00
c0254d0b92 Merge pull request #1567 from ADefWebserver/dev
Removes writing database connection string to appsettings.json in AzureDeploy.json
2021-07-22 09:21:19 -04:00
99d05fe4f9 Update README.md 2021-07-21 06:56:10 -07:00
f8ba6a0c5f Update README.md
Temporarily pointing to my Dev version of azuredeploy (do not merge)
2021-07-21 06:18:34 -07:00
3a58ed63e9 Update azuredeploy.json
https://github.com/oqtane/oqtane.framework/issues/1547
2021-07-21 06:14:42 -07:00
8aac801af2 Merge pull request #1566 from sbwalker/dev
update install wizard to Bootstrap 5
2021-07-19 16:44:43 -04:00
1cc16a7ed4 update install wizard to Bootstrap 5 2021-07-19 16:49:31 -04:00
7fe04d9651 Merge pull request #1564 from nicpitsch/dev
Copy external Theme to Oqtane.Server.
2021-07-19 16:44:34 -04:00
01a9dc4d2d Merge pull request #1562 from leigh-pointer/OffCanvas
user experience updates
2021-07-19 16:44:25 -04:00
1456462ca8 Copy external Theme to Oqtane.Server. 2021-07-19 20:53:45 +02:00
77415dc0e0 user experience updates 2021-07-18 17:30:32 +02:00
696b1970fc Update README.md 2021-07-18 11:15:03 -04:00
0e6baae366 Merge pull request #1554 from leigh-pointer/OffCanvas
ConltolPanel to use Bootstrap Offcanvas component
2021-07-18 10:42:09 -04:00
437e9ee43b Modifcations for ControlPanel BS5
current styles in app.css and theme.css for app-controlpanel removed as no longer needed.

Removed the _display var as that is no longer beign used.
2021-07-15 16:28:12 +02:00
6546f47bb2 Merge pull request #1556 from leigh-pointer/UsernameReadOnly 2021-07-15 09:47:51 -04:00
88c866057f Fix for #1555 Username readonly on Register form
Removed the readonly attribute from the username field.
2021-07-15 15:41:20 +02:00
3521dd41cd ConltolPanel to use Bootstrap Offcanvas component 2021-07-14 10:18:39 +02:00
dff3e6aaca Merge pull request #1550 from leigh-pointer/ModificationsBS5 2021-07-12 10:24:42 -04:00
4b04e6c8dc Merge pull request #1551 from leigh-pointer/ExtModDiv 2021-07-12 10:23:57 -04:00
37672ea632 Modified External Module Template
replace tables in markup with responsive approach.
2021-07-11 14:27:18 +02:00
31f35ad902 Modifications for Bootstrap 5
replace tables in markup with responsive approach
2021-07-11 14:16:33 +02:00
0b7b34f336 Merge pull request #1549 from leigh-pointer/Bootstrap5
modifications for Bootstrap 5
2021-07-10 19:35:41 -04:00
fca290f8f5 Modifications for Bootstrap 5
Admin section now finished.  All Tables now replaced with div
2021-07-10 13:37:05 +02:00
9da3b77f7d modifications for Bootstrap 5
Updated Admin areas Users and Roles
2021-07-10 09:00:34 +02:00
7b796f4a5f Merge pull request #1548 from sbwalker/dev
modifications for Bootstrap 5
2021-07-09 11:38:42 -04:00
0ce81169a6 modifications for Bootstrap 5 2021-07-09 11:43:37 -04:00
d1c2abf7e3 Merge pull request #1545 from hishamco/fix
Fix loading satellite assemblies
2021-07-08 10:34:02 -04:00
010872ef35 Merge pull request #1546 from sbwalker/dev
bootstrap5 fix to theme creator template, modifications to updater app
2021-07-08 10:31:19 -04:00
f536033087 framework updater UX improvements 2021-07-08 10:36:08 -04:00
9083888686 bootstrap5 fix to theme creator template, modifications to updater app 2021-07-07 16:15:38 -04:00
cf2d8531a3 Fix loading satellite assemblies 2021-07-07 20:38:53 +03:00
48b9e2f97b Merge pull request #1538 from oqtane/master
Merge pull request #1537 from oqtane/dev
2021-07-06 15:31:55 -04:00
57ac20519a Merge pull request #1537 from oqtane/dev
2.2.0 release
2021-07-06 15:31:18 -04:00
792086140b fix horizontal menu highlighting issue 2021-07-06 14:21:54 -04:00
ddeb05dfe2 Merge pull request #1535 from sbwalker/dev
fix horizontal menu highlighting issue
2021-07-06 14:17:12 -04:00
276431b62d update theme creator template to Bootstrap5 2021-07-06 11:00:45 -04:00
a89e7c06d4 Merge pull request #1534 from sbwalker/dev
update theme creator template to Bootstrap5
2021-07-06 10:56:02 -04:00
a79535d7d1 prepare for 2.2.0 release 2021-07-05 15:22:24 -04:00
649a134214 Merge pull request #1533 from sbwalker/dev
prepare for 2.2.0 release
2021-07-05 15:17:43 -04:00
910630cd26 Merge pull request #1531 from leigh-pointer/ModuleTemplates
Fix for #1530 Error in Module templates
2021-07-05 14:56:18 -04:00
a1baf70524 fix #1530 for Error in Module templates
The Get public Models.[Module] Get(int id) method in the [Module]Controller has an incorrect IF statement which in turn returns nothing and logs an error
2021-07-05 08:25:16 +02:00
7348540c4d Update README.md 2021-07-02 20:07:41 -04:00
534c6794f2 Merge pull request #1529 from sbwalker/dev
upgrade to Boostrap 5
2021-07-02 19:59:10 -04:00
cb7d9a0371 upgrade to Boostrap 5 2021-07-02 20:03:51 -04:00
525cbb87b0 allow disabling of swagger and package service 2021-07-01 09:11:29 -04:00
7f420e8a8f Merge pull request #1528 from sbwalker/dev
allow disabling of swagger and package service
2021-07-01 09:06:44 -04:00
eea417ff44 added logging for startup issues 2021-07-01 07:37:03 -04:00
070e00b735 Merge pull request #1527 from sbwalker/dev
added logging for startup issues
2021-07-01 07:32:34 -04:00
5268c326a1 suppress Internal EF Core API usage warning messages on build 2021-06-28 10:27:29 -04:00
49b2c323d0 Merge pull request #1523 from sbwalker/dev
suppress Internal EF Core API usage warning messages on build
2021-06-28 10:22:49 -04:00
bf6edceb36 Fix issue where module definition version was not being loaded correctly on startup. Also user customizable module definition properties were being overwritten on upgrade. 2021-06-28 10:21:48 -04:00
91eeeaf064 Merge pull request #1522 from sbwalker/dev
Fix issue where module definition version was not being loaded correctly on startup. Also user customizable module definition properties were being overwritten on upgrade.
2021-06-28 10:17:40 -04:00
17c0aec1fb show friendly message when no packages match criteria 2021-06-27 20:20:01 -04:00
e4c98ba24f Merge pull request #1519 from sbwalker/dev
show friendly message when no packages match criteria
2021-06-27 20:15:23 -04:00
8ef83b2de8 improve register for updates 2021-06-27 18:33:41 -04:00
376482e66e Merge pull request #1518 from sbwalker/dev
improve register for updates
2021-06-27 18:29:05 -04:00
2433958a0f fixed issue in SharedResource file 2021-06-27 08:59:04 -04:00
50bddd072b Merge pull request #1517 from sbwalker/dev
fixed issue in SharedResource file
2021-06-27 08:54:35 -04:00
b77d313715 Merge pull request #1515 from gjwalk/dev
Shared resources added
2021-06-27 08:50:46 -04:00
b59706ab8b Merge pull request #1516 from sbwalker/dev
add ability to register for updates
2021-06-27 08:47:25 -04:00
9e004f5b3c add ability to register for updates 2021-06-27 08:48:18 -04:00
f989f63546 Merge branch 'dev' into dev 2021-06-25 17:56:21 -04:00
ef6f5f2769 update resources 2021-06-25 17:03:29 -04:00
ba9ca22aaa update to resources 2021-06-25 16:34:30 -04:00
db1808d3e9 additional system info 2021-06-25 15:06:52 -04:00
8ad61a23c8 Merge pull request #1514 from sbwalker/dev
additional system info
2021-06-25 15:02:38 -04:00
e1e4d82684 resx file corrections 2021-06-25 08:22:36 -04:00
5d84eeea1d Merge pull request #1513 from sbwalker/dev
resx file corrections
2021-06-25 08:18:05 -04:00
509f054961 package UI updates 2021-06-25 08:10:15 -04:00
3a85eed397 Merge pull request #1512 from sbwalker/dev
package UI updates
2021-06-25 08:05:51 -04:00
52bcdb12c5 package management modifications 2021-06-24 18:02:01 -04:00
30a667debf Merge pull request #1511 from sbwalker/dev
package management modifications
2021-06-24 17:57:23 -04:00
161ab56701 improve handling of response status and logging in package controller 2021-06-24 10:42:43 -04:00
55bf2f1b6a Merge pull request #1510 from sbwalker/dev
improve handling of response status and logging in package controller
2021-06-24 10:38:31 -04:00
fb2a8e987e default resx fixes 2021-06-24 08:02:39 -04:00
0608287b5f Merge pull request #1509 from sbwalker/dev
default resx fixes
2021-06-24 07:58:08 -04:00
7fa30ff23c Merge pull request #1501 from Gotiap/dev
Allowed pages for external modules.
2021-06-24 07:37:49 -04:00
b0f76ff4e8 Merge pull request #1508 from sbwalker/dev
Page IsClickable column must be nullable in order to support upgrades, add more defensive logic
2021-06-24 07:37:27 -04:00
8e7b553ca8 Page IsClickable column must be nullable in order to support upgrades, add more defensive logic 2021-06-24 07:41:34 -04:00
89cc389918 Undo changes for login 2021-06-24 11:30:09 +05:30
bfafffd8cb add search to package manager components 2021-06-23 13:00:44 -04:00
4ea92652dd Merge pull request #1499 from ijaz-saeed/rich-text-editor
OnInitialized is not the right method to do this, Content is not set yet
2021-06-23 12:58:25 -04:00
6fbb325c42 Merge pull request #1506 from sbwalker/dev
add search to package manager components
2021-06-23 12:56:16 -04:00
c2f7546488 add database diagram 2021-06-22 14:30:20 -04:00
90778f5e1e Update README.md 2021-06-22 14:28:06 -04:00
c7d5f95949 Update README.md 2021-06-22 14:27:17 -04:00
67dff508ef Merge pull request #1504 from sbwalker/dev
add database diagram
2021-06-22 14:25:43 -04:00
c4e6a4af49 fix remaining default resx differences, enhance module message with ability to dismiss, fix issue in ConfigManager.RemoveSetting, introduce package registry service 2021-06-22 14:14:46 -04:00
c236e80e95 Merge pull request #1498 from ijaz-saeed/dev
adding SaveChangesAsync overloads to support Async save
2021-06-22 14:10:33 -04:00
1906a0ee8a Merge pull request #1502 from sbwalker/dev
fix remaining default resx differences, enhance module message with ability to dismiss, fix issue in ConfigManager.RemoveSetting, introduce package registry service
2021-06-22 14:10:23 -04:00
d348e9715f Allowed pages for external module.
Login internal module while edit, resolved error to be edit.
2021-06-22 10:51:05 +05:30
91a1bfcab2 adding SaveChangesAsync overloads to support Async save 2021-06-20 17:52:41 +05:00
73f2fc4f13 OnInitialized is not the right method to do this, Content is not available yet 2021-06-20 17:51:25 +05:00
247e00ecd4 implement shared resources 2021-06-18 16:56:54 -04:00
3274ab6258 Merge pull request #1497 from sbwalker/dev
implement shared resources
2021-06-18 16:52:27 -04:00
6bff09d0ca fix Localizer class specification 2021-06-18 16:46:15 -04:00
19f3095483 Merge pull request #1496 from sbwalker/dev
fix Localizer class specification
2021-06-18 16:41:47 -04:00
2e947625cd fix issue with HtmlText module rendering 2021-06-18 16:36:18 -04:00
8198f36530 Merge pull request #1495 from sbwalker/dev
fix issue with HtmlText module rendering
2021-06-18 16:32:09 -04:00
8bc0402801 fix syntax error 2021-06-18 16:30:49 -04:00
87aadcff5b Merge pull request #1494 from sbwalker/dev
fix syntax error
2021-06-18 16:27:00 -04:00
dc44fcd0d9 Merge pull request #1493 from gjwalk/dev
create default rex files with static keys
2021-06-18 16:21:37 -04:00
f7363504c2 Merge branch 'dev' into dev 2021-06-18 15:35:20 -04:00
ae0edcfd2d create default rex files with static keys 2021-06-18 14:45:38 -04:00
b180c260e5 Merge pull request #1492 from sbwalker/dev
improved error handling, improved consistency of console error messages, added ability to add a Decimal column in Migrations
2021-06-18 12:57:21 -04:00
3bc5744007 improved error handling, improved consistency of console error messages, added ability to add a Decimal column in Migrations 2021-06-18 13:01:42 -04:00
32c49f74d3 fix module template issues 2021-06-17 12:12:19 -04:00
8b70d1ccdc Merge pull request #1491 from sbwalker/dev
fix module template issues
2021-06-17 12:08:03 -04:00
f330c4fcb6 add extension method for Localization which allows specification of key and value 2021-06-16 22:16:48 -04:00
a953ca752e Merge pull request #1490 from sbwalker/dev
add extension method for Localization which allows specification of key and value
2021-06-16 22:12:48 -04:00
d32b622f7e allow order to be defined in page templates 2021-06-16 17:24:45 -04:00
8354d66c84 Merge pull request #1489 from sbwalker/dev
allow order to be defined in page templates
2021-06-16 17:20:53 -04:00
7ee9923b52 Merge pull request #1483 from leigh-pointer/SResControlPanel
Introduce SharedResource Localisation to ControlPanel
2021-06-16 16:27:52 -04:00
31c412a886 Merge pull request #1488 from sbwalker/dev
improvements to refresh logic, module template enhancements
2021-06-16 16:26:53 -04:00
72ff6fa0e7 improvements to refresh logic, module template enhancements 2021-06-16 16:31:02 -04:00
c6e12a614a Merge pull request #1485 from hishamco/database-projects
Add missing Oqtane.Server into Databases solution
2021-06-16 10:09:26 -04:00
cec24e7446 improve multi-tenancy navigation 2021-06-16 08:30:41 -04:00
05d7c7fc74 Merge pull request #1484 from hishamco/localization-cookie
Fix parsing localization cookie when the value is not present
2021-06-16 08:26:50 -04:00
0d10645d57 Merge pull request #1486 from sbwalker/dev
improve multi-tenancy navigation
2021-06-16 08:26:36 -04:00
1bfe4fbd4f Add missing Oqtane.Server to Databases solutions 2021-06-16 11:55:36 +03:00
c62a4ae8ae Fix parsing localization cookie when the value is not present 2021-06-16 11:02:12 +03:00
4fcfb2ab4e Introduce SharedResource Localisation
Updated the ControlPanel to use Shared resources for;
 Add, Edit, Delete, Cancel

Corrected the SharedResources.cs namespace.
2021-06-16 09:46:33 +02:00
0f208f3c30 Merge pull request #1 from oqtane/dev
merge
2021-06-16 09:19:09 +02:00
65a14da5a9 improve validation and exception handling in API Controllers 2021-06-15 19:11:00 -04:00
53f3245f1d Merge pull request #1482 from sbwalker/dev
improve validation and exception handling in API Controllers
2021-06-15 19:06:52 -04:00
ffbabcfb28 Update README.md 2021-06-15 09:18:17 -04:00
0a2293119e added back missing ITenantManager registration removed in #1245 2021-06-15 08:32:39 -04:00
abd9e2798d Merge pull request #1480 from sbwalker/dev
added back missing ITenantManager registration removed in #1245
2021-06-15 08:28:44 -04:00
f6cc11bd3b add logic removed in #1245 back to HttpClient creation 2021-06-15 08:23:26 -04:00
3e3bf937fd Merge pull request #1479 from sbwalker/dev
add logic removed in #1245 back to HttpClient creation
2021-06-15 08:19:20 -04:00
0fd16fbb59 added Oqtane.Client to _Imports so it can find the new SharedResources class 2021-06-15 07:59:05 -04:00
b2098bb2db Merge pull request #1478 from sbwalker/dev
added Oqtane.Client to _Imports so it can find the new SharedResources class
2021-06-15 07:54:54 -04:00
87c99e2ba7 Merge branch 'oqtane:dev' into dev 2021-06-15 07:46:51 -04:00
4a11524db3 Merge pull request #1245 from hishamco/clean-startup
Clean Startup Class
2021-06-15 07:45:12 -04:00
a9efc2792e Merge pull request #96 from oqtane/dev
sync
2021-06-15 07:42:22 -04:00
9558894a4f moved SharedResources class to proper location 2021-06-15 07:43:12 -04:00
abfb451290 Merge branch 'dev' into dev 2021-06-15 07:42:02 -04:00
e279bca8ad Merge pull request #1476 from leigh-pointer/SharedResource
SharedResources to reside at the project root.
2021-06-15 07:40:27 -04:00
f2b3d6bc5f SharedResources to reside at the project root.
Namespace was changed from Oqtane to Oqtane.Client
2021-06-15 07:09:22 +02:00
28694fc11f added defensive logic to app.razor, relocated shared resource definition in preparation for utilizing shared resources 2021-06-14 17:29:23 -04:00
e99d62d5c7 Merge pull request #1475 from sbwalker/dev
added defensive logic to app.razor, relocated shared resource definition in preparation for utilizing shared resources
2021-06-14 17:25:23 -04:00
216dd39474 Merge branch 'oqtane:dev' into dev 2021-06-13 15:24:05 -04:00
643895b62b add icon for reuse 2021-06-12 09:21:24 -04:00
bd0ee1370c Merge pull request #1471 from sbwalker/dev
add icon for reuse
2021-06-12 09:17:10 -04:00
7c181b65cd Fix merge conflict 2021-06-12 00:18:57 +03:00
bdf36fc49c bug fixes 2021-06-11 17:07:54 -04:00
965b935128 Merge pull request #1470 from sbwalker/dev
bug fixes
2021-06-11 17:03:57 -04:00
126024991c Merge remote-tracking branch 'upstream/dev' into clean-startup
# Conflicts:
#	Oqtane.Client/Program.cs
#	Oqtane.Server/Startup.cs
2021-06-11 23:54:38 +03:00
8f944e29ac set the DefaultDBType as the default database option in the Installer and Add Site UI 2021-06-11 08:43:46 -04:00
7b26ecfec8 Merge pull request #1467 from sbwalker/dev
set the DefaultDBType as the default database option in the Installer and Add Site UI
2021-06-11 08:40:17 -04:00
aa5aca3a8e back out auth policy header support as Blazor HttpClient is registered as Scoped and can not support variable headers 2021-06-11 07:54:02 -04:00
c46ef0fbc4 Merge pull request #1466 from sbwalker/dev
back out auth policy header support as Blazor HttpClient is registered as Scoped and can not support variable headers
2021-06-11 07:50:00 -04:00
d82fc8be90 added IsClickable Page property #1092, improve validation in Role Management, display database information in SQL Management, improve HttpClient header support 2021-06-10 20:10:46 -04:00
a51e7c2b44 Merge pull request #1465 from sbwalker/dev
added IsClickable Page property #1092, improve validation in Role Management, display database information in SQL Management, improve HttpClient header support
2021-06-10 20:06:41 -04:00
52d9c14d78 Update README.md 2021-06-10 13:06:05 -04:00
4bee097e66 fix Site Settings issue 2021-06-10 10:55:59 -04:00
6899a7cbb2 Merge pull request #1464 from sbwalker/dev
fix Site Settings issue
2021-06-10 10:51:49 -04:00
edc0e2a8fe Merge pull request #1462 from albahadly/dev
fix issue: WebAssembly with IDM app
2021-06-10 10:46:52 -04:00
f5f374d2ae #1460 fix issue: WebAssembly with IDM app 2021-06-11 01:06:20 +12:00
ece90c39cb Merge pull request #1461 from sbwalker/dev
refactoring, enhancements, and some fixes
2021-06-10 08:13:35 -04:00
bc720555c4 refactoring, enhancements, and some fixes 2021-06-10 08:16:02 -04:00
25dc6b9b08 when set Runtime as WebAssembly, IDM app recognize oqtane.zip and download this file and this make an issue and oqtane not work correctly and for this I set diffrent extention that IDM cant recognize it 2021-06-09 12:45:31 +12:00
36bd8e4b7f Update README.md 2021-06-07 15:58:04 -04:00
82c05a841f Improve validation and error handling in Controller methods 2021-06-07 15:29:08 -04:00
dd3385c447 Update README.md 2021-06-07 15:25:57 -04:00
93031c9aaa Merge pull request #1457 from sbwalker/dev
Improve validation and error handling in Controller methods
2021-06-07 15:25:04 -04:00
54cd360bb5 allow host to change runtime and rendermode settings in System Info 2021-06-06 11:04:37 -04:00
aa30caa1a7 Merge pull request #1451 from sbwalker/dev
allow host to change runtime and rendermode settings in System Info
2021-06-06 11:00:58 -04:00
900ea8cfbc allow host to view tenant information in Site Settings 2021-06-06 10:36:13 -04:00
cf99f04451 Merge pull request #1450 from sbwalker/dev
allow host to view tenant information in Site Settings
2021-06-06 10:32:21 -04:00
357ef09dd1 new controller auth parameter should take precedence over legacy 2021-06-06 10:03:54 -04:00
42d653969d Merge pull request #1449 from sbwalker/dev
new controller auth parameter should take precedence over legacy
2021-06-06 10:00:09 -04:00
a2b808fde2 use new service auth pattern in module template 2021-06-04 15:01:25 -04:00
33405b9457 Merge pull request #1448 from sbwalker/dev
use new service auth pattern in module template
2021-06-04 14:57:23 -04:00
ffb5ca44ec Merge pull request #1447 from oqtane/master
Merge pull request #1446 from oqtane/dev
2021-06-04 13:05:35 -04:00
3f8a0d4933 Merge pull request #1446 from oqtane/dev
2.1.0 release
2021-06-04 12:56:13 -04:00
970845d640 Merge pull request #1445 from sbwalker/dev
create Public folder during installation
2021-06-04 12:26:42 -04:00
fab06a5279 create Public folder during installation 2021-06-04 12:30:46 -04:00
ff59a12443 UX clarification 2021-06-04 12:18:04 -04:00
b7af8d0d91 Merge pull request #1444 from sbwalker/dev
UX clarification
2021-06-04 12:14:38 -04:00
3401d33a35 Update README.md 2021-06-04 09:32:58 -04:00
69c948ad79 misc site settings UX fixes 2021-06-04 08:42:33 -04:00
07b684d49a Merge pull request #1443 from sbwalker/dev
misc site settings UX fixes
2021-06-04 08:38:51 -04:00
ab3af5f294 use full package name for manifest 2021-06-04 07:30:33 -04:00
aefdcdcd8a Merge pull request #1442 from sbwalker/dev
use full package name for manifest
2021-06-04 07:26:32 -04:00
7b32a16fd8 handle versionless package names in installer 2021-06-03 15:03:11 -04:00
7c7dbfe638 Merge pull request #1440 from sbwalker/dev
handle versionless package names in installer
2021-06-03 14:59:13 -04:00
e527f6e3d1 updater improvements 2021-06-03 11:05:40 -04:00
5f068081f3 Merge pull request #1439 from sbwalker/dev
updater improvements
2021-06-03 11:01:57 -04:00
02de8e0bf7 fix path 2021-06-03 09:01:52 -04:00
f412657987 Merge pull request #1438 from sbwalker/dev
fix path
2021-06-03 08:57:46 -04:00
ec462e2372 Merge pull request #1437 from sbwalker/dev
create all artifacts in Oqtane.Packages folder
2021-06-03 08:56:01 -04:00
0deffff370 create all artifacts in Oqtane.Packages folder 2021-06-03 08:58:45 -04:00
57885f71a7 Revert "create all artifacts in Oqtane.Package folder"
This reverts commit a05a1d6321.
2021-06-03 08:52:51 -04:00
a05a1d6321 create all artifacts in Oqtane.Package folder 2021-06-03 08:52:25 -04:00
060f764da7 separated updater from main solution 2021-06-03 08:37:56 -04:00
ae2caacc81 Merge pull request #1436 from sbwalker/dev
separated updater from main solution
2021-06-03 08:34:02 -04:00
dd9f2e6675 improve System Update user experience 2021-06-02 19:22:20 -04:00
b5a81d8328 Merge pull request #1435 from sbwalker/dev
improve System Update user experience
2021-06-02 19:19:22 -04:00
d1204c7dea Merge pull request #1434 from sbwalker/dev
use secure Packages location for upgrade process
2021-06-02 17:10:44 -04:00
3db12a225b use secure Packages location for upgrade process 2021-06-02 16:53:55 -04:00
912b775553 preserve backward compatibility of CreateAuthorizationPolicyUrl method 2021-06-02 12:20:31 -04:00
25952b53f2 Merge pull request #1433 from sbwalker/dev
preserve backward compatibility of CreateAuthorizationPolicyUrl method
2021-06-02 12:16:31 -04:00
9bccc402a0 use versionless package names for database providers to support future framework upgrades 2021-06-02 08:09:46 -04:00
b44cc06324 Merge pull request #1432 from sbwalker/dev
use versionless package names for database providers to support future framework upgrades
2021-06-02 08:05:48 -04:00
1b4934d623 Merge pull request #1431 from sbwalker/dev
fix issue in theme creator
2021-06-01 15:45:09 -04:00
8dfe8eba27 fix issue in theme creator 2021-06-01 15:49:06 -04:00
56a1c1a57d Update README.md 2021-06-01 14:51:13 -04:00
3c48657e73 version all DLLs and packages consistently and fix all deprecated iconurl warnings 2021-06-01 12:30:21 -04:00
c7442f12e8 Merge pull request #1430 from sbwalker/dev
version all DLLs and packages consistently and fix all deprecated iconurl warnings
2021-06-01 12:26:23 -04:00
c91b8e72c0 fix EF Core version value in upgrade scripts 2021-06-01 10:31:16 -04:00
dc4e3213b1 Merge pull request #1429 from sbwalker/dev
fix EF Core version value in upgrade scripts
2021-06-01 10:27:30 -04:00
5a46d6b842 Merge pull request #1428 from cnurse/dev 2021-05-31 21:50:11 -04:00
c6f4723ee3 Fix for Issue #1420 2021-05-31 15:55:09 -07:00
b76e8498d7 move logic for inserting migrations history from MigrateableModuleBase to MigrationUtils 2021-05-31 16:17:06 -04:00
0347aae140 Merge pull request #1427 from sbwalker/dev
move logic for inserting migrations history from MigrateableModuleBase to MigrationUtils
2021-05-31 16:13:12 -04:00
4576f056d5 Merge pull request #1424 from 2sic-forks/dev
More docs for #1382
2021-05-31 11:56:18 -04:00
879829b9bb Merge pull request #1425 from sbwalker/dev
added metadata support for Module and Theme templates
2021-05-31 11:55:54 -04:00
ddd657bfa7 added metadata support for Module and Theme templates 2021-05-31 11:59:19 -04:00
d52cbf6817 More docs for https://github.com/oqtane/oqtane.framework/issues/1382 - should not affect any code 2021-05-31 15:45:07 +02:00
276817c89d made RenderMode configurable 2021-05-30 15:37:23 -04:00
d8b811e09f Update appsettings.release.json 2021-05-30 15:35:19 -04:00
b21ffc2d63 Update appsettings.json 2021-05-30 15:35:04 -04:00
c402113df3 Merge pull request #1423 from sbwalker/dev
made RenderMode configurable
2021-05-30 15:34:04 -04:00
afcc5e2170 handle HtmlText module transition from SQL scripts to Migrations in module rather than in core framework 2021-05-30 13:16:26 -04:00
5a2ad4f827 Merge pull request #1422 from sbwalker/dev
handle HtmlText module transition from SQL scripts to Migrations in module rather than in core framework
2021-05-30 13:12:27 -04:00
967f0fe626 leave a copy of database provider packages in distribution folder 2021-05-29 17:38:58 -04:00
f8aeef06b2 Merge pull request #1421 from sbwalker/dev
leave a copy of database provider packages in distribution folder
2021-05-29 17:35:03 -04:00
7ed93b5ce6 package installer fix to handle .bak files 2021-05-29 15:13:27 -04:00
89cd0c760a Merge pull request #1418 from sbwalker/dev
package installer fix to handle .bak files
2021-05-29 15:09:29 -04:00
d73e1d21c7 prepare for 2.1.0 release 2021-05-29 13:00:36 -04:00
21ad5f4c1a Merge pull request #1417 from sbwalker/dev
prepare for 2.1.0 release
2021-05-29 12:56:55 -04:00
e84908485f add support for custom "internal" module and theme templates. fix package installer issue related to absolute paths 2021-05-29 11:48:29 -04:00
b05fbae401 Merge pull request #1416 from sbwalker/dev
add support for custom "internal" module and theme templates. fix package installer issue related to absolute paths
2021-05-29 11:44:30 -04:00
54a639d1d5 Module Creator external template changes for 2.1 - supporting mutliple databases, EF Core migrations, and other multi-tenancy improvements 2021-05-28 17:01:25 -04:00
ba61c3318b Merge pull request #1413 from sbwalker/dev
Module Creator external template changes for 2.1 - supporting mutliple databases, EF Core migrations, and other multi-tenancy improvements
2021-05-28 16:57:39 -04:00
8529a42075 fixed upgrade logic 2021-05-28 16:01:11 -04:00
b62a9fa1ff Merge pull request #1412 from sbwalker/dev
fixed upgrade logic
2021-05-28 15:57:16 -04:00
e559a11261 Delete Oqtane.Server/Content/Tenants/1/Sites/1 directory 2021-05-28 08:43:42 -04:00
e878db2f21 Merge pull request #1411 from sbwalker/dev
add new packages location to gitignore
2021-05-28 08:40:12 -04:00
e693f6b52b add new packages location to gitignore 2021-05-28 08:44:14 -04:00
e0c2763c9f refactoring in preparation for release 2021-05-28 07:53:49 -04:00
723ae68609 Merge pull request #1410 from sbwalker/dev
refactoring in preparation for release
2021-05-28 07:49:56 -04:00
9dbfbd78a4 Update README.md 2021-05-27 21:47:04 -04:00
9cd29b62ed Update README.md 2021-05-27 21:46:27 -04:00
4108c07862 moved Packages folder to secure location 2021-05-27 21:39:43 -04:00
01716099d6 Merge pull request #1408 from sbwalker/dev
moved Packages folder to secure location
2021-05-27 21:35:57 -04:00
a144a5c432 improved legacy support for module authorization policy 2021-05-27 21:01:25 -04:00
dbccfe4bf7 Merge pull request #1407 from sbwalker/dev
improved legacy support for module authorization policy
2021-05-27 20:57:46 -04:00
9aff82e504 Update appsettings.release.json 2021-05-27 16:17:09 -04:00
9ff4d9c143 Update appsettings.json 2021-05-27 16:16:48 -04:00
6c8703356f Merge pull request #1406 from sbwalker/dev
automate the 2.1.0 upgrade
2021-05-27 16:15:02 -04:00
fbce6c7248 automate the 2.1.0 upgrade 2021-05-27 16:18:45 -04:00
908d572cc9 Update appsettings.release.json 2021-05-26 17:43:02 -04:00
f0384732ac Update appsettings.json 2021-05-26 17:42:43 -04:00
c089283645 Merge pull request #1405 from sbwalker/dev
move database projects into their own solution to keep the streamline main solution
2021-05-26 17:42:18 -04:00
50ac9236af move database projects into their own solution to keep the streamline main solution 2021-05-26 17:46:08 -04:00
d520d50b75 Update appsettings.release.json
synchronized with appsettings.json
2021-05-26 12:00:49 -04:00
99838679bd Update appsettings.json
reorganized to move most relevant settings to the top of the file
2021-05-26 12:00:15 -04:00
4b49358968 Merge pull request #1404 from sbwalker/dev
add support for public content folders
2021-05-26 11:57:46 -04:00
c07e766e57 add support for public content folders 2021-05-26 12:01:35 -04:00
1d171d2e56 Merge pull request #1400 from 2sic-forks/dev
More documentation - almost all Models done #1382
2021-05-26 07:31:16 -04:00
6527d383db Merge pull request #1401 from cnurse/database-options
Move Available Databases to appsettings and use IOptions
2021-05-26 07:30:34 -04:00
d280a4aa01 Move Available Databases to appsettings and use IOptions 2021-05-25 15:03:27 -07:00
bcff9caf5c More documentation - almost all Models done
https://github.com/oqtane/oqtane.framework/issues/1382
Should not contain any code changes, just docs
2021-05-26 00:01:22 +02:00
bb92011641 Merge pull request #1398 from cnurse/dev
Merge AppVersions Information into __EFMigrationsHistory table
2021-05-25 17:46:19 -04:00
9c0cef870c Merge AppVersions information into EFMigrationsHistory table 2021-05-25 12:17:44 -07:00
077b866e8c Move Migrations into Master and Tenant folders so its clear what type of Migration is being applied 2021-05-25 12:16:10 -07:00
7ec3376308 Merge pull request #1397 from cnurse/dev
Fix Upgrade issue with new componentized Database projects
2021-05-25 07:39:24 -04:00
7753988f64 Remove log files from git 2021-05-24 16:02:45 -07:00
a62a1be1f3 Merge branch 'dev' of https://github.com/cnurse/oqtane.framework into dev
# Conflicts:
#	Oqtane.Server/Infrastructure/DatabaseManager.cs
2021-05-24 15:47:02 -07:00
f2ae1e3bff Merge commit '42265cdda4ea03541522bf5cf5d104059927393f' into dev
# Conflicts:
#	Oqtane.Server/Infrastructure/DatabaseManager.cs
2021-05-24 15:40:35 -07:00
a0838cbc84 3rd attempt to resolve conflicts 2021-05-24 15:14:58 -07:00
17f5f39a54 2nd attempt to Fix conflict 2021-05-24 15:12:41 -07:00
4853a64cf9 Fix conflict 2021-05-24 15:08:44 -07:00
69376c5baf Missing files from previous commit 2021-05-24 14:46:54 -07:00
42265cdda4 Fix Upgrade issue with new componentized Database projects 2021-05-24 14:46:12 -07:00
6283fc9abb Merge pull request #1396 from sbwalker/dev
remove Microsoft.EntityFrameworkCore.Relational from Oqtane.Shared as it is no longer needed
2021-05-24 16:39:33 -04:00
723002968a remove Microsoft.EntityFrameworkCore.Relational from Oqtane.Shared as it is no longer needed 2021-05-24 16:43:17 -04:00
1f7207bd5a Merge pull request #1395 from sbwalker/dev
fix #1272 - add support for ref folder in package installation
2021-05-24 15:51:02 -04:00
72b06b16cf fix #1272 - add support for ref folder in package installation 2021-05-24 15:50:38 -04:00
8c70913a72 Merge pull request #1394 from cnurse/dev
Rename IOqtaneDatabase interface (and related base class)
2021-05-24 15:19:33 -04:00
4a609b444e Rename IOqtaneDatabase interface (and related base class) 2021-05-24 11:57:47 -07:00
791e786db8 Update appsettings.release.json 2021-05-24 09:03:57 -04:00
e5229626ab Update appsettings.json 2021-05-24 09:03:04 -04:00
34ed19deda Merge pull request #1393 from sbwalker/dev
added PackageName property to IModule and ITheme interfaces to allow creators to specify the Nuget package name associated to a specific module/theme. This is necessary for packages which contain multiple extensions.
2021-05-24 08:55:57 -04:00
5c21ab37ee added PackageName property to IModule and ITheme interfaces to allow creators to specify the Nuget package name associated to a specific module/theme. This is necessary for packages which contain multiple extensions. 2021-05-24 08:59:50 -04:00
41ed069072 fix #1389 - exception in PrincipalValidator 2021-05-24 08:17:46 -04:00
80c82a9ec3 Merge pull request #1392 from sbwalker/dev
fix #1389 - exception in PrincipalValidator
2021-05-24 08:14:56 -04:00
d69ceecb41 Merge pull request #1388 from sbwalker/dev
implemented Label component in Installer for consistency and removed redundant logic
2021-05-23 11:13:42 -04:00
63378e1654 implemented Label component in Installer for consistency and removed redundant logic 2021-05-23 11:17:23 -04:00
3f48c1f8fe fix #1367 - provides support for multiple entities in auth policy and makes parameter names more intuitive - backward compatible with entityid 2021-05-23 10:29:05 -04:00
35aaf476d0 Merge pull request #1387 from sbwalker/dev
fix #1367 - provides support for multiple entities in auth policy and makes parameter names more intuitive - backward compatible with entityid
2021-05-23 10:25:17 -04:00
fc3ba14d0f Merge pull request #1385 from 2sic-forks/dev 2021-05-22 08:25:06 -04:00
76879163d4 Merge pull request #1386 from cnurse/dev 2021-05-22 08:21:26 -04:00
342dae4aa7 Fix bug with MySQL Installation caused by new Migration 2021-05-21 16:38:48 -07:00
f533279dec Merge branch 'oqtane:dev' into dev 2021-05-21 18:30:03 +02:00
1bccf449fc Add docs to 2 core interfaces IAliasService and IFileService 2021-05-21 18:29:25 +02:00
de6acf0029 Tell all concrete implementations to be private / not show in the docs 2021-05-21 18:29:06 +02:00
074b998bbc Document most models 2021-05-21 18:28:21 +02:00
e4b12aa87f Document most core interfaces 2021-05-21 18:28:14 +02:00
6f123c0fff Merge pull request #1383 from cnurse/dev
Convert Database projects so they build installable Packages
2021-05-20 17:04:06 -04:00
47c04dc150 Convert Database projects so they build installable Packages rather than deploy to bin and modify installation to deploy Databases on demand as needed. 2021-05-20 12:39:09 -07:00
4474d49c6a Merge pull request #1379 from sbwalker/dev
fix #1359 - Image bug in src attribute - image is not displayed - caused by multi-tenancy refactoring
2021-05-19 13:28:48 -04:00
0b6efdbc57 fix #1359 - Image bug in src attribute - image is not displayed - caused by multi-tenancy refactoring 2021-05-19 13:32:34 -04:00
6f981e0928 revert unnecessary code change in router 2021-05-19 09:15:05 -04:00
bc53c9e443 Merge pull request #1374 from sbwalker/dev
revert unnecessary code change in router
2021-05-19 09:13:06 -04:00
9256c88fc4 update template to remove ASP.NET Core 3 reference 2021-05-19 09:08:59 -04:00
9d877bc072 Merge pull request #1373 from sbwalker/dev
update template to remove ASP.NET Core 3 reference
2021-05-19 09:05:16 -04:00
470c7bb2a9 Merge pull request #1355 from hishamco/folder-columns
Change Folder Name & Path length
2021-05-19 08:50:56 -04:00
bb195e4796 Merge pull request #1357 from hishamco/database-type
Database type name should use type AssemblyQualifiedName instead of magic string
2021-05-19 08:44:12 -04:00
4d59460fbb Merge pull request #1358 from hishamco/unit-tests
Fix broken unit tests
2021-05-19 08:42:59 -04:00
ddc4254053 Merge pull request #1372 from sbwalker/dev
auth improvements related to multi-tenancy
2021-05-19 08:42:50 -04:00
09537ab0e4 auth improvements related to multi-tenancy 2021-05-19 08:46:02 -04:00
07e8215919 Fix broken unit tests 2021-05-16 22:54:34 +03:00
2324ae1ccb Database type name should use type AssemblyQualifiedName instead of maigic string 2021-05-16 22:50:39 +03:00
b3d367329e Fix migration version name 2021-05-16 21:47:34 +03:00
795e71278b Change Folder Name & Path length 2021-05-16 20:28:41 +03:00
943adec3a0 fixes for running on WebAssembly 2021-05-13 07:58:57 -04:00
6e21eb7327 Merge pull request #1347 from sbwalker/dev
fixes for running on WebAssembly
2021-05-13 07:55:31 -04:00
fe3b42feed Merge pull request #1345 from cnurse/dev
Remove DbConfig and new constructors on DbContextBase and refactor Migrations
2021-05-12 20:20:02 -04:00
c036a9d11f Remove DbConfig and new constructors on DbContextBase and refactor Migrations to use explcit generation of IOqtaneDatabase instance 2021-05-12 15:17:40 -07:00
c958f90ee2 Merge pull request #1342 from cnurse/dev
Switch DBType to use the fully-qualified type name rather than a simple name
2021-05-12 14:41:47 -04:00
92dd8354ba Switch DBType to use the fully-qualified type name rather than a simple name 2021-05-12 10:22:17 -07:00
c926fa0d27 Merge pull request #1340 from cnurse/dev 2021-05-11 18:39:40 -04:00
4ffdcf1e52 Remove dependency of Oqtane.Server on SqlClient 2021-05-11 13:56:49 -07:00
efe803f147 Merge branch 'oqtane:dev' into dev 2021-05-11 13:29:23 -07:00
0bd6fea476 Merge pull request #1339 from sbwalker/dev
added DatabaseService to get list of database types from server
2021-05-11 15:53:19 -04:00
bae6120e3b added DatabaseService to get list of database types from server 2021-05-11 15:56:41 -04:00
7c0721797c Update README.md 2021-05-11 09:07:19 -04:00
d88bd87aea add support for Blazor pre-rendering 2021-05-11 09:03:05 -04:00
dab94defa0 Merge pull request #1338 from sbwalker/dev
add support for Blazor pre-rendering
2021-05-11 08:59:39 -04:00
6eaa3d259e Merge pull request #1337 from sbwalker/dev
optimizing tenant resolution and routing
2021-05-11 08:37:44 -04:00
d88b6987a1 Update README.md 2021-05-11 08:35:27 -04:00
b003e3734d Update README.md 2021-05-11 08:33:49 -04:00
a5de639d15 optimizing tenant resolution and routing 2021-05-10 17:45:39 -04:00
75c336454b Merge pull request #1323 from hishamco/tiny-fixes
Few Enhancements in Language Management
2021-05-07 12:17:20 -04:00
15b0bed257 fix #1319 - module title is required 2021-05-07 12:14:54 -04:00
7b15c0a542 Merge pull request #1325 from sbwalker/dev
fix #1319 - module title is required
2021-05-07 12:11:33 -04:00
9f3b6197fe Fix the UX for add a new language 2021-05-07 14:32:41 +03:00
4c4553a1d1 Ability to delete a default language if there's one language alongside with English 2021-05-07 14:10:15 +03:00
c5f65e4767 Fix IsDefault for English 2021-05-07 13:55:45 +03:00
d86beecf5c Add default language by default 2021-05-07 13:43:21 +03:00
be38d2cd70 add upgrade support for language packages 2021-05-06 08:20:09 -04:00
5a511e63ad Merge pull request #1320 from sbwalker/dev
add upgrade support for language packages
2021-05-06 08:16:52 -04:00
38aebf5aff Allow installation of Language packages through Language Management 2021-05-05 19:03:06 -04:00
6625fa7c23 Merge pull request #1318 from sbwalker/dev
Allow installation of Language packages through Language Management
2021-05-05 18:59:46 -04:00
3d5a7fdc21 Merge branch 'dev' of https://github.com/cnurse/oqtane.framework into dev 2021-05-05 10:13:05 -07:00
e836e27a5a fix #1305 - tabpanel loses focus 2021-05-03 08:19:25 -04:00
71e004aca6 Merge pull request #1310 from sbwalker/dev
fix #1305 - tabpanel loses focus
2021-05-03 08:17:59 -04:00
42c4b351f3 Merge pull request #95 from oqtane/dev
sync
2021-05-02 09:21:15 -04:00
3f28282ab6 Merge pull request #1261 from hishamco/sql-server
Move SqlServer databases into a separate project
2021-05-02 09:20:43 -04:00
d83eac5af3 Merge remote-tracking branch 'upstream/dev' into sql-server 2021-05-01 21:36:08 +03:00
4dc364b623 Merge pull request #1304 from leigh-pointer/NuGetPackageIcon
NuGet Icon Warning
2021-04-30 10:55:31 -04:00
d1abbde20c Merge pull request #1303 from leigh-pointer/AddModulePermissions
Permissions for edit applied from the page permissions.
2021-04-30 10:55:22 -04:00
2be48e910e Set module view permissions to page edit permissions 2021-04-30 14:15:56 +02:00
a310ac4ed6 Merge remote-tracking branch 'origin/AddModulePermissions' into AddModulePermissions 2021-04-30 13:53:07 +02:00
bc2e3a7333 Permissions for edit are applied from the page permissions. 2021-04-30 13:52:50 +02:00
9180ceaeb7 Merge pull request #94 from oqtane/dev
sync
2021-04-30 07:36:10 -04:00
b710ac8705 Merge pull request #1301 from leigh-pointer/AddModulePermissions
Permissions for edit are applied from the page permissions.
2021-04-30 07:35:37 -04:00
2244fcc814 Merge pull request #1259 from hishamco/more-localization
Localize Page Template Module Titles
2021-04-30 07:35:21 -04:00
c02e83547b Merge pull request #1302 from leigh-pointer/AddPageWarning
Check now displays warning instead of an Error
2021-04-30 07:32:12 -04:00
14801e7f0d NuGet Icon Warning
The IconURL is depreciate in favor of an embedded icon file in the package
2021-04-30 10:50:12 +02:00
c36cd77ab4 Check now displays warning instead of an Error 2021-04-30 09:49:51 +02:00
9e814fad64 Permissions for edit are applied from the page permissions. 2021-04-30 09:36:50 +02:00
18aa8d48fe Use SharedResources 2021-04-29 02:44:41 +03:00
d7966f2acf Revert changes in SiteRepository 2021-04-29 02:42:01 +03:00
9d3b460a6c Merge branch 'dev' of https://github.com/oqtane/oqtane.framework into dev 2021-04-28 15:14:36 -07:00
7296f94360 Merge remote-tracking branch 'upstream/dev' into dev 2021-04-28 15:08:00 -07:00
9037f49680 Merge pull request #1294 from cnurse/dev
Adding new DatabaseConfig components in the Client project for supported Databases
2021-04-28 15:30:22 -04:00
c759d22b3d Merge pull request #1295 from leigh-pointer/dev
Make container fluid to fill the pane
2021-04-28 15:25:53 -04:00
6ae5777e1c Revert "Fix NuGet Warning NU5048"
This reverts commit cad5ce51fc.
2021-04-28 20:31:52 +02:00
cad5ce51fc Fix NuGet Warning NU5048
Icon URL is deprecated in favor of embedding the icon inside the NuGet package.
2021-04-28 09:17:01 +02:00
a79ba591fe Make container fluid to fill the pane 2021-04-28 07:01:39 +02:00
1efd623a99 Adding new DatabaseConfig components in the Client project for supported Databases to avoid deploying server dlls to client 2021-04-27 15:35:10 -07:00
c32003320d Merge pull request #1284 from iJungleboy/dev
Make version static-readonly
2021-04-27 07:56:28 -04:00
307f97c60c Merge pull request #1281 from hishamco/localization-manager
LocalizationManager enhancements
2021-04-27 07:51:01 -04:00
722c234cb5 Merge pull request #1285 from leigh-pointer/dev.fork
Fix Error with Footer on Oqtane Theme #1282
2021-04-27 07:50:36 -04:00
189dcf5b90 Fix for Error with Footer on Oqtane Theme #1282 2021-04-26 15:38:33 +02:00
a531a235a0 Make version static-readonly
https://github.com/oqtane/oqtane.framework/issues/1283
2021-04-26 13:51:37 +02:00
d7569aa41a Merge pull request #3 from oqtane/dev
Merge with Fork
2021-04-26 11:31:42 +02:00
55b69f0afc Order the cultures alphabatically 2021-04-26 01:55:55 +03:00
76c2a2f2f9 Add English to supported cultures by default 2021-04-26 01:55:27 +03:00
d42c7a5ea5 user and role management improvements 2021-04-24 13:47:20 -04:00
60f386838e Merge pull request #1280 from sbwalker/dev
user and role management improvements
2021-04-24 13:44:22 -04:00
5a519510a9 improve comments and logging 2021-04-23 16:29:18 -04:00
a3449f5beb Merge pull request #1275 from sbwalker/dev
improve comments and logging
2021-04-23 16:26:22 -04:00
ffafe5d14c Merge pull request #93 from oqtane/dev
sync
2021-04-23 16:10:06 -04:00
9d083726be user and role management improvements 2021-04-23 16:11:35 -04:00
310c782326 Merge pull request #1274 from sbwalker/dev
user and role management improvements
2021-04-23 16:09:19 -04:00
65aac34f8c Merge pull request #1267 from sbwalker/dev
modify nuget package installer to support satellite assemblies in subfolders
2021-04-23 09:34:17 -04:00
856b11b574 Merge pull request #1265 from hishamco/language-switcher
Add English to the language switcher
2021-04-23 09:34:01 -04:00
1088e9adda Merge pull request #1264 from hishamco/language-management-exculde
Exclude English from the languages list
2021-04-23 09:33:22 -04:00
7470513892 Merge pull request #2 from oqtane/dev
Updates
2021-04-22 05:49:01 +02:00
2780e4d029 modify nuget package installer to support satellite assemblies in subfolders 2021-04-21 19:31:02 -04:00
c52f6c92f1 Add English to the language switcher 2021-04-21 17:47:50 +03:00
3cafb68f6b Localize module titles in UI 2021-04-21 17:31:22 +03:00
6e5496e969 Exclude English from the languages list 2021-04-21 16:41:24 +03:00
f9f0999315 Merge pull request #1260 from hishamco/supported-cultures
Remove supported cultures entry from appsettings.json
2021-04-20 17:14:21 -04:00
e7c5ca2c1a Merge pull request #1254 from chlupac/alias
Default Alias
2021-04-20 17:07:20 -04:00
d2ffb93fa4 Merge pull request #92 from oqtane/dev
sync
2021-04-20 14:13:41 -04:00
c4680c1972 Merge pull request #1262 from cnurse/dev
Fix bug with installs due to missing Migrations
2021-04-20 14:13:09 -04:00
8f5beaf3fe Fix bug with installs due to missing Migrations 2021-04-20 10:58:26 -07:00
97fb6ede7e Reuse AddOqtaneScopedServices() 2021-04-20 19:10:06 +03:00
f7d8888232 Refactor Program.cs 2021-04-20 19:01:56 +03:00
e7f5fe9827 Merge branch 'database' into clean-startup
# Conflicts:
#	Oqtane.Server/Startup.cs
2021-04-20 17:42:39 +03:00
35225b2bb9 Move SqlServer databases into a separate project 2021-04-20 17:29:24 +03:00
0b32dcf9b3 Make English default culture instead of current installed culture 2021-04-20 16:27:18 +03:00
e33c9e417d Remove SupportedCultures entry from appsettings.json 2021-04-20 16:26:17 +03:00
9d9a5a0275 Localize modules title 2021-04-20 16:20:24 +03:00
8069d838d5 Merge pull request #4 from oqtane/master
sync
2021-04-20 08:50:44 -04:00
a5494be8f1 Merge pull request #1 from oqtane/dev
Update to 2.0.2
2021-04-20 06:44:31 +02:00
bc6be52ae7 Merge pull request #91 from oqtane/dev
sync
2021-04-19 21:13:47 -04:00
40f8436947 Merge pull request #1239 from cnurse/dev
Implement Database Migrations and add Multi-Database Support
2021-04-19 21:11:11 -04:00
13f7db7b19 Resolve conflict 2021-04-19 11:57:45 -07:00
6b204cc988 Second attempt to resolve conflcit in Server project 2021-04-19 11:56:51 -07:00
a11d30b1e4 Attempt to resolve conflict in Server project file 2021-04-19 11:53:45 -07:00
2402cab3f3 Attempt to resolve conflict in DatabaseManager 2021-04-19 11:50:25 -07:00
20b5a10882 Resolve name of variable in AddSite 2021-04-19 11:15:53 -07:00
4598d0d6ba Resolve conflict in AddSite 2021-04-19 11:13:10 -07:00
7516be41d9 Update README.md 2021-04-19 14:10:13 -04:00
74c403cef1 Merge remote-tracking branch 'upstream/dev' into dev 2021-04-19 11:08:35 -07:00
468f186511 Default Alias
When alias is not found in alias table, Oqtane fails with exception. This solution allows define default alias (*) for default site with unknown alias.
2021-04-19 20:03:18 +02:00
5455fadd65 Merge pull request #90 from oqtane/dev
sync
2021-04-19 09:33:34 -04:00
58ac86db8d Merge pull request #1253 from oqtane/master
Merge pull request #1252 from oqtane/dev
2021-04-19 09:28:24 -04:00
d8a65e9422 Merge pull request #1252 from oqtane/dev
2.0.2 release
2021-04-19 09:24:01 -04:00
a85376afb1 2.0.2 release testing 2021-04-19 08:40:58 -04:00
da3b83acf8 Merge pull request #1251 from sbwalker/dev
2.0.2 release testing
2021-04-19 08:38:08 -04:00
0fe931d772 prepare for 2.0.2 release 2021-04-19 08:04:33 -04:00
2c69392f43 Merge pull request #1248 from sbwalker/dev
prepare for 2.0.2 release
2021-04-19 08:01:50 -04:00
3b54d17286 Merge pull request #89 from oqtane/dev
sync
2021-04-18 14:10:17 -04:00
7a3b2e6dfc Merge pull request #1246 from leigh-pointer/dev.fork
Redirect on log out #1225 Fix
2021-04-18 14:07:55 -04:00
82b85ab24d optimize GetSupportedCultures 2021-04-18 11:51:33 -04:00
8899504d05 Merge pull request #1247 from sbwalker/dev
optimize GetSupportedCultures
2021-04-18 11:48:59 -04:00
1e7e2c8848 Redirect on log out #1225 Fix 2021-04-18 16:58:58 +02:00
7417e8dd27 Merge branch 'dev' of https://github.com/oqtane/oqtane.framework into dev 2021-04-18 02:29:39 +03:00
a018e853a8 Register configuration in startup 2021-04-18 02:27:31 +03:00
73b13d7a54 Add Oqtane extension methods for clean startup 2021-04-18 02:25:40 +03:00
cbe843bafc User experience improvements 2021-04-17 19:18:24 -04:00
8dac6fab54 Merge pull request #1244 from sbwalker/dev
User experience improvements
2021-04-17 19:16:13 -04:00
096f8249c3 Resolve conflict in Constants 2021-04-14 16:05:59 -07:00
e11a65c6ca Rollback removal of script files for Html Module to resolve conflict 2021-04-14 16:03:00 -07:00
23bab67bd3 Resolve new conflict 2021-04-14 15:59:31 -07:00
713b19e0e9 Remove Html Module Script files 2021-04-14 15:57:07 -07:00
f8b607911d Resolve conflict in project file 2021-04-14 15:53:07 -07:00
0222bbdeae Resolve conflict with main Oqtane repo 2021-04-14 15:48:54 -07:00
a0329b1b6c Fix bug in new Migration to support PostgreSQL 2021-04-08 14:58:02 -07:00
8c45b7e42f Added support for migrating existing Oqtane installations from DbUp to Migrations. Also added a Migration for version 2.0.2, and set current version to 2.1.0 2021-04-08 12:20:21 -07:00
1d3a79437c add theme creator module to simplify the scaffolding of external themes 2021-04-07 13:05:18 -04:00
947c254e12 Merge pull request #1230 from sbwalker/dev
add theme creator modules to simplify the scaffolding of external themes
2021-04-07 13:03:04 -04:00
d05747af1e enhanced ModuleActions component to display panes in a submenu, added more containers to Oqtane theme, added more panes to MultiPane layout, added module outline in edit mode to distinguish modules in panes, consolidated to use a single default AdminPane named "Content", fixed bug related to custom Admin Container behavior 2021-04-06 17:45:11 -04:00
0bf85a7a14 Merge pull request #1228 from sbwalker/dev
enhanced ModuleActions component to display panes in a submenu, added more containers to Oqtane theme, added more panes to MultiPane layout, added module outline in edit mode to distinguish modules in panes, consolidated to use a single default AdminPane named "Content", fixed bug related to custom Admin Container behavior
2021-04-06 17:42:43 -04:00
6b90e0f37b fix #1197 - move folder path logic from Client Service to Server Controller 2021-04-05 08:53:22 -04:00
224f0c3ceb Merge pull request #1227 from sbwalker/dev
fix #1197 - move folder path logic from Client Service to Server Controller
2021-04-05 08:52:29 -04:00
fc145a167d organize theme components into folders 2021-04-03 19:46:29 -04:00
6354c9ebd0 Merge pull request #1226 from sbwalker/dev
organize theme components into folders
2021-04-03 19:43:49 -04:00
61b73060e5 login form validation 2021-04-02 16:14:02 -04:00
b5c1184fa4 Merge pull request #1224 from sbwalker/dev
login form validation
2021-04-02 16:11:38 -04:00
d12b18350f Fixed bug in AddSite due to missing assignment of database type 2021-04-02 11:49:38 -07:00
985987d8f4 Updated initial appsettings.json 2021-04-02 11:28:34 -07:00
e6530ee127 Added support for MySQL and ProgreSQL and AddSite/Tenant 2021-04-02 10:55:00 -07:00
6606ebb58a Merge pull request #88 from oqtane/dev
sync
2021-04-02 13:19:36 -04:00
14480edd67 added globally unique identifier for Site ( used string data type to ensure compatibility with multiple database engines ) 2021-04-02 12:30:20 -04:00
d990a9fc30 Merge pull request #1221 from sbwalker/dev
added globally unique identifier for Site ( used string data type to ensure compatibility with multiple database engines )
2021-04-02 12:27:43 -04:00
00f8f2cb89 fix #1211 - add message to indicate Module Creator is only intended for use in development environments 2021-04-02 08:37:37 -04:00
57575945cd Merge pull request #1219 from sbwalker/dev
fix #1211 - add message to indicate Module Creator is only intended for use in development environments
2021-04-02 08:35:04 -04:00
9995066628 Merge pull request #1216 from leigh-pointer/dev.fork
Added Keypress event to catch Enter key on Login page #1198
2021-04-02 08:20:42 -04:00
c794f77d65 fix #1217 - ensure parent page cannot be set to current page 2021-04-02 08:19:55 -04:00
6d443842b2 Merge pull request #1218 from sbwalker/dev
fix #1217 - ensure parent page cannot be set to current page
2021-04-02 08:17:17 -04:00
60a1f4ed15 Added functionality, when the EnterKey is press the login command is run. 2021-04-02 07:01:27 +02:00
59e5eabeb8 Merge pull request #3 from oqtane/dev
Pull from oqtane.dev
2021-04-02 06:43:44 +02:00
8da55e1926 Merge pull request #3 from oqtane/dev
sync
2021-04-01 19:31:21 -04:00
af5f79d343 fix dropdown list UX behavior where there is a default option 2021-04-01 17:58:59 -04:00
39d0afa321 Merge pull request #1215 from sbwalker/dev
fix dropdown list UX behavior where there is a default option
2021-04-01 17:56:29 -04:00
0a7fe5d82e Merge pull request #2 from oqtane/dev
Pull from source
2021-04-01 16:45:16 +02:00
58c84da9c9 add ability to test SMTP connection in Site Settings 2021-04-01 09:44:07 -04:00
46219df6b6 Merge pull request #1213 from sbwalker/dev
add ability to test SMTP connection in Site Settings
2021-04-01 09:41:45 -04:00
49f21e235b Merge pull request #87 from oqtane/dev
sync
2021-03-31 15:37:15 -04:00
bd48e1d8f1 if running on WebAssembly reload the client application if the server application is restarted 2021-03-31 15:39:01 -04:00
710d1be64b Merge pull request #1212 from sbwalker/dev
if running on WebAssembly reload the client application if the server application is restarted
2021-03-31 15:36:32 -04:00
bde1ff44f8 Merge pull request #1206 from hishamco/localizer
Fix localizer in Admin pages
2021-03-31 10:16:53 -04:00
c92a81fcb6 Remove unnecessary localizer from RecycleBin page 2021-03-31 14:15:36 +03:00
ec0b317f80 Fix localizer in Admin pages 2021-03-31 00:50:19 +03:00
09c040128a Ensure Install Wizard will only be displayed if the Master database connection string in appsettings.json is not specified. This addresses a potential security issue where the Install Wizard could be displayed in an existing installation if the Master database connection failed during startup. 2021-03-30 17:48:49 -04:00
c037614917 Merge pull request #1205 from sbwalker/dev
Ensure Install Wizard will only be displayed if the Master database connection string in appsettings.json is not specified. This addresses a potential security issue where the Install Wizard could be displayed in an existing installation if the Master database connection failed during startup.
2021-03-30 17:46:42 -04:00
3c5e7f997b Merge pull request #1204 from hishamco/typo
thene -> theme
2021-03-30 15:50:56 -04:00
367a23171d thene -> theme 2021-03-30 17:26:03 +03:00
0e20cad2d1 Merge pull request #1 from oqtane/dev
pull from source dev
2021-03-30 16:22:55 +02:00
5cd1d3a7af Merge pull request #86 from oqtane/dev
sync
2021-03-30 10:05:35 -04:00
62362b9194 make module creator templates extensible 2021-03-30 10:06:25 -04:00
40501c6ebd Merge pull request #1202 from hishamco/resources
Add Resources folder to Oqtane.Client
2021-03-30 10:04:38 -04:00
82b8c86fe3 Merge pull request #1203 from sbwalker/dev
make module creator templates extensible
2021-03-30 10:03:56 -04:00
40f9437ea5 Add Resources folder to Oqtane.Client 2021-03-29 20:15:04 +03:00
d8bcc32239 refactor user deletion 2021-03-29 12:58:40 -04:00
77694da100 Merge pull request #1201 from sbwalker/dev
refactor user deletion
2021-03-29 12:56:18 -04:00
2fb63e8117 Added suuport to inject an IOqtaneDatabase in EntityBuilders to allow each Database to control certain Migration behaviors. Also updated Installer to dynamically build Database Configuration section 2021-03-27 11:16:16 -07:00
7223952eb2 Merge pull request #85 from oqtane/dev
sync
2021-03-25 13:49:47 -04:00
1271064872 Merge pull request #1178 from Raceeend/user-delete
fix: user delete
2021-03-25 13:49:11 -04:00
095d2e5f45 Merge pull request #1188 from erw13n/dev
Remove admin border after edit
2021-03-25 08:33:06 -04:00
4152b5c360 Merge pull request #1177 from hishamco/profile
Delete profile should refresh profiles list
2021-03-25 08:12:53 -04:00
3a032f401a Added IDatabase interface and refactored to use it to handle database type - updated Installer to dynamically add databases to selector 2021-03-24 11:45:44 -07:00
cbcfc88492 Add support for Sqlite - Installation path tested but AddSite not supported yet 2021-03-23 11:06:18 -07:00
8f1c760e87 Updated the Installation of Oqtane to use Migrations 2021-03-21 14:52:45 -07:00
83e5502111 Creation of EF Core Migrations - these execute using EF Tools, but are not integrated to run programmatically 2021-03-19 17:01:49 -07:00
63140bce81 Remove admin border after edit
After finish Edit, there's a class "container" which is conflict with Bootstrap that cause an issue on Full-width pane.
2021-03-17 19:26:50 +07:00
c527f28a6d Address feedback 2021-03-13 17:02:24 +03:00
801615c981 fix: user delete 2021-03-13 12:34:47 +01:00
c5e3c9b35e Delete profile should refresh profiles list 2021-03-13 13:02:40 +03:00
86ce8994d9 fix #1156 add defensive coding for scenario where host name does not match any alias 2021-03-11 20:21:15 -05:00
51687c382d Merge pull request #1174 from sbwalker/dev
fix #1156 add defensive coding for scenario where host name does not match any alias
2021-03-11 20:19:07 -05:00
6d3e17a5f5 Fix Pager component issue which manifested itself in Event Log. This reverts a fix from #1160 and addresses the root problem. 2021-03-11 11:21:47 -05:00
32eaa90e5e Merge pull request #1171 from sbwalker/dev
Fix Pager component issue which manifested itself in Event Log. This reverts a fix from #1160 and addresses the root problem.
2021-03-11 11:19:58 -05:00
ed12194ea2 Merge pull request #84 from oqtane/dev
sync
2021-03-10 17:01:33 -05:00
6bde83fab1 Merge pull request #1163 from leigh-pointer/dev
Issue with menu Icon Spacing. #1160
2021-03-10 17:00:08 -05:00
b58c7386b3 Fixed issue Event Log strange behavior with the Paging control #1158 2021-03-10 17:00:49 +01:00
3ff1dc4a21 MenuItems updated with HTML markup in place of the FontIcon component 2021-03-09 17:16:25 +01:00
78aabaafb3 Merge pull request #11 from oqtane/dev
Merge from source
2021-03-09 15:50:08 +01:00
81b76f03ee Add folder structure to external module template client project to organize code and emphasize that a module project can contain multiple modules. 2021-03-09 09:47:01 -05:00
c9fa022279 Merge pull request #1161 from hishamco/fix#1152
Rows -> Maximum Records
2021-03-09 09:46:35 -05:00
7e4a37b07c Merge pull request #1168 from cnurse/dev
Create new UseOqtaneDatabase extension method
2021-03-09 09:46:14 -05:00
642bafb2fc Merge pull request #1170 from sbwalker/dev
Add folder structure to external module template client project to organize code and emphasize that a module project can contain multiple modules.
2021-03-09 09:46:05 -05:00
7f1f4ec4fb Rows -> Records 2021-03-08 20:57:06 +03:00
c64f350f36 Create new DbContextUtils class and move common SaveChanges code to this class. While MasterDbContext has no entities that support IDeletable it does not hurt to check and minimizes duplication 2021-03-06 16:06:29 -08:00
8376a09ad6 Create new UseOqtaneDatabase extension method and replace all uses of UseSqlServer.
This is a preparative step to locate all database configuration in one file.
2021-03-06 14:13:06 -08:00
13b45453c6 Fixed Issue with menu Icon Spacing. #1160 2021-03-05 15:31:38 +01:00
4c9960b983 Rows -> Maximum Rows 2021-03-05 16:41:53 +03:00
541bd5a279 Merge pull request #10 from oqtane/dev
Merge
2021-03-05 06:32:14 +01:00
745575c1f1 Fix UX issue where the Location field was not being updated when the user selected Create Module 2021-03-04 08:30:09 -05:00
af896e0fa8 Merge pull request #1155 from sbwalker/dev
Fix UX issue where the Location field was not being updated when the user selected Create Module
2021-03-04 08:29:54 -05:00
ab102f1883 Merge pull request #1154 from leigh-pointer/dev
Delete all Modules and Pages from Recycle bin
2021-03-04 08:29:45 -05:00
04caa8f725 Merge pull request #1151 from cnurse/dev
Add gitignore and README to Solution files and ad support for Jetbrains Rider
2021-03-04 08:28:23 -05:00
63fc1cd042 Update Index.razor 2021-03-03 17:15:00 +01:00
442a8018ce Merge branch 'dev' of https://github.com/leigh-pointer/oqtane.framework into dev 2021-03-03 17:07:04 +01:00
0fd46d28b5 Delete all modules and pages in recycle bin 2021-03-03 16:55:13 +01:00
a03434ac41 recycle bin and log paging 2021-03-03 16:39:32 +01:00
b08fdf8690 Merge branch 'dev' of https://github.com/leigh-pointer/oqtane.framework into dev 2021-03-03 13:26:01 +01:00
7205246718 Added Delete all pages and modules 2021-03-03 13:25:49 +01:00
a4f0afa0ab Merge branch 'master' into dev 2021-03-02 12:14:41 -08:00
dbc4e3ea66 Add the Jetbrains Rider "idea" folder to gitignore. 2021-03-02 12:07:04 -08:00
0d5ecb7427 Include gitignore and README files in Solution Files folder, so they are more accessible for editing 2021-03-02 12:03:26 -08:00
e1b04f5dae Merge pull request #9 from oqtane/dev
Merge pull request #1149 from leigh-pointer/dev
2021-03-02 16:52:28 +01:00
ea1bcba32f Merge pull request #1149 from leigh-pointer/dev
Fix for #1148 LangVersion set to 7.3
2021-03-02 10:48:13 -05:00
cccb0806e1 LangVersion Fix #1148
removed the LangVersion property from project
2021-03-02 16:17:33 +01:00
92a1804846 Merge pull request #8 from oqtane/dev
2.0.1
2021-02-28 08:29:21 +01:00
191a1dea4d Update README.md 2021-02-27 12:50:36 -05:00
be782ee001 Merge pull request #1143 from oqtane/master
sync with master
2021-02-27 12:34:28 -05:00
d69a392141 Merge pull request #7 from oqtane/dev
Commits on Feb 25,26,  2021
2021-02-26 15:49:06 +01:00
579992d8f2 Merge pull request #6 from oqtane/dev
Merge pull request #1135 from leigh-pointer/dev
2021-02-25 15:49:43 +01:00
12b44f6fa5 Merge pull request #2 from oqtane/dev
sync
2021-02-23 18:50:55 -05:00
8cd355135b Merge pull request #1 from oqtane/dev
sync
2021-02-15 13:49:36 -05:00
633 changed files with 34387 additions and 21732 deletions

8
.gitignore vendored
View File

@ -7,10 +7,18 @@ msbuild.binlog
.vscode/
*.binlog
*.nupkg
*.zip
*.idea
Oqtane.Server/appsettings.json
Oqtane.Server/Data/*.mdf
Oqtane.Server/Data/*.ldf
Oqtane.Server/Data/*.db
/Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml
Oqtane.Server/Content
Oqtane.Server/Packages
Oqtane.Server/wwwroot/Content
Oqtane.Server/wwwroot/Packages/*.log

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018-2020 .NET Foundation
Copyright (c) 2018-2021 .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,34 +1,76 @@
@inject IInstallationService InstallationService
@inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime
@inject SiteState SiteState
@if (_initialized)
{
@if (!_installed)
@if (!_installation.Success)
{
<Installer />
}
else
{
<CascadingAuthenticationState>
<CascadingValue Value="@PageState">
<SiteRouter OnStateChange="@ChangeState" />
</CascadingValue>
</CascadingAuthenticationState>
@if (string.IsNullOrEmpty(_installation.Message))
{
<div style="@_display">
<CascadingAuthenticationState>
<CascadingValue Value="@PageState">
<SiteRouter Runtime="@Runtime" RenderMode="@RenderMode" OnStateChange="@ChangeState" />
</CascadingValue>
</CascadingAuthenticationState>
</div>
}
else
{
<div class="app-alert">
@_installation.Message
</div>
}
}
}
@code {
private bool _initialized;
private bool _installed;
[Parameter]
public string AntiForgeryToken { get; set; }
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string RenderMode { get; set; }
private bool _initialized = false;
private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" };
private PageState PageState { get; set; }
protected override async Task OnParametersSetAsync()
{
var installation = await InstallationService.IsInstalled();
_installed = installation.Success;
SiteState.AntiForgeryToken = AntiForgeryToken;
InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken);
_installation = await InstallationService.IsInstalled();
if (_installation.Alias != null)
{
SiteState.Alias = _installation.Alias;
}
else
{
_installation.Message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name";
}
_initialized = true;
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
_display = "";
StateHasChanged();
}
}
private void ChangeState(PageState pageState)
{
PageState = pageState;

View File

@ -1,3 +1,5 @@
using Microsoft.Extensions.Localization;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Localization;
[assembly: RootNamespace("Oqtane")]
[assembly: InternalsVisibleTo("Oqtane.Server")]

View File

@ -0,0 +1,22 @@
namespace Microsoft.Extensions.Localization
{
public static class OqtaneLocalizationExtensions
{
/// <summary>
/// Gets the string resource for the specified key and returns the value if the resource does not exist
/// </summary>
/// <param name="localizer"></param>
/// <param name="key">the static key used to identify the string resource</param>
/// <param name="value">the default value if the resource for the static key does not exist</param>
/// <returns></returns>
public static string GetString(this IStringLocalizer localizer, string key, string value)
{
string localizedValue = localizer[key];
if (localizedValue == key && !string.IsNullOrEmpty(value)) // not localized
{
localizedValue = value;
}
return localizedValue;
}
}
}

View File

@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Components.Authorization;
using Oqtane.Providers;
using Oqtane.Services;
using Oqtane.Shared;
namespace Microsoft.Extensions.DependencyInjection
{
public static class OqtaneServiceCollectionExtensions
{
public static IServiceCollection AddOqtaneAuthorization(this IServiceCollection services)
{
services.AddAuthorizationCore();
services.AddScoped<IdentityAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
return services;
}
internal static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
{
services.AddScoped<SiteState>();
services.AddScoped<IInstallationService, InstallationService>();
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
services.AddScoped<IThemeService, ThemeService>();
services.AddScoped<IAliasService, AliasService>();
services.AddScoped<ITenantService, TenantService>();
services.AddScoped<ISiteService, SiteService>();
services.AddScoped<IPageService, PageService>();
services.AddScoped<IModuleService, ModuleService>();
services.AddScoped<IPageModuleService, PageModuleService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IProfileService, ProfileService>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<IUserRoleService, UserRoleService>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IPackageService, PackageService>();
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<INotificationService, NotificationService>();
services.AddScoped<IFolderService, FolderService>();
services.AddScoped<IFileService, FileService>();
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
services.AddScoped<ISqlService, SqlService>();
services.AddScoped<ISystemService, SystemService>();
services.AddScoped<ILocalizationService, LocalizationService>();
services.AddScoped<ILanguageService, LanguageService>();
services.AddScoped<IDatabaseService, DatabaseService>();
services.AddScoped<ISyncService, SyncService>();
return services;
}
}
}

View File

@ -0,0 +1,32 @@
@namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
<div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
<div class="col-sm-9">
<input id="database" type="text" class="form-control" @bind="@_database" />
</div>
</div>
@code {
private string _server = "(LocalDb)\\MSSQLLocalDB";
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
public string GetConnectionString()
{
var connectionString = String.Empty;
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
{
connectionString = $"Data Source={_server};AttachDbFilename=|DataDirectory|\\{_database}.mdf;Initial Catalog={_database};Integrated Security=SSPI;Encrypt=false;";
}
return connectionString;
}
}

View File

@ -0,0 +1,58 @@
@namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
<div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="port" HelpText="Enter the port used to connect to the server" ResourceKey="Port">Port:</Label>
<div class="col-sm-9">
<input id="port" type="text" class="form-control" @bind="@_port" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
<div class="col-sm-9">
<input id="database" type="text" class="form-control" @bind="@_database" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
<div class="col-sm-9">
<input id="uid" type="text" class="form-control" @bind="@_uid" />
</div>
</div>
<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" />
</div>
</div>
@code {
private string _server = "127.0.0.1";
private string _port = "3306";
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
private string _uid = String.Empty;
private string _pwd = String.Empty;
public string GetConnectionString()
{
var connectionString = String.Empty;
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database) && !String.IsNullOrEmpty(_uid) && !String.IsNullOrEmpty(_pwd))
{
connectionString = $"Server={_server};Database={_database};Uid={_uid};Pwd={_pwd};";
}
if (!String.IsNullOrEmpty(_port))
{
connectionString += $"Port={_port};";
}
return connectionString;
}
}

View File

@ -0,0 +1,83 @@
@namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<PostgreSQLConfig> Localizer
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
<div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="port" HelpText="Enter the port used to connect to the server" ResourceKey="Port">Port:</Label>
<div class="col-sm-9">
<input id="port" type="text" class="form-control" @bind="@_port" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
<div class="col-sm-9">
<input id="database" type="text" class="form-control" @bind="@_database" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="security" HelpText="Select your security method" ResourceKey="Security">Security:</Label>
<div class="col-sm-9">
<select id="security" class="form-select custom-select" @bind="@_security">
<option value="integrated" selected>@Localizer["Integrated"]</option>
<option value="custom">@Localizer["Custom"]</option>
</select>
</div>
</div>
@if (_security == "custom")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
<div class="col-sm-9">
<input id="uid" type="text" class="form-control" @bind="@_uid" />
</div>
</div>
<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" />
</div>
</div>
}
@code {
private string _server = "127.0.0.1";
private string _port = "5432";
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
private string _security = "integrated";
private string _uid = String.Empty;
private string _pwd = String.Empty;
public string GetConnectionString()
{
var connectionString = String.Empty;
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database) && !String.IsNullOrEmpty(_port))
{
connectionString = $"Server={_server};Port={_port};Database={_database};";
}
if (_security == "integrated")
{
connectionString += "Integrated Security=true;";
}
else
{
if (!String.IsNullOrEmpty(_uid) && !String.IsNullOrEmpty(_pwd))
{
connectionString += $"User ID={_uid};Password={_pwd};";
}
else
{
connectionString = String.Empty;
}
}
return connectionString;
}
}

View File

@ -0,0 +1,95 @@
@namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<SqlServerConfig> 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>
<div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
<div class="col-sm-9">
<input id="database" type="text" class="form-control" @bind="@_database" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="security" HelpText="Select your security method" ResourceKey="Security">Security:</Label>
<div class="col-sm-9">
<select id="security" class="form-select custom-select" @bind="@_security">
<option value="integrated" selected>@Localizer["Integrated"]</option>
<option value="custom">@Localizer["Custom"]</option>
</select>
</div>
</div>
@if (_security == "custom")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
<div class="col-sm-9">
<input id="uid" type="text" class="form-control" @bind="@_uid" />
</div>
</div>
<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" />
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="encryption" HelpText="Specify if you are using an encrypted database connection. It is highly recommended to use encryption in a production environment." ResourceKey="Encryption">Encryption:</Label>
<div class="col-sm-9">
<select id="encryption" class="form-select custom-select" @bind="@_encryption">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</div>
</div>
@if (_encryption == "true")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption" ResourceKey="TrustServerCertificate">Certificate:</Label>
<div class="col-sm-9">
<select id="encryption" class="form-select custom-select" @bind="@_trustservercertificate">
<option value="true">@Localizer["Self Signed"]</option>
<option value="false">@Localizer["Verifiable"]</option>
</select>
</div>
</div>
}
@code {
private string _server = String.Empty;
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
private string _security = "integrated";
private string _uid = String.Empty;
private string _pwd = String.Empty;
private string _encryption = "false";
private string _trustservercertificate = "false";
public string GetConnectionString()
{
var connectionString = String.Empty;
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
{
connectionString = $"Data Source={_server};Initial Catalog={_database};";
}
if (_security == "integrated")
{
connectionString += "Integrated Security=SSPI;";
}
else
{
connectionString += $"User ID={_uid};Password={_pwd};";
}
connectionString += $"Encrypt={_encryption};";
connectionString += $"TrustServerCertificate={_trustservercertificate};";
return connectionString;
}
}

View File

@ -0,0 +1,25 @@
@namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the file name to use for the database" ResourceKey="Server">File Name:</Label>
<div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" />
</div>
</div>
@code {
private string _server = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm") + ".db";
public string GetConnectionString()
{
var connectionstring = String.Empty;
if (!String.IsNullOrEmpty(_server))
{
connectionstring = $"Data Source={_server};";
}
return connectionstring;
}
}

View File

@ -0,0 +1,214 @@
@namespace Oqtane.Installer
@using Oqtane.Interfaces
@inject NavigationManager NavigationManager
@inject IInstallationService InstallationService
@inject ISiteService SiteService
@inject IUserService UserService
@inject IDatabaseService DatabaseService
@inject IJSRuntime JSRuntime
@inject IStringLocalizer<Installer> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<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>
</div>
<hr class="app-rule" />
<div class="row justify-content-center">
<div class="col text-center">
<h2>@Localizer["DatabaseConfig"]</h2><br />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databasetype" HelpText="Select the type of database you wish to use" ResourceKey="DatabaseType">Database:</Label>
<div class="col-sm-9">
@if (_databases != null)
{
<select id="databasetype" class="form-select custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
@foreach (var database in _databases)
{
if (database.IsDefault)
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
}
</select>
}
</div>
</div>
@{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent;
}
}
</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>
<div class="col-sm-9">
<input id="username" type="text" class="form-control" @bind="@_hostUsername" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@_hostPassword" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
<div class="col-sm-9">
<input id="confirm" type="password" class="form-control" @bind="@_confirmPassword" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
<div class="col-sm-9">
<input type="text" class="form-control" @bind="@_hostEmail" />
</div>
</div>
</div>
</div>
</div>
<hr class="app-rule" />
<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>
</div>
<div class="app-progress-indicator" style="@_loadingDisplay"></div>
</div>
<div class="row">
<div class="mx-auto text-center">
<input type="checkbox" @bind="@_register" /> @Localizer["Register"]
</div>
</div>
</div>
@code {
private List<Database> _databases;
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty;
private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty;
private bool _register = true;
private string _message = string.Empty;
private string _loadingDisplay = "display: none;";
protected override async Task OnInitializedAsync()
{
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
{
_databaseName = _databases.Find(item => item.IsDefault).Name;
}
else
{
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
}
private void DatabaseChanged(ChangeEventArgs eventArgs)
{
try
{
_databaseName = (string)eventArgs.Value;
LoadDatabaseConfigComponent();
}
catch
{
_message = Localizer["Error.DbConfig.Load"];
}
}
private void LoadDatabaseConfigComponent()
{
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (database != null)
{
_databaseConfigType = Type.GetType(database.ControlType);
DatabaseConfigComponent = builder =>
{
builder.OpenComponent(0, _databaseConfigType);
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
builder.CloseComponent();
};
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/css/bootstrap.min.css", "text/css", "sha512-usVBAd66/NpVNfBge19gws2j6JZinnca12rAe2l+d+QkLU9fiG02O1X8Q6hepIpr/EYKZvKx/I9WsnujJuOmBA==", "anonymous", "");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/js/bootstrap.min.js", "sha512-a6ctI6w1kg3J4dSjknHj3aWLEbjitAXAjLDRUxo2wyYmDFRcz2RJuQr5M3Kt8O/TtUSp8n2rAyaXYy1sjoKmrQ==", "anonymous", "", "head", "");
}
}
private async Task Install()
{
var connectionString = String.Empty;
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
{
_loadingDisplay = "";
StateHasChanged();
Uri uri = new Uri(NavigationManager.Uri);
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var config = new InstallConfig
{
DatabaseType = database.DBType,
ConnectionString = connectionString,
Aliases = uri.Authority,
HostUsername = _hostUsername,
HostPassword = _hostPassword,
HostEmail = _hostEmail,
HostName = _hostUsername,
TenantName = TenantNames.Master,
IsNewTenant = true,
SiteName = Constants.DefaultSite,
Register = _register
};
var installation = await InstallationService.Install(config);
if (installation.Success)
{
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
}
else
{
_message = installation.Message;
_loadingDisplay = "display: none;";
}
}
else
{
_message = Localizer["Message.Require.DbInfo"];
}
}
}

View File

@ -2,6 +2,7 @@
@inherits ModuleBase
@inject IPageService PageService
@inject IUserService UserService
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row">
@foreach (var p in _pages)
@ -11,7 +12,7 @@
string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center">
<NavLink class="nav-link" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@p.Name
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
</NavLink>
</div>
}
@ -20,7 +21,7 @@
@code {
private List<Page> _pages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override void OnInitialized()

View File

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

View File

@ -5,58 +5,64 @@
@inject IFileService FileService
@inject IFolderService FolderService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Upload" Heading="Upload Files" ResourceKey="UploadFiles">
<table class="table table-borderless">
<tr>
<td>
<Label For="upload" HelpText="Upload the file you want" ResourceKey="Upload">Upload: </Label>
</td>
<td>
<FileManager UploadMultiple="true" ShowFiles="false" FolderId="@_folderId" />
</td>
</tr>
</table>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="upload" HelpText="Upload the file you want" ResourceKey="Upload">Upload: </Label>
<div class="col-sm-9">
<FileManager UploadMultiple="true" ShowFiles="false" FolderId="@_folderId" ShowSuccess="true" />
</div>
</div>
</div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
<TabPanel Name="Download" Heading="Download Files" ResourceKey="DownloadFiles">
@if (_folders != null)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="url" HelpText="Enter the url of the file you wish to download" ResourceKey="Url">Url: </Label>
</td>
<td>
<input id="url" class="form-control" @bind="@url" />
</td>
</tr>
<tr>
<td>
<Label For="folder" HelpText="Select the folder to save the file in" ResourceKey="Folder">Folder: </Label>
</td>
<td>
<select id="folder" class="form-control" @bind="@_folderId">
<option value="-1">&lt;@Localizer["Select Folder"]&gt;</option>
@foreach (Folder folder in _folders)
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
</select>
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="Download">@Localizer["Download"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<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="url" HelpText="Enter the url of the file you wish to download" ResourceKey="Url">Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="folder" HelpText="Select the folder to save the file in" ResourceKey="Folder">Folder: </Label>
<div class="col-sm-9">
<select id="folder" class="form-select" @bind="@_folderId" required>
<option value="-1">&lt;@Localizer["Folder.Select"]&gt;</option>
@foreach (Folder folder in _folders)
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the name of the file being downloaded" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" />
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="Download">@SharedLocalizer["Download"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
}
</TabPanel>
</TabStrip>
@code {
private string url = string.Empty;
private ElementReference form;
private bool validated = false;
private string _url = string.Empty;
private List<Folder> _folders;
private int _folderId = -1;
private string _name = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -72,37 +78,48 @@
private async Task Download()
{
if (url == string.Empty || _folderId == -1)
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
AddModuleMessage(Localizer["You Must Enter A Url And Select A Folder"], MessageType.Warning);
return;
}
if (_url == string.Empty || _folderId == -1)
{
AddModuleMessage(Localizer["Message.Required.UrlFolder"], MessageType.Warning);
return;
}
var filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
if (string.IsNullOrEmpty(_name))
{
_name = _url.Substring(_url.LastIndexOf("/", StringComparison.Ordinal) + 1);
}
if (!Constants.UploadableFiles.Split(',')
.Contains(Path.GetExtension(filename).ToLower().Replace(".", "")))
{
AddModuleMessage(Localizer["File Could Not Be Downloaded From Url Due To Its File Extension"], MessageType.Warning);
return;
}
if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(_name).ToLower().Replace(".", "")))
{
AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning);
return;
}
if (!filename.IsPathOrFileValid())
{
AddModuleMessage(Localizer["You Must Enter A Url With A Valid File Name"], MessageType.Warning);
return;
}
if (!_name.IsPathOrFileValid())
{
AddModuleMessage(Localizer["Message.Required.UrlName"], MessageType.Warning);
return;
}
try
{
await FileService.UploadFileAsync(url, _folderId);
await logger.LogInformation("File Downloaded Successfully From Url {Url}", url);
AddModuleMessage(Localizer["File Downloaded Successfully From Url"], MessageType.Success);
try
{
await FileService.UploadFileAsync(_url, _folderId, _name);
await logger.LogInformation("File Downloaded Successfully From Url {Url}", _url);
AddModuleMessage(Localizer["Success.Download.File"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading File From Url {Url} {Error}", _url, ex.Message);
AddModuleMessage(Localizer["Error.Download.InvalidUrl"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Downloading File From Url {Url} {Error}", url, ex.Message);
AddModuleMessage(Localizer["Error Downloading File From Url. Please Verify That The Url Is Valid."], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}
}

View File

@ -4,52 +4,58 @@
@inject IFolderService FolderService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Details> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_folders != null)
{
<table class="table table-borderless">
<tr>
<td>
<Label for="name" HelpText="The name of the file" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="parent" HelpText="The folder where the file is located" ResourceKey="Folder">Folder: </Label>
</td>
<td>
<select id="parent" class="form-control" @bind="@_folderId">
@foreach (Folder folder in _folders)
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label for="size" HelpText="The size of the file (in bytes)" ResourceKey="Size">Size: </Label>
</td>
<td>
<input id="size" class="form-control" @bind="@_size" readonly />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveFile">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
<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 file" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="The folder where the file is located" ResourceKey="Folder">Folder: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" @bind="@_folderId" required>
@foreach (Folder folder in _folders)
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="A description of the file. This can be used as a caption for image files." ResourceKey="Description">Description: </Label>
<div class="col-sm-9">
<input id="description" class="form-control" @bind="@_description" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="size" HelpText="The size of the file (in bytes)" ResourceKey="Size">Size: </Label>
<div class="col-sm-9">
<input id="size" class="form-control" @bind="@_size" readonly />
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
</form>
}
@code {
private ElementReference form;
private bool validated = false;
private int _fileId = -1;
private string _name;
private List<Folder> _folders;
private int _folderId = -1;
private string _description = string.Empty;
private int _size;
private string _createdBy;
private DateTime _createdOn;
@ -71,6 +77,7 @@
{
_name = file.Name;
_folderId = file.FolderId;
_description = file.Description;
_size = file.Size;
_createdBy = file.CreatedBy;
_createdOn = file.CreatedOn;
@ -81,32 +88,42 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading File {FileId} {Error}", _fileId, ex.Message);
AddModuleMessage(Localizer["Error Loading File"], MessageType.Error);
AddModuleMessage(Localizer["Error.File.Load"], MessageType.Error);
}
}
private async Task SaveFile()
{
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (_name.IsPathOrFileValid())
try
{
File file = await FileService.GetFileAsync(_fileId);
file.Name = _name;
file.FolderId = _folderId;
file = await FileService.UpdateFileAsync(file);
await logger.LogInformation("File Saved {File}", file);
NavigationManager.NavigateTo(NavigateUrl());
if (_name.IsPathOrFileValid())
{
File file = await FileService.GetFileAsync(_fileId);
file.Name = _name;
file.FolderId = _folderId;
file.Description = _description;
file = await FileService.UpdateFileAsync(file);
await logger.LogInformation("File Saved {File}", file);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.File.InvalidName"], MessageType.Warning);
}
}
else
catch (Exception ex)
{
AddModuleMessage(Localizer["File Name Not Valid"], MessageType.Warning);
await logger.LogError(ex, "Error Saving File {FileId} {Error}", _fileId, ex.Message);
AddModuleMessage(Localizer["Error.File.Save"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Saving File {FileId} {Error}", _fileId, ex.Message);
AddModuleMessage(Localizer["Error Saving File"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}
}

View File

@ -4,49 +4,80 @@
@inject IFileService FileService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_folders != null)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
</td>
<td>
<select id="parent" class="form-control" @bind="@_parentId">
<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="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" @bind="@_parentId" required>
@if (PageState.QueryString.ContainsKey("id"))
{
<option value="-1">&lt;@Localizer["NoParent"]&gt;</option>
}
@foreach (Folder folder in _folders)
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label>
<div class="col-sm-9">
@if (PageState.QueryString.ContainsKey("id"))
{
<option value="-1">&lt;@Localizer["No Parent"]&gt;</option>
<input id="type" class="form-control" readonly @bind="@_type" />
}
@foreach (Folder folder in _folders)
else
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
<select id="type" class="form-select" @bind="@_type" required>
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
</select>
}
</select>
</td>
</tr>
<tr>
<td>
<Label for="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td colspan="2" align="center">
<Label For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="Browse,View,Edit" Permissions="@_permissions" @ref="_permissionGrid" />
</td>
</tr>
</table>
</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>
<div class="col-sm-9">
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
<div class="col-sm-9">
<input id="capacity" class="form-control" @bind="@_capacity" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-12">
<Label Class="col-sm-3" For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</div>
</form>
@if (!_isSystem)
{
<button type="button" class="btn btn-success" @onclick="SaveFolder">@Localizer["Save"]</button>
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
@((MarkupString)"&nbsp;")
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@if (!_isSystem && PageState.QueryString.ContainsKey("id"))
{
@((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 />
@ -58,10 +89,15 @@
}
@code {
private ElementReference form;
private bool validated = false;
private List<Folder> _folders;
private int _folderId = -1;
private string _name;
private int _parentId = -1;
private string _name;
private string _type = FolderTypes.Private;
private string _imagesizes = string.Empty;
private string _capacity = "0";
private bool _isSystem;
private string _permissions = string.Empty;
private string _createdBy;
@ -91,6 +127,9 @@
{
_parentId = folder.ParentId ?? -1;
_name = folder.Name;
_type = folder.Type;
_imagesizes = folder.ImageSizes;
_capacity = folder.Capacity.ToString();
_isSystem = folder.IsSystem;
_permissions = folder.Permissions;
_createdBy = folder.CreatedBy;
@ -102,81 +141,92 @@
else
{
_parentId = _folders[0].FolderId;
_permissions = string.Empty;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Folder {FolderId} {Error}", _folderId, ex.Message);
AddModuleMessage(Localizer["Error Loading Folder"], MessageType.Error);
AddModuleMessage(Localizer["Error.Folder.Load"], MessageType.Error);
}
}
private async Task SaveFolder()
{
if (_name == string.Empty || _parentId == -1)
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
AddModuleMessage(Localizer["Folders Must Have A Parent And A Name"], MessageType.Warning);
return;
}
if (!_name.IsPathOrFileValid())
{
AddModuleMessage(Localizer["Folder Name Not Valid."], MessageType.Warning);
return;
}
try
{
Folder folder;
if (_folderId != -1)
if (_name == string.Empty || _parentId == -1)
{
folder = await FolderService.GetFolderAsync(_folderId);
}
else
{
folder = new Folder();
AddModuleMessage(Localizer["Message.Required.FolderParent"], MessageType.Warning);
return;
}
folder.SiteId = PageState.Site.SiteId;
if (_parentId == -1)
if (!_name.IsPathOrFileValid())
{
folder.ParentId = null;
}
else
{
folder.ParentId = _parentId;
AddModuleMessage(Localizer["Message.Folder.InvalidName"], MessageType.Warning);
return;
}
folder.Name = _name;
folder.IsSystem = _isSystem;
folder.Permissions = _permissionGrid.GetPermissions();
try
{
Folder folder;
if (_folderId != -1)
{
folder = await FolderService.GetFolderAsync(_folderId);
}
else
{
folder = new Folder();
}
if (_folderId != -1)
{
folder = await FolderService.UpdateFolderAsync(folder);
}
else
{
folder = await FolderService.AddFolderAsync(folder);
}
folder.SiteId = PageState.Site.SiteId;
if (folder != null)
{
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
await logger.LogInformation("Folder Saved {Folder}", folder);
NavigationManager.NavigateTo(NavigateUrl());
if (_parentId == -1)
{
folder.ParentId = null;
}
else
{
folder.ParentId = _parentId;
}
folder.Name = _name;
folder.Type = _type;
folder.ImageSizes = _imagesizes;
folder.Capacity = int.Parse(_capacity);
folder.IsSystem = _isSystem;
folder.Permissions = _permissionGrid.GetPermissions();
if (_folderId != -1)
{
folder = await FolderService.UpdateFolderAsync(folder);
}
else
{
folder = await FolderService.AddFolderAsync(folder);
}
if (folder != null)
{
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
await logger.LogInformation("Folder Saved {Folder}", folder);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error);
}
}
else
catch (Exception ex)
{
AddModuleMessage(Localizer["An Error Was Encountered Saving The Folder"], MessageType.Error);
await logger.LogError(ex, "Error Saving Folder {FolderId} {Error}", _folderId, ex.Message);
AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Saving Folder {FolderId} {Error}", _folderId, ex.Message);
AddModuleMessage(Localizer["Error Saving Folder"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
@ -204,18 +254,18 @@
}
else
{
AddModuleMessage(Localizer["Folder Has Files And Cannot Be Deleted"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Folder.Files.InvalidDelete"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Folder Has Subfolders And Cannot Be Deleted"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Folder.Subfolders.InvalidDelete"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Folder {Folder} {Error}", _folderId, ex.Message);
AddModuleMessage(Localizer["Error Deleting Folder"], MessageType.Error);
AddModuleMessage(Localizer["Error.Folder.Delete"], MessageType.Error);
}
}
}
}

View File

@ -4,50 +4,51 @@
@inject IFolderService FolderService
@inject IFileService FileService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_files != null)
{
<table class="table table-borderless">
<tr>
<td>
<label class="control-label">@Localizer["Folder:"] </label>
</td>
<td>
<select class="form-control" @onchange="(e => FolderChanged(e))">
<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">
<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>
</td>
<td>
</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" />
</td>
</tr>
</table>
</div>
</div>
</div>
<Pager Items="@_files">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@Localizer["Modified"]</th>
<th>@Localizer["Type"]</th>
<th>@Localizer["Size"]</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Modified"]</th>
<th>@Localizer["Type"]</th>
<th>@Localizer["Size"]</th>
</Header>
<Row>
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td>
<td><ActionDialog Header="Delete File" Message="@Localizer["Are You Sure You Wish To Delete {0}?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
<td><a href="@(ContentUrl(context.FileId))" target="_new">@context.Name</a></td>
<td><ActionDialog Header="Delete File" Message="@string.Format(Localizer["Confirm.File.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
<td><a href="@context.Url" target="_new">@context.Name</a></td>
<td>@context.ModifiedOn</td>
<td>@context.Extension.ToUpper() @Localizer["File"]</td>
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
</Row>
</Pager>
@if (_files.Count == 0)
{
<div class="text-center">@Localizer["No Files Exist In Selected Folder"]</div>
<div class="text-center">@Localizer["NoFiles"]</div>
}
}
@ -63,7 +64,7 @@
try
{
_folders = await FolderService.GetFoldersAsync(PageState.Site.SiteId);
if (_folderId == -1 && _folders.Count > 0)
{
_folderId = _folders[0].FolderId;
@ -73,7 +74,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Files"], MessageType.Error);
AddModuleMessage(Localizer["Error.File.Load"], MessageType.Error);
}
}
@ -93,7 +94,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Files"], MessageType.Error);
AddModuleMessage(Localizer["Error.File.Load"], MessageType.Error);
}
}
@ -103,14 +104,14 @@
{
await FileService.DeleteFileAsync(file.FileId);
await logger.LogInformation("File Deleted {File}", file.Name);
AddModuleMessage(Localizer["File {0} Deleted", file.Name], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.File.Delete"], file.Name), MessageType.Success);
await GetFiles();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting File {File} {Error}", file.Name, ex.Message);
AddModuleMessage(Localizer["Error Deleting File {0}", file.Name], MessageType.Error);
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
}
}
}

View File

@ -3,96 +3,117 @@
@inject NavigationManager NavigationManager
@inject IJobService JobService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
</td>
<td>
<input id="type" class="form-control" @bind="@_jobType" readonly />
</td>
</tr>
<tr>
<td>
<Label For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
</td>
<td>
<select id="enabled" class="form-control" @bind="@_isEnabled">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
</td>
<td>
<input id="runs-every" class="form-control" @bind="@_interval" />
<select id="runs-every" class="form-control" @bind="@_frequency">
<option value="m">@Localizer["Minute(s)"]</option>
<option value="H">@Localizer["Hour(s)"]</option>
<option value="d">@Localizer["Day(s)"]</option>
<option value="M">@Localizer["Month(s)"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="starting" HelpText="What time do you want the job to start" ResourceKey="Starting">Starting: </Label>
</td>
<td>
<input id="starting" class="form-control" @bind="@_startDate" />
</td>
</tr>
<tr>
<td>
<Label For="ending" HelpText="When do you want the job to end" ResourceKey="Ending">Ending: </Label>
</td>
<td>
<input id="ending" class="form-control" @bind="@_endDate" />
</td>
</tr>
<tr>
<td>
<Label For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
</td>
<td>
<input id="retention" class="form-control" @bind="@_retentionHistory" />
</td>
</tr>
<tr>
<td>
<Label For="next" HelpText="Next execution for this job." ResourceKey="NextExecution">Next Execution: </Label>
</td>
<td>
<input id="next" class="form-control" @bind="@_nextExecution" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveJob">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<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="Enter the job name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
<div class="col-sm-9">
<input id="type" class="form-control" @bind="@_jobType" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
<div class="col-sm-9">
<select id="enabled" class="form-select" @bind="@_isEnabled" 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="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 />
<select id="runs-every" class="form-select" @bind="@_frequency" required>
<option value="m">@Localizer["Minute(s)"]</option>
<option value="H">@Localizer["Hour(s)"]</option>
<option value="d">@Localizer["Day(s)"]</option>
<option value="M">@Localizer["Month(s)"]</option>
</select>
</div>
</div>
<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 />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="starting" HelpText="Optionally enter the date and time when this job should start executing" ResourceKey="Starting">Starting: </Label>
<div class="col-sm-9">
<div class="row">
<div class="col">
<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" />
</div>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="ending" HelpText="Optionally enter the date and time when this job should stop executing" ResourceKey="Ending">Ending: </Label>
<div class="col-sm-9">
<div class="row">
<div class="col">
<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" />
</div>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="next" HelpText="Optionally modify the date and time when this job should execute next" ResourceKey="NextExecution">Next Execution: </Label>
<div class="col-sm-9">
<div class="row">
<div class="col">
<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" />
</div>
</div>
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
</form>
@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 string _startDate = string.Empty;
private string _endDate = 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 string _nextExecution = string.Empty;
private DateTime? _nextDate = null;
private string _nextTime = string.Empty;
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -109,22 +130,40 @@
_isEnabled = job.IsEnabled.ToString();
_interval = job.Interval.ToString();
_frequency = job.Frequency;
_startDate = (job.StartDate != null) ? job.StartDate.ToString() : string.Empty;
_endDate = (job.EndDate != null) ? job.EndDate.ToString() : string.Empty;
_startDate = job.StartDate;
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{
_startTime = job.StartDate.Value.ToString("HH:mm");
}
_endDate = job.EndDate;
if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0)
{
_endTime = job.EndDate.Value.ToString("HH:mm");
}
_retentionHistory = job.RetentionHistory.ToString();
_nextExecution = job.NextExecution.ToString();
_nextDate = job.NextExecution;
if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0)
{
_nextTime = job.NextExecution.Value.ToString("HH:mm");
}
createdby = job.CreatedBy;
createdon = job.CreatedOn;
modifiedby = job.ModifiedBy;
modifiedon = job.ModifiedOn;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message);
AddModuleMessage(Localizer["Error Loading Job"], MessageType.Error);
AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error);
}
}
private async Task SaveJob()
{
if (_name != string.Empty && !string.IsNullOrEmpty(_jobType) && _frequency != string.Empty && _interval != string.Empty && _retentionHistory != string.Empty)
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
var job = await JobService.GetJobAsync(_jobId);
job.Name = _name;
@ -132,35 +171,34 @@
job.IsEnabled = Boolean.Parse(_isEnabled);
job.Frequency = _frequency;
job.Interval = int.Parse(_interval);
if (_startDate == string.Empty)
job.StartDate = _startDate;
if (job.StartDate != null)
{
job.StartDate = null;
job.StartDate = job.StartDate.Value.Date;
if (!string.IsNullOrEmpty(_startTime))
{
job.StartDate = DateTime.Parse(job.StartDate.Value.ToShortDateString() + " " + _startTime);
}
}
else
job.EndDate = _endDate;
if (job.EndDate != null)
{
job.StartDate = DateTime.Parse(_startDate);
job.EndDate = job.EndDate.Value.Date;
if (!string.IsNullOrEmpty(_endTime))
{
job.EndDate = DateTime.Parse(job.EndDate.Value.ToShortDateString() + " " + _endTime);
}
}
if (_endDate == string.Empty)
{
job.EndDate = null;
}
else
{
job.EndDate = DateTime.Parse(_endDate);
}
if (_nextExecution == string.Empty)
{
job.NextExecution = null;
}
else
{
job.NextExecution = DateTime.Parse(_nextExecution);
}
job.RetentionHistory = int.Parse(_retentionHistory);
job.NextExecution = _nextDate;
if (job.NextExecution != null)
{
job.NextExecution = job.NextExecution.Value.Date;
if (!string.IsNullOrEmpty(_nextTime))
{
job.NextExecution = DateTime.Parse(job.NextExecution.Value.ToShortDateString() + " " + _nextTime);
}
}
try
{
@ -171,13 +209,12 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
AddModuleMessage(Localizer["Error Updating Job"], MessageType.Error);
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["You Must Provide The Job Name, Type, Frequency, and Retention"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
}
}
}

View File

@ -2,10 +2,11 @@
@inherits ModuleBase
@inject IJobService JobService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_jobs == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
@ -19,10 +20,10 @@ else
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@Localizer["Status"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Status"]</th>
<th>@Localizer["Frequency"]</th>
<th>@Localizer["Next Execution"]</th>
<th>@Localizer["NextExecution"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
@ -118,7 +119,7 @@ else
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Job {Job} {Error}", job, ex.Message);
AddModuleMessage(Localizer["Error Deleting Job"], MessageType.Error);
AddModuleMessage(Localizer["Error.Job.Delete"], MessageType.Error);
}
}

View File

@ -2,46 +2,47 @@
@inherits ModuleBase
@inject IJobLogService JobLogService
@inject IStringLocalizer<Log> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_jobLogs == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<Pager Items="@_jobLogs">
<Header>
<th>@Localizer["Name"]</th>
<th>@Localizer["Status"]</th>
<th>@Localizer["Started"]</th>
<th>@Localizer["Finished"]</th>
</Header>
<Row>
<td>@context.Job.Name</td>
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
<td>@context.StartDate</td>
<td>@context.FinishDate</td>
</Row>
<Detail>
<td colspan="4">@((MarkupString)context.Notes)</td>
</Detail>
</Pager>
<Pager Items="@_jobLogs">
<Header>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Status"]</th>
<th>@Localizer["Started"]</th>
<th>@Localizer["Finished"]</th>
</Header>
<Row>
<td>@context.Job.Name</td>
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
<td>@context.StartDate</td>
<td>@context.FinishDate</td>
</Row>
<Detail>
<td colspan="4">@((MarkupString)context.Notes)</td>
</Detail>
</Pager>
}
@code {
private List<JobLog> _jobLogs;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnParametersSetAsync()
{
_jobLogs = await JobLogService.GetJobLogsAsync();
if (PageState.QueryString.ContainsKey("id"))
{
_jobLogs = _jobLogs.Where(item => item.JobId == Int32.Parse(PageState.QueryString["id"])).ToList();
}
_jobLogs = _jobLogs.OrderByDescending(item => item.JobLogId).ToList();
}
@ -63,7 +64,7 @@ else
status = Localizer["Failed"];
}
}
return status;
}
}

View File

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

View File

@ -1,11 +1,14 @@
@namespace Oqtane.Modules.Admin.Languages
@inherits ModuleBase
@inject ILanguageService LanguageService
@inject ILocalizationService LocalizationService
@inject IPackageService PackageService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_languages == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
@ -14,27 +17,46 @@ else
<Pager Items="@_languages">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th>
<th>@Localizer["Default?"]</th>
<th>@Localizer["Default"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionDialog Header="Delete Langauge" Message="@Localizer["Are You Sure You Wish To Delete The {0} Language?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@(context.IsDefault)" ResourceKey="DeleteLanguage" /></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>
<td>
@if (UpgradeAvailable(context.Code))
{
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
}
</td>
</Row>
</Pager>
}
@code {
private List<Language> _languages;
private List<Package> _packages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var cultures = await LocalizationService.GetCulturesAsync();
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
// Adds English as default language
_languages.Insert(0, new Language { Name = culture.DisplayName, Code = culture.Name, IsDefault = !_languages.Any(l => l.IsDefault) });
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
}
}
private async Task DeleteLanguage(Language language)
@ -50,7 +72,41 @@ else
{
await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error Deleting Language"], MessageType.Error);
AddModuleMessage(Localizer["Error.Language.Delete"], MessageType.Error);
}
}
private bool UpgradeAvailable(string code)
{
var upgradeavailable = false;
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + ".Client." + code)).FirstOrDefault();
if (package != null)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) > 0);
}
}
return upgradeavailable;
}
private async Task DownloadLanguage(string code)
{
try
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await PackageService.DownloadPackageAsync(Constants.PackageId + ".Client." + code, Constants.Version, Constants.PackagesFolder);
await logger.LogInformation("Translation Downloaded {Code} {Version}", code, Constants.Version);
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Translation {Code} {Version} {Error}", code, Constants.Version, ex.Message);
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
}
}
}

View File

@ -3,34 +3,44 @@
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IServiceProvider ServiceProvider
@inject SiteState SiteState
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_message != string.Empty)
{
<ModuleMessage Message="@_message" Type="@_type" />
}
<AuthorizeView>
<AuthorizeView Roles="@RoleNames.Registered">
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<div>@Localizer["Info.SignedIn"]</div>
</Authorized>
<NotAuthorized>
<div class="container Oqtane-Modules-Admin-Login">
<div class="form-group">
<label for="Username" class="control-label">@Localizer["Username:"] </label>
<input type="text" name="Username" class="form-control username" placeholder="Username" @bind="@_username" id="Username" />
</div>
<div class="form-group">
<label for="Password" class="control-label">@Localizer["Password:"] </label>
<input type="password" name="Password" class="form-control password" placeholder="Password" @bind="@_password" id="Password" />
</div>
<div class="form-group">
<div class="form-check form-check-inline">
<label class="form-check-label" for="Remember">@Localizer["Remember Me?"]</label>&nbsp;
<input type="checkbox" class="form-check-input" name="Remember" @bind="@_remember" id="Remember" />
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
<div class="form-group">
<label for="Username" class="control-label">@SharedLocalizer["Username"] </label>
<input type="text" @ref="username" name="Username" class="form-control username" placeholder="Username" @bind="@_username" id="Username" required />
</div>
<div class="form-group">
<label for="Password" class="control-label">@SharedLocalizer["Password"] </label>
<input type="password" name="Password" class="form-control password" placeholder="Password" @bind="@_password" id="Password" required />
</div>
<div class="form-group">
<div class="form-check form-check-inline">
<label class="form-check-label" for="Remember">@Localizer["RememberMe"]</label>&nbsp;
<input type="checkbox" class="form-check-input" name="Remember" @bind="@_remember" id="Remember" />
</div>
</div>
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<br /><br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
</div>
<button type="button" class="btn btn-primary" @onclick="Login">@Localizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button>
<br /><br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["Forgot Password"]</button>
</div>
</form>
</NotAuthorized>
</AuthorizeView>
@ -41,6 +51,10 @@
private string _username = string.Empty;
private string _password = string.Empty;
private bool _remember = false;
private bool validated = false;
private ElementReference login;
private ElementReference username;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
@ -70,62 +84,82 @@
if (user != null)
{
_message = Localizer["User Account Verified Successfully. You Can Now Login With Your Username And Password Below."];
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
_message = Localizer["Success.Account.Verified"];
}
else
{
_message = Localizer["User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions."];
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
_message = Localizer["Message.Account.NotVerfied"];
_type = MessageType.Warning;
}
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if(PageState.User == null)
{
await username.FocusAsync();
}
}
}
private async Task Login()
{
if (PageState.Runtime == Oqtane.Shared.Runtime.Server)
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
// server-side Blazor
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
user.Password = _password;
user = await UserService.LoginUserAsync(user, false, false);
if (user.IsAuthenticated)
if (PageState.Runtime == Oqtane.Shared.Runtime.Server)
{
await logger.LogInformation("Login Successful For Username {Username}", _username);
// complete the login on the server so that the cookies are set correctly on SignalR
var interop = new Interop(JSRuntime);
string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken");
var fields = new { __RequestVerificationToken = antiforgerytoken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
await interop.SubmitForm($"/{PageState.Alias.AliasId}/pages/login/", fields);
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
user.Password = _password;
user = await UserService.LoginUserAsync(user, false, false);
if (user.IsAuthenticated)
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
// server-side Blazor needs to post 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
{
await logger.LogError(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
}
}
else
{
await logger.LogInformation("Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Login Failed. Please Remember That Passwords Are Case Sensitive And User Accounts Require Email Verification When They Initially Created."], MessageType.Error);
// client-side Blazor
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
user.Password = _password;
user = await UserService.LoginUserAsync(user, true, _remember);
if (user.IsAuthenticated)
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true));
}
else
{
await logger.LogError(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
}
}
}
else
{
// client-side Blazor
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
user.Password = _password;
user = await UserService.LoginUserAsync(user, true, _remember);
if (user.IsAuthenticated)
{
await logger.LogInformation("Login Successful For Username {Username}", _username);
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, "reload"));
}
else
{
await logger.LogInformation("Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Login Failed. Please Remember That Passwords Are Case Sensitive And User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email."], MessageType.Error);
}
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
@ -142,6 +176,7 @@
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
_message = "Please Check The Email Address Associated To Your User Account For A Password Reset Notification";
}
else
@ -157,4 +192,12 @@
StateHasChanged();
}
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await Login();
}
}
}

View File

@ -6,208 +6,249 @@
@inject IPageService PageService
@inject IPageModuleService PageModuleService
@inject IUserService UserService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<Detail> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless">
<tr>
<td>
<Label For="dateTime" HelpText="The date and time of this log" ResourceKey="DateTime">Date/Time: </Label>
</td>
<td>
<input id="dateTime" class="form-control" @bind="@_logDate" readonly />
</td>
</tr>
<tr>
<td>
<Label For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label>
</td>
<td>
<input id="level" class="form-control" @bind="@_level" readonly />
</td>
</tr>
<tr>
<td>
<Label For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label>
</td>
<td>
<input id="feature" class="form-control" @bind="@_feature" readonly />
</td>
</tr>
<tr>
<td>
<Label For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label>
</td>
<td>
<input id="function" class="form-control" @bind="@_function" readonly />
</td>
</tr>
<tr>
<td>
<Label For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
</td>
<td>
<input id="category" class="form-control" @bind="@_category" readonly />
</td>
</tr>
@if (_pageName != string.Empty)
{
<tr>
<td>
<Label For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label>
</td>
<td>
<input id="page" class="form-control" @bind="@_pageName" readonly />
</td>
</tr>
}
@if (_moduleTitle != string.Empty)
{
<tr>
<td>
<Label For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label>
</td>
<td>
<input id="module" class="form-control" @bind="@_moduleTitle" readonly />
</td>
</tr>
}
@if (_username != string.Empty)
{
<tr>
<td>
<Label For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label>
</td>
<td>
<input id="user" class="form-control" @bind="@_username" readonly />
</td>
</tr>
}
<tr>
<td>
<Label For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label>
</td>
<td>
<input id="url" class="form-control" @bind="@_url" readonly />
</td>
</tr>
<tr>
<td>
<Label For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label>
</td>
<td>
<input id="template" class="form-control" @bind="@_template" readonly />
</td>
</tr>
<tr>
<td>
<Label For="message" HelpText="The message that the system generated" class="control-label" ResourceKey="Message">Message: </Label>
</td>
<td>
<textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea>
</td>
</tr>
@if (!string.IsNullOrEmpty(_exception))
{
<tr>
<td>
<Label For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label>
</td>
<td>
<textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea>
</td>
</tr>
}
<tr>
<td>
<Label For="properties" HelpText="The properties that were affected" ResourceKey="Properties">Properties: </Label>
</td>
<td>
<textarea id="properties" class="form-control" @bind="@_properties" rows="5" readonly></textarea>
</td>
</tr>
<tr>
<td>
<Label For="server" HelpText="The server that was affected" ResourceKey="Server">Server: </Label>
</td>
<td>
<input id="server" class="form-control" @bind="@_server" readonly />
</td>
</tr>
</table>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<div class="container">
<div class="row mb-1 align-items-center">
@code {
private int _logId;
private string _logDate = string.Empty;
private string _level = string.Empty;
private string _feature = string.Empty;
private string _function = string.Empty;
private string _category = string.Empty;
private string _pageName = string.Empty;
private string _moduleTitle = string.Empty;
private string _username = string.Empty;
private string _url = string.Empty;
private string _template = string.Empty;
private string _message = string.Empty;
private string _exception = string.Empty;
private string _properties = string.Empty;
private string _server = string.Empty;
<div class="col-sm-9">
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
</div>
</div>
<div class="row mb-1 align-items-center">
protected override async Task OnInitializedAsync()
{
try
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="dateTime" HelpText="The date and time of this log" ResourceKey="DateTime">Date/Time: </Label>
<div class="col-sm-9">
<input id="dateTime" class="form-control" @bind="@_logDate" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label>
<div class="col-sm-9">
<input id="level" class="form-control" @bind="@_level" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label>
<div class="col-sm-9">
<input id="feature" class="form-control" @bind="@_feature" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label>
<div class="col-sm-9">
<input id="function" class="form-control" @bind="@_function" readonly />
</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>
<div class="col-sm-9">
<input id="category" class="form-control" @bind="@_category" readonly />
</div>
</div>
@if (_pageName != string.Empty)
{
_logId = Int32.Parse(PageState.QueryString["id"]);
var log = await LogService.GetLogAsync(_logId);
if (log != null)
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label>
<div class="col-sm-9">
<input id="page" class="form-control" @bind="@_pageName" readonly />
</div>
</div>
}
@if (_moduleTitle != string.Empty)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_moduleTitle" readonly />
</div>
</div>
}
@if (_username != string.Empty)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label>
<div class="col-sm-9">
<input id="user" class="form-control" @bind="@_username" readonly />
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label>
<div class="col-sm-9">
<input id="template" class="form-control" @bind="@_template" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="message" HelpText="The message that the system generated" ResourceKey="Message">Message: </Label>
<div class="col-sm-9">
<textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea>
</div>
</div>
@if (!string.IsNullOrEmpty(_exception))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label>
<div class="col-sm-9">
<textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="properties" HelpText="The properties that were affected" ResourceKey="Properties">Properties: </Label>
<div class="col-sm-9">
<textarea id="properties" class="form-control" @bind="@_properties" rows="5" readonly></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="The server that was affected" ResourceKey="Server">Server: </Label>
<div class="col-sm-9">
<input id="server" class="form-control" @bind="@_server" readonly />
</div>
</div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private int _logId;
private string _logDate = string.Empty;
private string _level = string.Empty;
private string _feature = string.Empty;
private string _function = string.Empty;
private string _category = string.Empty;
private string _pageName = string.Empty;
private string _moduleTitle = string.Empty;
private string _username = string.Empty;
private string _url = string.Empty;
private string _template = string.Empty;
private string _message = string.Empty;
private string _exception = string.Empty;
private string _properties = string.Empty;
private string _server = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_logDate = log.LogDate.ToString(CultureInfo.CurrentCulture);
_level = log.Level;
_feature = log.Feature;
_function = log.Function;
_category = log.Category;
if (log.PageId != null)
_logId = Int32.Parse(PageState.QueryString["id"]);
var log = await LogService.GetLogAsync(_logId);
if (log != null)
{
var page = await PageService.GetPageAsync(log.PageId.Value);
if (page != null)
{
_pageName = page.Name;
}
}
_logDate = log.LogDate.ToString(CultureInfo.CurrentCulture);
_level = log.Level;
_feature = log.Feature;
_function = log.Function;
_category = log.Category;
if (log.PageId != null && log.ModuleId != null)
{
var pagemodule = await PageModuleService.GetPageModuleAsync(log.PageId.Value, log.ModuleId.Value);
if (pagemodule != null)
if (log.PageId != null)
{
_moduleTitle = pagemodule.Title;
var page = await PageService.GetPageAsync(log.PageId.Value);
if (page != null)
{
_pageName = page.Name;
}
}
}
if (log.UserId != null)
{
var user = await UserService.GetUserAsync(log.UserId.Value, PageState.Site.SiteId);
if (user != null)
if (log.PageId != null && log.ModuleId != null)
{
_username = user.Username;
var pagemodule = await PageModuleService.GetPageModuleAsync(log.PageId.Value, log.ModuleId.Value);
if (pagemodule != null)
{
_moduleTitle = pagemodule.Title;
}
}
}
_url = log.Url;
_template = log.MessageTemplate;
_message = log.Message;
_exception = log.Exception;
_properties = log.Properties;
_server = log.Server;
if (log.UserId != null)
{
var user = await UserService.GetUserAsync(log.UserId.Value, PageState.Site.SiteId);
if (user != null)
{
_username = user.Username;
}
}
_url = log.Url;
_template = log.MessageTemplate;
_message = log.Message;
_exception = log.Exception;
_properties = log.Properties;
_server = log.Server;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Log {LogId} {Error}", _logId, ex.Message);
AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Log {LogId} {Error}", _logId, ex.Message);
AddModuleMessage(Localizer["Error Loading Log"], MessageType.Error);
}
}
}

View File

@ -1,20 +1,21 @@
@namespace Oqtane.Modules.Admin.Logs
@inherits ModuleBase
@inject ILogService LogService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_logs == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<table class="table table-borderless">
<tr>
<td>
<label>@Localizer["Level:"] </label>
<select class="form-control" @onchange="(e => LevelChanged(e))">
<option value="-">&lt;@Localizer["All Levels"]&gt;</option>
<div class="container g-0">
<div class="row mb-1 align-items-center">
<div class="col-sm-4">
<Label For="level" HelpText="Select the log level for event log items" ResourceKey="Level">Level: </Label><br /><br />
<select id="level" class="form-select" @onchange="(e => LevelChanged(e))">
<option value="-">&lt;@Localizer["AllLevels"]&gt;</option>
<option value="Trace">@Localizer["Trace"]</option>
<option value="Debug">@Localizer["Debug"]</option>
<option value="Information">@Localizer["Information"]</option>
@ -22,39 +23,39 @@ else
<option value="Error">@Localizer["Error"]</option>
<option value="Critical">@Localizer["Critical"]</option>
</select>
</td>
<td>
<label>@Localizer["Function:"] </label>
<select class="form-control" @onchange="(e => FunctionChanged(e))">
<option value="-">&lt;@Localizer["All Functions"]&gt;</option>
</div>
<div class="col-sm-4">
<Label For="function" HelpText="Select the function for event log items" ResourceKey="Function">Function: </Label><br /><br />
<select id="function" class="form-select" @onchange="(e => FunctionChanged(e))">
<option value="-">&lt;@Localizer["AllFunctions"]&gt;</option>
<option value="Create">@Localizer["Create"]</option>
<option value="Read">@Localizer["Read"]</option>
<option value="Update">@Localizer["Update"]</option>
<option value="Delete">@Localizer["Delete"]</option>
<option value="Update">@SharedLocalizer["Update"]</option>
<option value="Delete">@SharedLocalizer["Delete"]</option>
<option value="Security">@Localizer["Security"]</option>
<option value="Other">@Localizer["Other"]</option>
</select>
</td>
<td>
<label>@Localizer["Rows:"] </label>
<select class="form-control" @onchange="(e => RowsChanged(e))">
</div>
<div class="col-sm-4">
<Label For="rows" HelpText="Select the maximum number of event log items to review. Please note that if you choose more than 10 items the information will be split into pages." ResourceKey="Rows">Maximum Items: </Label><br /><br />
<select id="rows" class="form-select" @onchange="(e => RowsChanged(e))">
<option value="10">10</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</td>
</tr>
</table>
</div>
</div>
</div>
@if (_logs.Any())
{
<Pager Items="@_logs">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Date"]</th>
<th>@Localizer["Level"]</th>
<th>@Localizer["Feature"]</th>
<th>@Localizer["Function"]</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Date"]</th>
<th>@Localizer["Level"]</th>
<th>@Localizer["Feature"]</th>
<th>@Localizer["Function"]</th>
</Header>
<Row>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"id=" + context.LogId.ToString())" ResourceKey="LogDetails" /></td>
@ -67,7 +68,7 @@ else
}
else
{
<p><em>@Localizer["No Logs Match The Criteria Specified"]</em></p>
<p><em>@Localizer["NoLogs"]</em></p>
}
}
@ -88,7 +89,7 @@ else
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Logs {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Logs"], MessageType.Error);
AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error);
}
}
@ -103,7 +104,7 @@ else
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Logs {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Logs"], MessageType.Error);
AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error);
}
}
@ -118,7 +119,7 @@ else
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Logs {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Logs"], MessageType.Error);
AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error);
}
}
@ -134,7 +135,7 @@ else
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Logs {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Logs"], MessageType.Error);
AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error);
}
}

View File

@ -1,96 +1,92 @@
@namespace Oqtane.Modules.Admin.ModuleCreator
@inherits ModuleBase
@using System.Text.RegularExpressions
@inject NavigationManager NavigationManager
@inject IModuleDefinitionService ModuleDefinitionService
@inject IModuleService ModuleService
@inject ISystemService SystemService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@using System.Text.RegularExpressions
@using System.IO;
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (string.IsNullOrEmpty(_moduledefinitionname))
@if (string.IsNullOrEmpty(_moduledefinitionname) && _templates != null)
{
<table class="table table-borderless">
<tr>
<td>
<Label 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>
</td>
<td>
<input id="owner" class="form-control" @bind="@_owner" />
</td>
</tr>
<tr>
<td>
<Label For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation." ResourceKey="ModuleName">Module Name: </Label>
</td>
<td>
<input id="module" class="form-control" @bind="@_module" />
</td>
</tr>
<tr>
<td>
<Label For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
</td>
<td>
<textarea id="description" class="form-control" @bind="@_description" rows="3"></textarea>
</td>
</tr>
<tr>
<td>
<Label For="template" HelpText="Select a module template. Internal modules are created inside of the framework solution. External modules are created outside of the framework solution." ResourceKey="Template">Template: </Label>
</td>
<td>
<select id="template" class="form-control" @onchange="(e => TemplateChanged(e))">
<option value="-">&lt;@Localizer["Select Template"]&gt;</option>
<option value="internal">@Localizer["Internal"]</option>
<option value="external">@Localizer["External"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
</td>
<td>
<select id="reference" class="form-control" @bind="@_reference">
@foreach (string version in Constants.ReleaseVersions.Split(','))
{
if (Version.Parse(version).CompareTo(Version.Parse("2.0.0")) >= 0)
{
<option value="@(version)">@(version)</option>
}
}
<option value="local">@Localizer["Local Version"]</option>
</select>
</td>
</tr>
@if (!string.IsNullOrEmpty(_location))
{
<tr>
<td>
<Label For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
</td>
<td>
<input id="module" class="form-control" @bind="@_location" readonly />
</td>
</tr>
}
</table>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["Create Module"]</button>
<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["Activate Module"]</button>
<button type="button" class="btn btn-success" @onclick="ActivateModule">@Localizer["Module.Activate"]</button>
}
@code {
private string _moduledefinitionname = "";
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 = "-";
public string _reference = Constants.Version;
private string[] _versions;
private string _reference = Constants.Version;
private string _minversion = "2.0.0";
private string _location = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -100,21 +96,34 @@ else
_moduledefinitionname = SettingService.GetSetting(ModuleState.Settings, "ModuleDefinitionName", "");
if (string.IsNullOrEmpty(_moduledefinitionname))
{
_owner = ModuleState.Title;
_module = ModuleState.Title;
_description = ModuleState.Title;
AddModuleMessage(Localizer["Info.Module.Creator"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Once You Have Compiled The Module And Restarted The Application You Can Activate The Module Below"], MessageType.Info);
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()
{
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
try
{
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
@ -123,16 +132,17 @@ else
SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
AddModuleMessage(Localizer["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 <a href=\"{0}\">Restart</a> Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
catch (Exception ex)
{
AddModuleMessage(Localizer["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"], MessageType.Warning);
await logger.LogError(ex, "Error Creating Module");
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Creating Module");
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
@ -145,8 +155,7 @@ else
Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
module.ModuleDefinitionName = _moduledefinitionname;
await ModuleService.UpdateModuleAsync(module);
ClearModuleMessage();
NavigationManager.NavigateTo(NavigateUrl());
NavigationManager.NavigateTo(NavigateUrl(), true);
}
}
catch (Exception ex)
@ -158,41 +167,30 @@ else
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private async void TemplateChanged(ChangeEventArgs e)
private void TemplateChanged(ChangeEventArgs e)
{
try
_template = (string)e.Value;
_minversion = "2.0.0";
if (_template != "-")
{
_location = string.Empty;
_template = (string)e.Value;
if (_template != "-")
{
Dictionary<string, string> systeminfo = await SystemService.GetSystemInfoAsync();
if (systeminfo != null)
{
string[] path = systeminfo["serverpath"].Split(Path.DirectorySeparatorChar);
if (_template == "internal")
{
_location = string.Join(Path.DirectorySeparatorChar, path, 0, path.Length - 1) +
Path.DirectorySeparatorChar + "Oqtane.Client" +
Path.DirectorySeparatorChar + "Modules" +
Path.DirectorySeparatorChar + _owner + "." + _module;
}
else
{
_location = string.Join(Path.DirectorySeparatorChar, path, 0, path.Length - 2) +
Path.DirectorySeparatorChar + _owner + "." + _module;
}
}
}
StateHasChanged();
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
}
catch (Exception ex)
GetLocation();
}
private void GetLocation()
{
_location = string.Empty;
if (_owner != "" && _module != "" && _template != "-")
{
await logger.LogError(ex, "Error Getting System Info {Error}", ex.Message);
AddModuleMessage(Localizer["Error Getting System Info"], MessageType.Error);
var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _module;
}
StateHasChanged();
}
}

View File

@ -5,50 +5,122 @@
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_packages != null)
{
<TabStrip>
@if (_packages.Count > 0)
<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>
</div>
</div>
@if (_packages != null)
{
<TabPanel Name="Download" ResourceKey="Download">
<ModuleMessage Type="MessageType.Info" Message="Download one or more modules from the list below. Once you are ready click Install to complete the installation."></ModuleMessage>
if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Header>
<th>@Localizer["Name"]</th>
<th>@Localizer["Version"]</th>
<th style="width: 1px"></th>
</Header>
<Row>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadModule(context.PackageId, context.Version))>@Localizer["Download"]</button>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
</TabPanel>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
}
<TabPanel Name="Upload" ResourceKey="Upload">
<table class="table table-borderless">
<tr>
<td>
<Label HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
</td>
<td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Modules" UploadMultiple="true" />
</td>
</tr>
</table>
</TabPanel>
</TabStrip>
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
</div>
</div>
</div>
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="InstallModules">@Localizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
@if (_productname != "")
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
{
@((MarkupString)_license)
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -56,21 +128,118 @@
{
try
{
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module");
await LoadModuleDefinitions();
}
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, "");
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (moduledefinitions.Exists(item => Utilities.GetTypeName(item.ModuleDefinitionName) == package.PackageId))
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
}
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadModuleDefinitions();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Packages"], MessageType.Error);
await logger.LogError(ex, "Error On PriceChanged");
}
}
private async Task Search()
{
try
{
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
private async Task Reset()
{
try
{
_search = "";
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
}
private void HideModal()
{
_productname = "";
_license = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Module.Download"], MessageType.Success);
_productname = "";
_license = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
@ -79,27 +248,11 @@
try
{
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(Localizer["Module Installed Successfully. You Must <a href=\"{0}\">Restart</a> Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Module");
}
}
private async Task DownloadModule(string packageid, string version)
{
try
{
await PackageService.DownloadPackageAsync(packageid, version, "Modules");
await logger.LogInformation("Module {ModuleDefinitionName} {Version} Downloaded Successfully", packageid, version);
AddModuleMessage(Localizer["Modules Downloaded Successfully. Click Install To Complete Installation."], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version}", packageid, version);
AddModuleMessage(Localizer["Error Downloading Module"], MessageType.Error);
}
}
}

View File

@ -0,0 +1,168 @@
@namespace Oqtane.Modules.Admin.ModuleDefinitions
@inherits ModuleBase
@using System.Text.RegularExpressions
@inject NavigationManager NavigationManager
@inject IModuleDefinitionService ModuleDefinitionService
@inject IModuleService ModuleService
@inject ISettingService SettingService
@inject IStringLocalizer<Create> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_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" maxlength="2000" required></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>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
}
@code {
private ElementReference form;
private bool validated = false;
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 = Constants.Version;
private string _minversion = "2.0.0";
private string _location = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override void OnInitialized()
{
AddModuleMessage(Localizer["Info.Module.Development"], 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);
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 bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
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

@ -3,113 +3,96 @@
@inject IModuleDefinitionService ModuleDefinitionService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Definition" ResourceKey="Definition">
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="description" HelpText="The description of the module" ResourceKey="Description">Description: </Label>
</td>
<td>
<textarea id="description" class="form-control" @bind="@_description" rows="2"></textarea>
</td>
</tr>
<tr>
<td>
<Label For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
</td>
<td>
<input id="categories" class="form-control" @bind="@_categories" />
</td>
</tr>
</table>
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="The description of the module" ResourceKey="Description">Description: </Label>
<div class="col-sm-9">
<textarea id="description" class="form-control" @bind="@_description" rows="2" maxlength="2000" required></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
<div class="col-sm-9">
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information">
<table class="table table-borderless">
<tr>
<td>
<Label For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
</td>
<td>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="moduledefinitionname" class="form-control" @bind="@_moduledefinitionname" disabled />
</td>
</tr>
<tr>
<td>
<Label For="version" HelpText="The version of the module" ResourceKey="Version">Version: </Label>
</td>
<td>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the module" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</td>
</tr>
<tr>
<td>
<Label For="owner" HelpText="The owner or creator of the module" ResourceKey="Owner">Owner: </Label>
</td>
<td>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the module" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</td>
</tr>
<tr>
<td>
<Label For="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label>
</td>
<td>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</td>
</tr>
<tr>
<td>
<Label For="contact" HelpText="The contact for the module" ResourceKey="Contact">Contact: </Label>
</td>
<td>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the module" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</td>
</tr>
<tr>
<td>
<Label For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
</td>
<td>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</td>
</tr>
<tr>
<td>
<Label For="runtimes" HelpText="The Blazor runtimes which this module supports" ResourceKey="Runtimes">Runtimes: </Label>
</td>
<td>
</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 />
</td>
</tr>
</table>
</div>
</div>
</div>
</Section>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<table class="table table-borderless">
<tr>
<td>
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" Permissions="@_permissions" @ref="_permissionGrid" />
</td>
</tr>
</table>
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
@code {
private ElementReference form;
private bool validated = false;
private int _moduleDefinitionId;
private string _name;
private string _version;
@ -161,36 +144,45 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error Loading Module"], MessageType.Error);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
private async Task SaveModuleDefinition()
{
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduledefinition.Name != _name)
try
{
moduledefinition.Name = _name;
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduledefinition.Name != _name)
{
moduledefinition.Name = _name;
}
if (moduledefinition.Description != _description)
{
moduledefinition.Description = _description;
}
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.Permissions = _permissionGrid.GetPermissions();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
}
if (moduledefinition.Description != _description)
catch (Exception ex)
{
moduledefinition.Description = _description;
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.Permissions = _permissionGrid.GetPermissions();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error Saving Module"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@ -4,37 +4,44 @@
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_moduleDefinitions == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
<Pager Items="@_moduleDefinitions">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@Localizer["Version"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
<td>
@if (context.AssemblyName != "Oqtane.Client")
{
<ActionDialog Header="Delete Module" Message="@("Are You Sure You Wish To Delete The " + context.Name + " Module?")" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" />
}
{
<ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete", context.Name])" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" />
}
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if (UpgradeAvailable(context.ModuleDefinitionName, context.Version))
@((MarkupString)PurchaseLink(context.PackageName))
</td>
<td>
@if (UpgradeAvailable(context.PackageName, context.Version))
{
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.ModuleDefinitionName, context.Version))>@Localizer["Upgrade"]</button>
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, context.Version))>@SharedLocalizer["Upgrade"]</button>
}
</td>
</Row>
@ -59,17 +66,38 @@ else
if (_moduleDefinitions == null)
{
await logger.LogError(ex, "Error Loading Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Modules"], MessageType.Error);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
}
private bool UpgradeAvailable(string moduledefinitionname, string version)
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>";
}
}
}
}
return link;
}
private bool UpgradeAvailable(string packagename, string version)
{
var upgradeavailable = false;
if (_packages != null)
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == Utilities.GetTypeName(moduledefinitionname)).FirstOrDefault();
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
@ -79,19 +107,19 @@ else
return upgradeavailable;
}
private async Task DownloadModule(string moduledefinitionname, string version)
private async Task DownloadModule(string packagename, string version)
{
try
{
await PackageService.DownloadPackageAsync(moduledefinitionname, version, "Modules");
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", moduledefinitionname, version);
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(Localizer["Module Installed Successfully. You Must <a href=\"{0}\">Restart</a> Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", moduledefinitionname, version, ex.Message);
AddModuleMessage(Localizer["Error Downloading Module"], MessageType.Error);
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
@ -100,13 +128,13 @@ else
try
{
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
AddModuleMessage(Localizer["Module Deleted Successfully"], MessageType.Success);
StateHasChanged();
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message);
AddModuleMessage(Localizer["Error Deleting Module"], MessageType.Error);
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
}
}
}

View File

@ -3,32 +3,37 @@
@inject NavigationManager NavigationManager
@inject IModuleService ModuleService
@inject IStringLocalizer<Export> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="content" HelpText="The Exported Module Content" ResourceKey="Content">Content: </Label>
<div class="col-sm-9">
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
</div>
</div>
</div>
<table class="table table-borderless">
<tbody>
<tr>
<td>
<Label For="content" HelpText="Enter the module content" ResourceKey="Content">Content: </Label>
</td>
<td>
<textarea id="content" class="form-control" @bind="@_content" rows="5"></textarea>
</td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-success" @onclick="ExportModule">@Localizer["Export"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _content = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override string Title => "Export Content";
private async Task ExportModule()
{
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId);
try
{
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId);
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Exporting Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
}
}
}

View File

@ -3,54 +3,65 @@
@inject NavigationManager NavigationManager
@inject IModuleService ModuleService
@inject IStringLocalizer<Import> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless">
<tbody>
<tr>
<td>
<Label For="content" HelpText="Enter the module content" ResourceKey="Content">Content: </Label>
</td>
<td>
<textarea id="content" class="form-control" @bind="@_content" rows="5"></textarea>
</td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<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="content" HelpText="Enter The Module Content To Import" ResourceKey="Content">Content: </Label>
<div class="col-sm-9">
<textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
@code {
private string _content = string.Empty;
private ElementReference form;
private bool validated = false;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override string Title => "Import Content";
private async Task ImportModule()
{
if (_content != string.Empty)
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
if (_content != string.Empty)
{
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content);
if (success)
try
{
AddModuleMessage(Localizer["Content Imported Successfully"], MessageType.Success);
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content);
if (success)
{
AddModuleMessage(Localizer["Success.Content.Import"], MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Content.ImportProblem"], MessageType.Warning);
}
}
else
catch (Exception ex)
{
AddModuleMessage(Localizer["A Problem Was Encountered Importing Content. Please Ensure The Content Is Formatted Correctly For The Module."], MessageType.Warning);
await logger.LogError(ex, "Error Importing Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Import"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Importing Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
AddModuleMessage(Localizer["Error Importing Module"], MessageType.Error);
AddModuleMessage(Localizer["Message.Required.ImportContent"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["You Must Enter Some Content To Import"], MessageType.Warning);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@ -6,87 +6,96 @@
@inject IModuleService ModuleService
@inject IPageModuleService PageModuleService
@inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Settings" Heading="Module Settings" ResourceKey="ModuleSettings">
@if (_containers != null)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
</td>
<td>
<input id="title" type="text" name="Title" class="form-control" @bind="@_title" />
</td>
</tr>
<tr>
<td>
<Label For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
</td>
<td>
<select id="container" class="form-control" @bind="@_containerType">
<option value="-">&lt;@Localizer["Inherit From Page Or Site"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
</td>
<td>
<select id="allpages" class="form-control" @bind="@_allPages">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="page" HelpText="The page that the module is on" ResourceKey="Page">Page: </Label>
</td>
<td>
<select id="page" class="form-control" @bind="@_pageId">
@foreach (Page p in PageState.Pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
@if (_containers != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="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 />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containerType" required>
@foreach (var container in _containers)
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
<option value="@container.TypeName">@container.Name</option>
}
}
</select>
</td>
</tr>
</table>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<table class="table table-borderless">
<tr>
<td>
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" Permissions="@_permissions" @ref="_permissionGrid" />
</td>
</tr>
</table>
}
</TabPanel>
@if (_settingsModuleType != null)
{
<TabPanel Name="ModuleSettings" Heading="@_settingstitle" ResourceKey="Module Settings">
@DynamicComponent
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
<div class="col-sm-9">
<select id="allpages" class="form-select" @bind="@_allPages" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
<div class="col-sm-9">
<select id="page" class="form-select" @bind="@_pageId" required>
@foreach (Page p in PageState.Pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
}
}
</select>
</div>
</div>
</div>
}
</TabPanel>
}
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SaveModule">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<TabPanel Name="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
}
</TabPanel>
@if (_moduleSettingsType != null)
{
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
@ModuleSettingsComponent
</TabPanel>
}
@if (_containerSettingsType != null)
{
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
@ContainerSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
</form>
@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;
@ -95,85 +104,133 @@
private string _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _settingsModuleType;
private string _settingstitle = "Other Settings";
private object _settings;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
private RenderFragment DynamicComponent { get; set; }
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;
protected override async Task OnInitializedAsync()
{
_title = ModuleState.Title;
_containers = ThemeService.GetContainerControls(await ThemeService.GetThemesAsync(), PageState.Page.ThemeType);
_themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
if (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType) && _containerType == PageState.Page.DefaultContainerType)
{
_containerType = "-";
}
if (_containerType == PageState.Site.DefaultContainerType)
{
_containerType = "-";
}
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.Permissions;
_permissionNames = ModuleState.ModuleDefinition.PermissionNames;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
_settingsModuleType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
if (_settingsModuleType != null)
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{
var moduleobject = Activator.CreateInstance(_settingsModuleType) as IModuleControl;
_settingstitle = moduleobject.Title;
if (string.IsNullOrEmpty(_settingstitle))
// 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))
{
_settingstitle = "Other Settings";
_moduleSettingsTitle = moduleobject.Title;
}
DynamicComponent = builder =>
ModuleSettingsComponent = builder =>
{
builder.OpenComponent(0, _settingsModuleType);
builder.AddComponentReferenceCapture(1, inst => { _settings = Convert.ChangeType(inst, _settingsModuleType); });
builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
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()
{
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)
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
pagemodule.ContainerType = string.Empty;
}
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
if (!string.IsNullOrEmpty(_title))
{
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
if (_settings is ISettingsControl control)
{
await control.UpdateSettings();
if (_moduleSettingsType != null)
{
if (_moduleSettings is ISettingsControl moduleSettingsControl)
{
// module settings updated using explicit interface
await moduleSettingsControl.UpdateSettings();
}
else
{
// legacy support - module settings updated by convention ( ie. by calling a public method named "UpdateSettings" in settings component )
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
}
}
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
{
await containerSettingsControl.UpdateSettings();
}
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
}
}
else
{
// Compatibility 2.0 fallback
_settings?.GetType().GetMethod("UpdateSettings")?.Invoke(_settings, null); // method must be public in settings component
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
NavigationManager.NavigateTo(NavigateUrl());
}
}

View File

@ -4,233 +4,205 @@
@inject IPageService PageService
@inject IThemeService ThemeService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="Name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="Name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="Parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
</td>
<td>
<select id="Parent" class="form-control" @onchange="(e => ParentChanged(e))">
<option value="-1">&lt;@Localizer["Site Root"]&gt;</option>
@foreach (Page page in _pageList)
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="Insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
</td>
<td>
<select id="Insert" class="form-control" @bind="@_insert">
<option value="<<">@Localizer["At Beginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["At End"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-control" @bind="@_childid">
<option value="-1">&lt;@Localizer["Select Page"]&gt;</option>
@foreach (Page page in _children)
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null)
{
<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>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pageList)
{
<option value="@(page.PageId)">@(page.Name)</option>
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
</select>
}
</td>
</tr>
<tr>
<td>
<Label For="Navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
</td>
<td>
<select id="Navigation" class="form-control" @bind="@_isnavigation">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="Path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used." ResourceKey="UrlPath">Url Path: </Label>
</td>
<td>
<input id="Path" class="form-control" @bind="@_path" />
</td>
</tr>
<tr>
<td>
<Label For="Url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
</td>
<td>
<input id="Url" class="form-control" @bind="@_url" />
</td>
</tr>
</table>
<Section Name="Appearance" ResourceKey="Appearance">
<table class="table table-borderless">
<tr>
<td>
<Label 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>
</td>
<td>
<input id="Title" class="form-control" @bind="@_title" />
</td>
</tr>
<tr>
<td>
<Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
</td>
<td>
<select id="Theme" class="form-control" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Inherit From Site"]&gt;</option>
@foreach (var theme in _themes)
</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)
{
if (theme.TypeName == _themetype)
<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="@theme.TypeName" selected>@theme.Name</option>
<option value="@(page.PageId)">@(page.Name)</option>
}
else
</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">
<select id="navigation" class="form-select" @bind="@_isnavigation" 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="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
<div class="col-sm-9">
<select id="clickable" class="form-select" @bind="@_isclickable" 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="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
<div class="col-sm-9">
<input id="path" class="form-control" @bind="@_path" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" />
</div>
</div>
</div>
<Section Name="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" />
</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">
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
}
</select>
</td>
</tr>
@if (_layouts.Count > 0)
{
<tr>
<td>
<Label For="Layout" HelpText="Select a layout for the page (if the selected theme supports it)" ResourceKey="Layout">Layout: </Label>
</td>
<td>
<select id="Layout" class="form-control" @bind="@_layouttype">
<option value="-">&lt;@Localizer["Inherit From Site"]&gt;</option>
@foreach (var layout in _layouts)
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<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)
{
if (layout.TypeName == _layouttype)
{
<option value="@(layout.TypeName)" selected>@(layout.Name)</option>
}
else
{
<option value="@(layout.TypeName)">@(layout.Name)</option>
}
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
}
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;@Localizer["Inherit From Site"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="Icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
</td>
<td>
<input id="Icon" class="form-control" @bind="@_icon" />
</td>
</tr>
<tr>
<td>
<Label For="Personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
</td>
<td>
<select id="Personalizable" class="form-control" @bind="@_ispersonalizable">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
</table>
</Section>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<table class="table table-borderless">
<tr>
<td>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</Section>
}
</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" />
</td>
</tr>
</table>
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@Localizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button>
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _layouts = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pageList;
private string _name;
private string _title;
private string _path = string.Empty;
private string _parentid;
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 = "-";
private string _layouttype = "-";
private string _containertype = "-";
private string _themetype = string.Empty;
private string _containertype = string.Empty;
private string _icon = string.Empty;
private string _permissions = string.Empty;
private PermissionGrid _permissionGrid;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
private ElementReference form;
private bool validated = false;
protected override async Task OnInitializedAsync()
{
try
{
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = PageState.Site.DefaultThemeType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_pageList = PageState.Pages;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_themes = ThemeService.GetThemeControls(_themeList);
_permissions = string.Empty;
ThemeSettings();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error Initializing Page"], MessageType.Error);
AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error);
}
}
@ -265,7 +237,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error Loading Child Pages For Parent"], MessageType.Error);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
}
}
@ -274,137 +246,159 @@
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_layouts = ThemeService.GetLayoutControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_layouts = new List<ThemeControl>();
_containers = new List<ThemeControl>();
}
_layouttype = "-";
_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 Loading Pane Layouts For Theme"], MessageType.Error);
AddModuleMessage(Localizer["Error.Pane.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 async Task SavePage()
{
Page page = null;
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (_name != string.Empty && !string.IsNullOrEmpty(_themetype) && (_layouts.Count == 0 || _layouttype != "-"))
Page page = null;
try
{
page = new Page();
page.SiteId = PageState.Page.SiteId;
page.Name = _name;
page.Title = _title;
if (_path == "")
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
{
_path = _name;
}
page = new Page();
page.SiteId = PageState.Page.SiteId;
page.Name = _name;
page.Title = _title;
if (_path.Contains("/"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (string.IsNullOrEmpty(_parentid))
{
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)
if (string.IsNullOrEmpty(_path))
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
_path = _name;
}
if (_path.Contains("/"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1")
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
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);
}
}
}
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList))
{
AddModuleMessage(Localizer["A page with path {0} already exists for the selected parent page. The page path needs to be unique for the selected parent.", _path], MessageType.Warning);
return;
}
if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), 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;
}
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
return;
}
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.LayoutType = (_layouttype != "-") ? _layouttype : string.Empty;
if (!string.IsNullOrEmpty(page.LayoutType) && page.LayoutType == PageState.Site.DefaultLayoutType)
{
page.LayoutType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = (_icon == null ? string.Empty : _icon);
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
Page 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 = await PageService.AddPageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
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 (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = (_icon == null ? string.Empty : _icon);
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
await logger.LogInformation("Page Added {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
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"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
}
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["You Must Provide Page Name And Theme/Layout"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error Saving Page"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
@ -424,4 +418,9 @@
{
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath);
}
private static bool PagePathIsDeleted(string pagePath, int siteId, List<Page> existingPages)
{
return existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.IsDeleted == true);
}
}

View File

@ -1,216 +1,181 @@
@namespace Oqtane.Modules.Admin.Pages
@using Oqtane.Interfaces
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IPageService PageService
@inject IThemeService ThemeService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="Name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="Name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="Parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
</td>
<td>
<select id="Parent" class="form-control" @onchange="(e => ParentChanged(e))">
<option value="-1">&lt;@Localizer["Site Root"]&gt;</option>
@foreach (Page page in _pageList)
{
if (page.PageId.ToString() == _parentid)
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null)
{
<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>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="50" 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" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pageList)
{
<option value="@(page.PageId)" selected>@(new string('-', page.Level * 2))@(page.Name)</option>
}
else
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="Move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
</td>
<td>
<select id="Move" class="form-control" @bind="@_insert">
@if (_parentid == _currentparentid)
{
<option value="=">&lt;@Localizer["Maintain Current Location"]&gt;</option>
}
<option value="<<">@Localizer["To Beginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["To End"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-control" @bind="@_childid">
<option value="-1">&lt;@Localizer["Select Page"]&gt;</option>
@foreach (Page page in _children)
{
<option value="@(page.PageId)">@(page.Name)</option>
if (page.PageId != _pageId)
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
}
</select>
}
</td>
</tr>
<tr>
<td>
<Label For="Navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
</td>
<td>
<select id="Navigation" class="form-control" @bind="@_isnavigation">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="Path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used." ResourceKey="UrlPath">Url Path: </Label>
</td>
<td>
<input id="Path" class="form-control" @bind="@_path" />
</td>
</tr>
<tr>
<td>
<Label For="Url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
</td>
<td>
<input id="Url" class="form-control" @bind="@_url" />
</td>
</tr>
</table>
<Section Name="Appearance" ResourceKey="Appearance">
<table class="table table-borderless">
<tr>
<td>
<Label 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>
</td>
<td>
<input id="Title" class="form-control" @bind="@_title" />
</td>
</tr>
<tr>
<td>
<Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
</td>
<td>
<select id="Theme" class="form-control" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;Inherit From Site&gt;</option>
@foreach (var theme in _themes)
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
<div class="col-sm-9">
<select id="move" class="form-select" @bind="@_insert" required>
@if (_parentid == _currentparentid)
{
if (theme.TypeName == _themetype)
<option value="=">&lt;@Localizer["ThisLocation.Keep"]&gt;</option>
}
<option value="<<">@Localizer["ToBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["ToEnd"]</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="@theme.TypeName" selected>@theme.Name</option>
<option value="@(page.PageId)">@(page.Name)</option>
}
else
</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">
<select id="navigation" class="form-select" @bind="@_isnavigation" 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="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
<div class="col-sm-9">
<select id="clickable" class="form-select" @bind="@_isclickable" 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="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
<div class="col-sm-9">
<input id="path" class="form-control" @bind="@_path" maxlength="256"/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" maxlength="500"/>
</div>
</div>
</div>
<Section Name="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="200"/>
</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">
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
}
</select>
</td>
</tr>
@if (_layouts.Count > 0)
{
<tr>
<td>
<Label For="Layout" HelpText="Select a layout for the page (if the selected theme supports it)" ResourceKey="Layout">Layout: </Label>
</td>
<td>
<select id="Layout" class="form-control" @bind="@_layouttype">
<option value="-">&lt;@Localizer["Inherit From Site"]&gt;</option>
@foreach (var layout in _layouts)
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<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)
{
if (layout.TypeName == _layouttype)
{
<option value="@(layout.TypeName)" selected>@(layout.Name)</option>
}
else
{
<option value="@(layout.TypeName)">@(layout.Name)</option>
}
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
}
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;@Localizer["Inherit From Site"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="Icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
</td>
<td>
<input id="Icon" class="form-control" @bind="@_icon" />
</td>
</tr>
<tr>
<td>
<Label For="Personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
</td>
<td>
<select id="Personalizable" class="form-control" @bind="@_ispersonalizable">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
</table>
</Section>
<br /><br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<table class="table table-borderless">
<tr>
<td>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" maxlength="50"/>
</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>
<br /><br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</td>
</tr>
</table>
</div>
</div>
}
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
<br />
}
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@Localizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private ElementReference form;
private bool validated = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _layouts = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pageList;
private int _pageId;
@ -218,15 +183,15 @@
private string _title;
private string _path;
private string _currentparentid;
private string _parentid;
private string _parentid = "-1";
private string _insert = "=";
private List<Page> _children;
private int _childid = -1;
private string _isnavigation;
private string _isclickable;
private string _url;
private string _ispersonalizable;
private string _themetype = "-";
private string _layouttype = "-";
private string _themetype;
private string _containertype = "-";
private string _icon;
private string _permissions = null;
@ -236,12 +201,11 @@
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
#pragma warning disable 649
private PermissionGrid _permissionGrid;
#pragma warning restore 649
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
protected override async Task OnInitializedAsync()
{
@ -261,14 +225,21 @@
_title = page.Title;
_path = page.Path;
if (_path.Contains("/"))
if (string.IsNullOrEmpty(_path))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
_path = "/";
}
else
{
if (_path.Contains("/"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
}
if (page.ParentId == null)
{
_parentid = string.Empty;
_parentid = "-1";
}
else
{
@ -277,24 +248,19 @@
_currentparentid = _parentid;
_isnavigation = page.IsNavigation.ToString();
_isclickable = page.IsClickable.ToString();
_url = page.Url;
_ispersonalizable = page.IsPersonalizable.ToString();
_themetype = page.ThemeType;
if (_themetype == PageState.Site.DefaultThemeType)
if (string.IsNullOrEmpty(_themetype))
{
_themetype = "-";
_themetype = PageState.Site.DefaultThemeType;
}
_layouts = ThemeService.GetLayoutControls(_themeList, page.ThemeType);
_layouttype = page.LayoutType;
if (_layouttype == PageState.Site.DefaultLayoutType)
{
_layouttype = "-";
}
_containers = ThemeService.GetContainerControls(_themeList, page.ThemeType);
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = page.DefaultContainerType;
if (string.IsNullOrEmpty(_containertype))
{
_containertype = "-";
_containertype = PageState.Site.DefaultContainerType;
}
_icon = page.Icon;
_permissions = page.Permissions;
@ -304,12 +270,14 @@
_modifiedon = page.ModifiedOn;
_deletedby = page.DeletedBy;
_deletedon = page.DeletedOn;
ThemeSettings();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Page {PageId} {Error}", _pageId, ex.Message);
AddModuleMessage(Localizer["Error Loading Page"], MessageType.Error);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
@ -352,7 +320,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error Loading Child Pages For Parent"], MessageType.Error);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
}
}
@ -361,156 +329,178 @@
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_layouts = ThemeService.GetLayoutControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_layouts = new List<ThemeControl>();
_containers = new List<ThemeControl>();
}
_layouttype = "-";
_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 Loading Pane Layouts For Theme"], MessageType.Error);
AddModuleMessage(Localizer["Error.Pane.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 async Task SavePage()
{
Page page = null;
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (_name != string.Empty)
Page page = null;
try
{
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
string currentPath = page.Path;
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
{
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
string currentPath = page.Path;
page.Name = _name;
page.Title = _title;
if (_path == "" && _name.ToLower() != "home")
if (_path == string.Empty && _name.ToLower() != "home")
page.Name = _name;
page.Title = _title;
if (string.IsNullOrEmpty(_path))
{
_path = _name;
}
if (_path.Contains("/"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (string.IsNullOrEmpty(_parentid) || _parentid == "-1")
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
page.ParentId = Int32.Parse(_parentid);
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
if (parent.Path == string.Empty)
if (_path.Contains("/"))
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1")
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
page.ParentId = Int32.Parse(_parentid);
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
if (parent.Path == string.Empty)
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
}
}
if (!PagePathIsUnique(page.Path, page.SiteId, page.PageId, _pageList))
{
AddModuleMessage(Localizer["A page with path {0} already exists for the selected parent page. The page path needs to be unique for the selected parent.", _path], MessageType.Warning);
return;
}
if (_insert != "=")
{
Page child;
switch (_insert)
if (!PagePathIsUnique(page.Path, page.SiteId, page.PageId, _pageList))
{
case "<<":
page.Order = 0;
break;
case "<":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order - 1;
break;
case ">":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order + 1;
break;
case ">>":
page.Order = int.MaxValue;
break;
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
return;
}
}
page.IsNavigation = (_isnavigation == null || Boolean.Parse(_isnavigation));
page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.LayoutType = (_layouttype != "-") ? _layouttype : string.Empty;
if (!string.IsNullOrEmpty(page.LayoutType) && page.LayoutType == PageState.Site.DefaultLayoutType)
{
page.LayoutType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = _icon ?? string.Empty;
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
page.UserId = null;
page = await PageService.UpdatePageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
if (_currentparentid == string.Empty)
{
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, null);
if (_insert != "=")
{
Page child;
switch (_insert)
{
case "<<":
page.Order = 0;
break;
case "<":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order - 1;
break;
case ">":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order + 1;
break;
case ">>":
page.Order = int.MaxValue;
break;
}
}
page.IsNavigation = (_isnavigation == null || Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = _icon ?? string.Empty;
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
page.UserId = null;
page = await PageService.UpdatePageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
if (_currentparentid == string.Empty)
{
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, null);
}
else
{
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, int.Parse(_currentparentid));
}
// update child paths
if (_parentid != _currentparentid)
{
foreach (Page p in PageState.Pages.Where(item => item.Path.StartsWith(currentPath)))
{
p.Path = p.Path.Replace(currentPath, page.Path);
await PageService.UpdatePageAsync(p);
}
}
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
{
await themeSettingsControl.UpdateSettings();
}
await logger.LogInformation("Page Saved {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
}
}
else
{
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, int.Parse(_currentparentid));
}
// update child paths
if (_parentid != _currentparentid)
{
foreach (Page p in PageState.Pages.Where(item => item.Path.StartsWith(currentPath)))
{
p.Path = p.Path.Replace(currentPath, page.Path);
await PageService.UpdatePageAsync(p);
}
}
await logger.LogInformation("Page Saved {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
}
}
else
catch (Exception ex)
{
AddModuleMessage(Localizer["You Must Provide Page Name"], MessageType.Warning);
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error Saving Page"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}

View File

@ -2,7 +2,8 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IPageService PageService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.Pages != null)
{
@ -10,13 +11,14 @@
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
<td><ActionDialog Header="Delete Page" Message="@("Are You Sure You Wish To Delete The " + context.Name + " Page?")" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
<td>@(new string('-', context.Level * 2))@(context.Name)</td>
</Row>
</Pager>
@ -30,7 +32,7 @@
try
{
page.IsDeleted = true;
await PageService.UpdatePageAsync(page);
await logger.LogInformation("Page Deleted {Page}", page);
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
@ -38,7 +40,11 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error Deleting Page"], MessageType.Error);
AddModuleMessage(Localizer["Error.Page.Delete"], MessageType.Error);
}
}
protected string Browse(Page page)
{
return string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url;
}
}

View File

@ -3,100 +3,92 @@
@inject NavigationManager NavigationManager
@inject IProfileService ProfileService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="The name of this profile item" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="title" HelpText="The title of the profile item to display to the user" ResourceKey="Title">Title: </Label>
</td>
<td>
<input id="title" class="form-control" @bind="@_title" />
</td>
</tr>
<tr>
<td>
<Label For="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label>
</td>
<td>
<textarea id="description" class="form-control" @bind="@_description" rows="5"></textarea>
</td>
</tr>
<tr>
<td>
<Label For="category" HelpText="The category of this profile item (for grouping)" ResourceKey="Category">Category: </Label>
</td>
<td>
<input id="category" class="form-control" @bind="@_category" />
</td>
</tr>
<tr>
<td>
<Label For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label>
</td>
<td>
<input id="order" class="form-control" @bind="@_vieworder" />
</td>
</tr>
<tr>
<td>
<Label For="length" HelpText="The max number of characters this profile item should accept (enter zero for unlimited)" ResourceKey="Length">Length: </Label>
</td>
<td>
<input id="length" class="form-control" @bind="@_maxlength" />
</td>
</tr>
<tr>
<td>
<Label For="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label>
</td>
<td>
<input id="defaultVal" class="form-control" @bind="@_defaultvalue" />
</td>
</tr>
<tr>
<td>
<Label For="options" HelpText="A comma delimited list of options the user can select from" ResourceKey="Options">Options: </Label>
</td>
<td>
<input id="options" class="form-control" @bind="@_options" />
</td>
</tr>
<tr>
<td>
<Label For="required" HelpText="Should a user be required to provide a value for this profile item?" ResourceKey="Required">Required? </Label>
</td>
<td>
<select id="required" class="form-control" @bind="@_isrequired">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
</td>
<td>
<select id="private" class="form-control" @bind="@_isprivate">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveProfile">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<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 this profile item" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="The title of the profile item to display to the user" ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="50" required />
</div>
</div>
<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>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="category" HelpText="The category of this profile item (for grouping)" ResourceKey="Category">Category: </Label>
<div class="col-sm-9">
<input id="category" class="form-control" @bind="@_category" maxlength="50" />
</div>
</div>
<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 />
</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 />
</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"/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options the user can select from" ResourceKey="Options">Options: </Label>
<div class="col-sm-9">
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="required" HelpText="Should a user be required to provide a value for this profile item?" ResourceKey="Required">Required? </Label>
<div class="col-sm-9">
<select id="required" class="form-select" @bind="@_isrequired" 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="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
<div class="col-sm-9">
<select id="private" class="form-select" @bind="@_isprivate" 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="SaveProfile">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@if (PageState.QueryString.ContainsKey("id"))
{
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
}
</form>
@code {
private int _profileid = -1;
private ElementReference form;
private bool validated = false;
private string _name = string.Empty;
private string _title = string.Empty;
private string _description = string.Empty;
@ -107,6 +99,10 @@
private string _options = string.Empty;
private string _isrequired = "False";
private string _isprivate = "False";
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -132,57 +128,70 @@
_options = profile.Options;
_isrequired = profile.IsRequired.ToString();
_isprivate = profile.IsPrivate.ToString();
createdby = profile.CreatedBy;
createdon = profile.CreatedOn;
modifiedby = profile.ModifiedBy;
modifiedon = profile.ModifiedOn;
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Profile {ProfileId} {Error}", _profileid, ex.Message);
AddModuleMessage(Localizer["Error Loading Profile"], MessageType.Error);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
}
}
private async Task SaveProfile()
{
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
Profile profile;
if (_profileid != -1)
try
{
profile = await ProfileService.GetProfileAsync(_profileid);
}
else
{
profile = new Profile();
}
Profile profile;
if (_profileid != -1)
{
profile = await ProfileService.GetProfileAsync(_profileid);
}
else
{
profile = new Profile();
}
profile.SiteId = PageState.Site.SiteId;
profile.Name = _name;
profile.Title = _title;
profile.Description = _description;
profile.Category = _category;
profile.ViewOrder = int.Parse(_vieworder);
profile.MaxLength = int.Parse(_maxlength);
profile.DefaultValue = _defaultvalue;
profile.Options = _options;
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
if (_profileid != -1)
{
profile = await ProfileService.UpdateProfileAsync(profile);
}
else
{
profile = await ProfileService.AddProfileAsync(profile);
}
profile.SiteId = PageState.Site.SiteId;
profile.Name = _name;
profile.Title = _title;
profile.Description = _description;
profile.Category = _category;
profile.ViewOrder = int.Parse(_vieworder);
profile.MaxLength = int.Parse(_maxlength);
profile.DefaultValue = _defaultvalue;
profile.Options = _options;
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
if (_profileid != -1)
{
profile = await ProfileService.UpdateProfileAsync(profile);
}
else
{
profile = await ProfileService.AddProfileAsync(profile);
}
await logger.LogInformation("Profile Saved {Profile}", profile);
NavigationManager.NavigateTo(NavigateUrl());
await logger.LogInformation("Profile Saved {Profile}", profile);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Profile {ProfleId} {Error}", _profileid, ex.Message);
AddModuleMessage(Localizer["Error.Profile.Save"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Saving Profile {ProfleId} {Error}", _profileid, ex.Message);
AddModuleMessage(Localizer["Error Saving Profile"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@ -2,10 +2,11 @@
@inherits ModuleBase
@inject IProfileService ProfileService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_profiles == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
@ -15,11 +16,11 @@ else
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" ResourceKey="EditProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@("Are You Sure You Wish To Delete " + context.Name + "?")" Action="Delete" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
<td>@context.Name</td>
</Row>
</Pager>
@ -30,9 +31,9 @@ else
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
await GetProfilesAsync();
}
private async Task DeleteProfile(int profileId)
@ -41,12 +42,22 @@ else
{
await ProfileService.DeleteProfileAsync(profileId);
await logger.LogInformation("Profile Deleted {ProfileId}", profileId);
AddModuleMessage(Localizer["Profile Deleted"], MessageType.Success);
AddModuleMessage(Localizer["Success.Profile.Delete"], MessageType.Success);
await GetProfilesAsync();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Profile {ProfileId} {Error}", profileId, ex.Message);
AddModuleMessage(Localizer["Error Deleting Profile"], MessageType.Error);
AddModuleMessage(Localizer["Error.Profile.Delete"], MessageType.Error);
}
}
private async Task GetProfilesAsync()
{
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
}
}

View File

@ -5,13 +5,14 @@
@inject IModuleService ModuleService
@inject IPageService PageService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Pages" ResourceKey="Pages">
@if (_pages == null)
{
<br />
<p>@Localizer["No Deleted Pages"]</p>
<p>@Localizer["NoPage.Deleted"]</p>
}
else
{
@ -19,25 +20,31 @@
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@Localizer["Deleted By"]</th>
<th>@Localizer["Deleted On"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["DeletedBy"]</th>
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button @onclick="@(() => RestorePage(context))" class="btn btn-info" title="Restore">Restore</button></td>
<td><ActionDialog Header="Delete Page" Message="@Localizer["Are You Sure You Wish To Permanently Delete The {0} Page?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td>@context.Name</td>
<td>@context.DeletedBy</td>
<td>@context.DeletedOn</td>
</Row>
</Pager>
@if (_pages.Any())
{
<div style="text-align:right;">
<ActionDialog Header="Delete All Pages" Message="Are You Sure You Wish To Permanently Delete All Pages?" Action="Delete All Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
</div>
}
}
</TabPanel>
<TabPanel Name="Modules" ResourceKey="Modules">
@if (_modules == null)
{
<br />
<p>@Localizer["No Deleted Modules"]</p>
<p>@Localizer["NoModule.Deleted"]</p>
}
else
{
@ -47,18 +54,25 @@
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Page"]</th>
<th>@Localizer["Module"]</th>
<th>@Localizer["Deleted By"]</th>
<th>@Localizer["Deleted On"]</th>
<th>@Localizer["DeletedBy"]</th>
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button @onclick="@(() => RestoreModule(context))" class="btn btn-info" title="Restore">@Localizer["Restore"]</button></td>
<td><ActionDialog Header="Delete Module" Message="@Localizer["Are You Sure You Wish To Permanently Delete The {0} Module?", context.Title]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td>
<td>@context.Title</td>
<td>@context.DeletedBy</td>
<td>@context.DeletedOn</td>
</Row>
</Pager>
@if (_modules.Any())
{
<div style="text-align:right;">
<ActionDialog Header="Delete All Modules" Message="Are You Sure You Wish To Permanently Delete All Modules?" Action="Delete All Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
</div>
}
}
</TabPanel>
</TabStrip>
@ -78,7 +92,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Deleted Pages Or Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Deleted Pages Or Modules"], MessageType.Error);
AddModuleMessage(Localizer["Error.DeletedModulePage.Load"], MessageType.Error);
}
}
@ -105,7 +119,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Restoring Deleted Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error Restoring Deleted Page"], MessageType.Error);
AddModuleMessage(Localizer["Error.Page.Restore"], MessageType.Error);
}
}
@ -126,6 +140,28 @@
}
}
private async Task DeleteAllPages()
{
try
{
foreach (Page page in _pages)
{
await PageService.DeletePageAsync(page.PageId);
await logger.LogInformation("Page Permanently Deleted {Page}", page);
}
await logger.LogInformation("Pages Permanently Deleted");
await Load();
StateHasChanged();
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async Task RestoreModule(Module module)
{
try
@ -140,7 +176,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Restoring Deleted Module {Module} {Error}", module, ex.Message);
AddModuleMessage(Localizer["Error Restoring Deleted Module"], MessageType.Error);
AddModuleMessage(Localizer["Error.Module.Restore"], MessageType.Error);
}
}
@ -164,7 +200,34 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Permanently Deleting Module {Module} {Error}", module, ex.Message);
AddModuleMessage(Localizer["Error Permanently Deleting Module"], MessageType.Error);
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
}
}
private async Task DeleteAllModules()
{
try
{
foreach (Module module in _modules)
{
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// check if there are any remaining module instances in the site
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
}
await logger.LogInformation("Modules Permanently Deleted");
await Load();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
}
}
}

View File

@ -3,105 +3,129 @@
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.Site.AllowRegistration)
{
<AuthorizeView>
<AuthorizeView Roles="@RoleNames.Registered">
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<ModuleMessage Message="@Localizer["You Are Already Registered"]" Type="MessageType.Info" />
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized>
<ModuleMessage Message="@Localizer["Please Note That Registration Requires A Valid Email Address In Order To Verify Your Identity"]" Type="MessageType.Info" />
<div class="container">
<div class="form-group">
<label for="Username" class="control-label">@Localizer["Username:"] </label>
<input type="text" class="form-control" placeholder="Username" @bind="@_username" id="Username" />
</div>
<div class="form-group">
<label for="Password" class="control-label">@Localizer["Password:"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_password" id="Password" />
</div>
<div class="form-group">
<label for="Confirm" class="control-label">@Localizer["Confirm Password:"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm" />
</div>
<div class="form-group">
<label for="Email" class="control-label">@Localizer["Email:"] </label>
<input type="text" class="form-control" placeholder="Email" @bind="@_email" id="Email" />
</div>
<div class="form-group">
<label for="DisplayName" class="control-label">@Localizer["Full Name:"] </label>
<input type="text" class="form-control" placeholder="Full Name" @bind="@_displayName" id="DisplayName" />
<ModuleMessage Message="@Localizer["Info.Registration.InvalidEmail"]" Type="MessageType.Info" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@_password" autocomplete="new-password" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<input id="confirm" type="password" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button>
</div>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
</NotAuthorized>
</AuthorizeView>
}
else
{
<ModuleMessage Message="@Localizer["Registration is Disabled For This Site"]" Type="MessageType.Info" />
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
}
@code {
private string _username = string.Empty;
private ElementReference form;
private bool validated = false;
private string _password = string.Empty;
private string _confirm = string.Empty;
private string _email = string.Empty;
private string _displayName = string.Empty;
private string _displayname = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
private async Task Register()
{
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
bool _isEmailValid = Utilities.IsValidEmail(_email);
if (_username != "" && _password != "" && _confirm != "" && _isEmailValid)
try
{
if (_password == _confirm)
{
var user = new User
{
SiteId = PageState.Site.SiteId,
Username = _username,
DisplayName = (_displayName == string.Empty ? _username : _displayName),
Email = _email,
Password = _password
};
user = await UserService.AddUserAsync(user);
bool _isEmailValid = Utilities.IsValidEmail(_email);
if (user != null)
if (_isEmailValid)
{
if (_password == _confirm)
{
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["User Account Created. Please Check Your Email For Verification Instructions."], MessageType.Info);
var user = new User
{
SiteId = PageState.Site.SiteId,
Username = _username,
DisplayName = (_displayname == string.Empty ? _username : _displayname),
Email = _email,
Password = _password
};
user = await UserService.AddUserAsync(user);
if (user != null)
{
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Error.User.AddInfo"], MessageType.Error);
}
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use."], MessageType.Error);
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Passwords Entered Do Not Match"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
else
catch (Exception ex)
{
AddModuleMessage(Localizer["You Must Provide A Username, Password, and Email Address"], MessageType.Warning);
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message);
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message);
AddModuleMessage(Localizer["Error Adding User"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
@ -109,4 +133,4 @@ else
{
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
}
}
}

View File

@ -3,25 +3,30 @@
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="form-group">
<label for="Username" class="control-label">@Localizer["Username:"] </label>
<input type="text" class="form-control" placeholder="Username" @bind="@_username" readonly id="Username"/>
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="form-group">
<label for="Username" class="control-label">@SharedLocalizer["Username"] </label>
<input type="text" class="form-control" placeholder="Username" @bind="@_username" readonly id="Username" />
</div>
<div class="form-group">
<label for="Password" class="control-label">@SharedLocalizer["Password"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_password" id="Password" required />
</div>
<div class="form-group">
<label for="Confirm" class="control-label">@Localizer["Password.Confirm"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm" required />
</div>
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</div>
<div class="form-group">
<label for="Password" class="control-label">@Localizer["Password:"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_password" id="Password"/>
</div>
<div class="form-group">
<label for="Confirm" class="control-label">@Localizer["Confirm Password:"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm"/>
</div>
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Reset Password"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button>
</div>
</form>
@code {
private ElementReference form;
private bool validated = false;
private string _username = string.Empty;
private string _password = string.Empty;
private string _confirm = string.Empty;
@ -35,52 +40,61 @@
_username = PageState.QueryString["name"];
}
else
{
{
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
}
}
private async Task Reset()
{
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty)
try
{
if (_password == _confirm)
if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty)
{
var user = new User
if (_password == _confirm)
{
SiteId = PageState.Site.SiteId,
Username = _username,
Password = _password
};
user = await UserService.ResetPasswordAsync(user, PageState.QueryString["token"]);
var user = new User
{
SiteId = PageState.Site.SiteId,
Username = _username,
Password = _password
};
user = await UserService.ResetPasswordAsync(user, PageState.QueryString["token"]);
if (user != null)
{
await logger.LogInformation("User Password Reset {Username}", _username);
NavigationManager.NavigateTo(NavigateUrl("login"));
if (user != null)
{
await logger.LogInformation("User Password Reset {Username}", _username);
NavigationManager.NavigateTo(NavigateUrl("login"));
}
else
{
await logger.LogError("Error Resetting User Password {Username}", _username);
AddModuleMessage(Localizer["Error.Password.ResetInfo"], MessageType.Error);
}
}
else
{
await logger.LogError("Error Resetting User Password {Username}", _username);
AddModuleMessage(Localizer["Error Resetting User Password. Please Ensure Password Meets Complexity Requirements."], MessageType.Error);
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Passwords Entered Do Not Match"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
else
catch (Exception ex)
{
AddModuleMessage(Localizer["You Must Provide A Username, Password, and Email Address"], MessageType.Warning);
await logger.LogError(ex, "Error Resetting User Password {Username} {Error}", _username, ex.Message);
AddModuleMessage(Localizer["Error.Password.Reset"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Resetting User Password {Username} {Error}", _username, ex.Message);
AddModuleMessage(Localizer["Error Resetting User Password"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}

View File

@ -3,40 +3,41 @@
@inject NavigationManager NavigationManager
@inject IRoleService RoleService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
</td>
<td>
<textarea id="description" class="form-control" @bind="@_description" rows="5"></textarea>
</td>
</tr>
<tr>
<td>
<Label For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label>
</td>
<td>
<select id="isautoassigned" class="form-control" @bind="@_isautoassigned">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveRole">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<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 Role" ResourceKey="Name">Name:</Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
<div class="col-sm-9">
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label>
<div class="col-sm-9">
<select id="isautoassigned" class="form-select" @bind="@_isautoassigned" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveRole">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</div>
</form>
@code {
private ElementReference form;
private bool validated = false;
private string _name = string.Empty;
private string _description = string.Empty;
private string _isautoassigned = "False";
@ -45,24 +46,33 @@
private async Task SaveRole()
{
var role = new Role();
role.SiteId = PageState.Page.SiteId;
role.Name = _name;
role.Description = _description;
role.IsAutoAssigned = (_isautoassigned == null ? false : Boolean.Parse(_isautoassigned));
role.IsSystem = false;
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
role = await RoleService.AddRoleAsync(role);
await logger.LogInformation("Role Added {Role}", role);
var role = new Role();
role.SiteId = PageState.Page.SiteId;
role.Name = _name;
role.Description = _description;
role.IsAutoAssigned = (_isautoassigned == null ? false : Boolean.Parse(_isautoassigned));
role.IsSystem = false;
NavigationManager.NavigateTo(NavigateUrl());
try
{
role = await RoleService.AddRoleAsync(role);
await logger.LogInformation("Role Added {Role}", role);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Role {Role} {Error}", role, ex.Message);
AddModuleMessage(Localizer["Error.AddRole"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Adding Role {Role} {Error}", role, ex.Message);
AddModuleMessage(Localizer["Error Adding Role"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}

View File

@ -3,44 +3,51 @@
@inject NavigationManager NavigationManager
@inject IRoleService RoleService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
</td>
<td>
<textarea id="description" class="form-control" @bind="@_description" rows="5"></textarea>
</td>
</tr>
<tr>
<td>
<Label For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label>
</td>
<td>
<select id="isautoassigned" class="form-control" @bind="@_isautoassigned">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveRole">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<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 Role" ResourceKey="Name">Name:</Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
<div class="col-sm-9">
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label>
<div class="col-sm-9">
<select id="isautoassigned" class="form-select" @bind="@_isautoassigned" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveRole">@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>
</div>
</form>
@code {
private ElementReference form;
private bool validated = false;
private int _roleid;
private string _name = string.Empty;
private string _description = string.Empty;
private string _isautoassigned = "False";
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -55,33 +62,46 @@
_name = role.Name;
_description = role.Description;
_isautoassigned = role.IsAutoAssigned.ToString();
_createdby = role.CreatedBy;
_createdon = role.CreatedOn;
_modifiedby = role.ModifiedBy;
_modifiedon = role.ModifiedOn;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Role {RoleId} {Error}", _roleid, ex.Message);
AddModuleMessage(Localizer["Error Loading Role"], MessageType.Error);
AddModuleMessage(Localizer["Error.LoadRole"], MessageType.Error);
}
}
private async Task SaveRole()
{
var role = await RoleService.GetRoleAsync(_roleid);
role.Name = _name;
role.Description = _description;
role.IsAutoAssigned = (_isautoassigned != null && Boolean.Parse(_isautoassigned));
role.IsSystem = false;
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
var role = await RoleService.GetRoleAsync(_roleid);
role.Name = _name;
role.Description = _description;
role.IsAutoAssigned = (_isautoassigned != null && Boolean.Parse(_isautoassigned));
role.IsSystem = false;
try
{
role = await RoleService.UpdateRoleAsync(role);
await logger.LogInformation("Role Saved {Role}", role);
NavigationManager.NavigateTo(NavigateUrl());
try
{
role = await RoleService.UpdateRoleAsync(role);
await logger.LogInformation("Role Saved {Role}", role);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Role {Role} {Error}", role, ex.Message);
AddModuleMessage(Localizer["Error.SaveRole"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Saving Role {Role} {Error}", role, ex.Message);
AddModuleMessage(Localizer["Error Saving Role"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@ -2,10 +2,11 @@
@inherits ModuleBase
@inject IRoleService RoleService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_roles == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
@ -16,11 +17,11 @@ else
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete Role" Message="@Localizer["Are You Sure You Wish To Delete The {0} Role?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" ResourceKey="Users" /></td>
<td>@context.Name</td>
</Row>
@ -34,7 +35,7 @@ else
protected override async Task OnParametersSetAsync()
{
_roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
await GetRoles();
}
private async Task DeleteRole(Role role)
@ -43,12 +44,26 @@ else
{
await RoleService.DeleteRoleAsync(role.RoleId);
await logger.LogInformation("Role Deleted {Role}", role);
await GetRoles();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Role {Role} {Error}", role, ex.Message);
AddModuleMessage(Localizer["Error Deleting Role"], MessageType.Error);
AddModuleMessage(Localizer["Error.DeleteRole"], MessageType.Error);
}
}
private async Task GetRoles()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
_roles = _roles.Where(item => item.Name != RoleNames.Everyone).ToList();
}
else
{
_roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
}
}
}

View File

@ -3,74 +3,79 @@
@inject IRoleService RoleService
@inject IUserRoleService UserRoleService
@inject IStringLocalizer<Users> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (userroles == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<table class="table table-borderless">
<tr>
<td>
<Label For="role" HelpText="The role you are assigning users to" ResourceKey="Role">Role: </Label>
</td>
<td>
<input id="role" class="form-control" @bind="@name" disabled />
</td>
</tr>
<tr>
<td>
<Label For="user" HelpText="Select a user" ResourceKey="User">User: </Label>
</td>
<td>
<select id="user" class="form-control" @bind="@userid">
<option value="-1">&lt;@Localizer["Select User"]&gt;</option>
@foreach (UserRole userrole in users)
{
<option value="@(userrole.UserId)">@userrole.User.DisplayName</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
</td>
<td>
<input type="date" id="effectiveDate" class="form-control" @bind="@effectivedate" />
</td>
</tr>
<tr>
<td>
<Label For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
</td>
<td>
<input type="date" id="expiryDate" class="form-control" @bind="@expirydate" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<hr class="app-rule" />
<p align="center">
<Pager Items="@userroles">
<Header>
<th>@Localizer["Users"]</th>
<th>&nbsp;</th>
</Header>
<Row>
<td>@context.User.DisplayName</td>
<td>
<button type="button" class="btn btn-danger" @onclick=@(async () => await DeleteUserRole(context.UserRoleId))>@Localizer["Delete"]</button>
</td>
</Row>
</Pager>
</p>
<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="role" HelpText="The role you are assigning users to" ResourceKey="Role">Role: </Label>
<div class="col-sm-9">
<input id="role" class="form-control" @bind="@name" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label>
<div class="col-sm-9">
<select id="user" class="form-select" @bind="@userid" required>
<option value="-1">&lt;@Localizer["User.Select"]&gt;</option>
@foreach (UserRole userrole in users)
{
<option value="@(userrole.UserId)">@userrole.User.DisplayName</option>
}
</select>
</div>
</div>
<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 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 type="date" id="expiryDate" class="form-control" @bind="@expirydate" />
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<hr class="app-rule" />
<div class="row mb-1 align-items-center">
<p align="center">
<Pager Items="@userroles">
<Header>
<th>@Localizer["Users"]</th>
<th>@Localizer["Effective"]</th>
<th>@Localizer["Expiry"]</th>
<th>&nbsp;</th>
</Header>
<Row>
<td>@context.User.DisplayName</td>
<td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td>
<td>
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || PageState.User.Username == UserNames.Host)" ResourceKey="DeleteUserRole" />
</td>
</Row>
</Pager>
</p>
</div>
</div>
</form>
}
@code {
private ElementReference form;
private bool validated = false;
private int roleid;
private string name = string.Empty;
private List<UserRole> users;
@ -98,7 +103,7 @@ else
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Users"], MessageType.Error);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
}
@ -112,63 +117,84 @@ else
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Roles {RoleId} {Error}", roleid, ex.Message);
AddModuleMessage(Localizer["Error Loading User Roles"], MessageType.Error);
AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error);
}
}
private async Task SaveUserRole()
{
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (userid != -1)
try
{
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null)
if (userid != -1)
{
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null)
{
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
}
else
{
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.AddUserRoleAsync(userrole);
}
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success);
await GetUserRoles();
StateHasChanged();
}
else
{
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.AddUserRoleAsync(userrole);
AddModuleMessage(Localizer["Message.Required.UserSelect"], MessageType.Warning);
}
await GetUserRoles();
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
AddModuleMessage(Localizer["User Assigned To Role"], MessageType.Success);
}
else
catch (Exception ex)
{
AddModuleMessage(Localizer["You Must Select A User"], MessageType.Warning);
await logger.LogError(ex, "Error Saving User Roles {RoleId} {Error}", roleid, ex.Message);
AddModuleMessage(Localizer["Error.User.SaveRole"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Saving User Roles {RoleId} {Error}", roleid, ex.Message);
AddModuleMessage(Localizer["Error Saving User Roles"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task DeleteUserRole(int UserRoleId)
{
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
await GetUserRoles();
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
AddModuleMessage(Localizer["User Removed From Role"], MessageType.Success);
try
{
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
await GetUserRoles();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message);
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
}
}
catch (Exception ex)
else
{
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message);
AddModuleMessage(Localizer["Error Removing User From Role"], MessageType.Error);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@ -3,261 +3,259 @@
@inject NavigationManager NavigationManager
@inject ISiteService SiteService
@inject ITenantService TenantService
@inject IDatabaseService DatabaseService
@inject IAliasService AliasService
@inject IThemeService ThemeService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject INotificationService NotificationService
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="Enter the site name" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="tenant" HelpText="Enter the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
</td>
<td>
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</td>
</tr>
<tr>
<td>
<Label For="alias" HelpText="Enter the alias for the server" ResourceKey="Aliases">Aliases: </Label>
</td>
<td>
<textarea id="alias" class="form-control" @bind="@_urls" rows="3"></textarea>
</td>
</tr>
<tr>
<td>
<Label For="logo" HelpText="Upload a logo for the site" ResourceKey="Logo">Logo: </Label>
</td>
<td>
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</td>
</tr>
<tr>
<td>
<Label For="favicon" HelpText="Select Your default icon" ResourceKey="FavoriteIcon">Favicon: </Label>
</td>
<td>
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</td>
</tr>
<tr>
<td>
<Label For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
</td>
<td>
<select id="defaultTheme" class="form-control" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Select Theme"]&gt;</option>
@foreach (var theme in _themes)
{
if (theme.TypeName == _themetype)
<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="Enter the site name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<option value="@theme.TypeName" selected>@theme.Name</option>
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
}
else
{
<option value="@theme.TypeName">@theme.Name</option>
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" readonly></textarea>
}
}
</select>
</td>
</tr>
@if (_layouts.Count > 0)
{
<tr>
<td>
<Label For="defaultLayout" HelpText="Select the sites default layout" ResourceKey="DefaultLayout">Default Layout: </Label>
</td>
<td>
<select id="defaultLayout" class="form-control" @bind="@_layouttype">
<option value="-">&lt;@Localizer["Select Layout"]&gt;</option>
@foreach (var layout in _layouts)
{
<option value="@(layout.TypeName)">@(layout.Name)</option>
}
</select>
</td>
</tr>
}
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
</td>
<td>
<select id="defaultAdminContainer" class="form-control" @bind="@_admincontainertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
<option value="">&lt;@Localizer["Default Admin Container"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="allowRegister" HelpText="Do you want the users to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration? </Label>
</td>
<td>
<select id="allowRegister" class="form-control" @bind="@_allowregistration">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
</td>
<td>
<select id="isDeleted" class="form-control" @bind="@_isdeleted">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
</table>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<table class="table table-borderless">
<tr>
<td colspan="2">
@Localizer["Please Note That SMTP Requires The Notification Job To Be Enabled In the Scheduled Jobs"]
</td>
</tr>
<tr>
<td>
<Label For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
</td>
<td>
<input id="host" class="form-control" @bind="@_smtphost" />
</td>
</tr>
<tr>
<td>
<Label For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
</td>
<td>
<input id="port" class="form-control" @bind="@_smtpport" />
</td>
</tr>
<tr>
<td>
<Label For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
</td>
<td>
<select id="enabledSSl" class="form-control" @bind="@_smtpssl">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allowRegister" HelpText="Do you want the users to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration? </Label>
<div class="col-sm-9">
<select id="allowRegister" class="form-select" @bind="@_allowregistration" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmptUsername">Username: </Label>
</td>
<td>
<input id="username" class="form-control" @bind="@_smtpusername" />
</td>
</tr>
<tr>
<td>
<Label For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
</td>
<td>
<input id="password" type="password" class="form-control" @bind="@_smtppassword" />
</td>
</tr>
<tr>
<td>
<Label For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmptSender">Email Sender: </Label>
</td>
<td>
<input id="sender" class="form-control" @bind="@_smtpsender" />
</td>
</tr>
</table>
</Section>
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
<table class="table table-borderless">
<tr>
<td>
<Label For="isEnabled" HelpText="Select whether you would like this site to be available as a Progressive Web Application (PWA)" ResourceKey="EnablePWA">Is Enabled? </Label>
</td>
<td>
<select id="isEnabled" class="form-control" @bind="@_pwaisenabled">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="appIcon" HelpText="Include an application icon for your PWA. It should be a PNG which is 192 X 192 pixels in dimension." ResourceKey="PwaApplicationIcon">App Icon: </Label>
</td>
<td>
<FileManager FileId="@_pwaappiconfileid" Filter="png" @ref="_pwaappiconfilemanager" />
</td>
</tr>
<tr>
<td>
<Label For="splashIcon" HelpText="Include a splash icon for your PWA. It should be a PNG which is 512 X 512 pixels in dimension." ResourceKey="PwaSplashIcon">Splash Icon: </Label>
</td>
<td>
<FileManager FileId="@_pwasplashiconfileid" Filter="png" @ref="_pwasplashiconfilemanager" />
</td>
</tr>
</table>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveSite">@Localizer["Save"]</button>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
</div>
</div>
</div>
<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="@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>
</Section>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-3">
</div>
<div class="col-sm-9">
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
<div class="col-sm-9">
<input id="host" class="form-control" @bind="@_smtphost" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
<div class="col-sm-9">
<input id="port" class="form-control" @bind="@_smtpport" />
</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>
<div class="col-sm-9">
<select id="enabledSSl" class="form-select" @bind="@_smtpssl" >
<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="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmptUsername">Username: </Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_smtpusername" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@_smtppassword" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmptSender">Email Sender: </Label>
<div class="col-sm-9">
<input id="sender" class="form-control" @bind="@_smtpsender" />
</div>
</div>
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
<br /><br />
</div>
</Section>
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isEnabled" HelpText="Select whether you would like this site to be available as a Progressive Web Application (PWA)" ResourceKey="EnablePWA">Is Enabled? </Label>
<div class="col-sm-9">
<select id="isEnabled" class="form-select" @bind="@_pwaisenabled" 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="appIcon" HelpText="Include an application icon for your PWA. It should be a PNG which is 192 X 192 pixels in dimension." ResourceKey="PwaApplicationIcon">App Icon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_pwaappiconfileid" Filter="png" @ref="_pwaappiconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="splashIcon" HelpText="Include a splash icon for your PWA. It should be a PNG which is 512 X 512 pixels in dimension." ResourceKey="PwaSplashIcon">Splash Icon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_pwasplashiconfileid" Filter="png" @ref="_pwasplashiconfilemanager" />
</div>
</div>
</div>
</Section>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<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>
<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>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if the site should be prerendered (for search crawlers, etc...)" ResourceKey="Prerender">Prerender? </Label>
<div class="col-sm-9">
<select id="prerender" class="form-select" @bind="@_prerender" required>
<option value="Prerendered">@SharedLocalizer["Yes"]</option>
<option value="">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</Section>
<Section Name="TenantInformation" Heading="Tenant Information" ResourceKey="TenantInformation">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="The tenant for the site" ResourceKey="Tenant">Tenant: </Label>
<div class="col-sm-9">
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="database" HelpText="The database for the tenant" ResourceKey="Database">Database: </Label>
<div class="col-sm-9">
<input id="database" class="form-control" @bind="@_database" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea>
</div>
</div>
</div>
</Section>
}
<br />
<button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button>
<ActionDialog Header="Delete Site" Message="@Localizer["Confirm.DeleteSite"]" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSite())" ResourceKey="DeleteSite" />
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
</form>
}
@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> _layouts = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
private List<Tenant> _tenantList;
private string _tenant = string.Empty;
private List<Alias> _aliasList;
private string _urls = string.Empty;
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 _layouttype = "-";
private string _containertype = "-";
private string _admincontainertype = "-";
private string _allowregistration;
@ -272,6 +270,9 @@
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;
@ -287,17 +288,26 @@
try
{
_themeList = await ThemeService.GetThemesAsync();
_aliasList = await AliasService.GetAliasesAsync();
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
_name = site.Name;
_tenantList = await TenantService.GetTenantsAsync();
_tenant = _tenantList.Find(item => item.TenantId == site.TenantId).Name;
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
_runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, "");
_allowregistration = site.AllowRegistration.ToString();
_isdeleted = site.IsDeleted.ToString();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_urls += alias.Name + "\n";
_aliasList = await AliasService.GetAliasesAsync();
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
_urls += alias.Name + ",";
}
_urls = _urls.Substring(0, _urls.Length - 1);
}
if (site.LogoFileId != null)
{
_logofileid = site.LogoFileId.Value;
@ -309,13 +319,10 @@
}
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = site.DefaultThemeType;
_layouts = ThemeService.GetLayoutControls(_themeList, _themetype);
_layouttype = site.DefaultLayoutType;
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = site.DefaultContainerType;
_admincontainertype = site.AdminContainerType;
_allowregistration = site.AllowRegistration.ToString();
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
@ -347,13 +354,25 @@
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
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;
}
}
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy;
_modifiedon = site.ModifiedOn;
_deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
_isdeleted = site.IsDeleted.ToString();
_initialized = true;
}
@ -372,134 +391,221 @@
_themetype = (string)e.Value;
if (_themetype != "-")
{
_layouts = ThemeService.GetLayoutControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_layouts = new List<ThemeControl>();
_containers = new List<ThemeControl>();
}
_layouttype = "-";
_containertype = "-";
_admincontainertype = "";
_admincontainertype = Constants.DefaultAdminContainer;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error Loading Pane Layouts For Theme"], MessageType.Error);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
}
}
private async Task SaveSite()
{
try
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && (_layouts.Count == 0 || _layouttype != "-") && _containertype != "-")
try
{
var unique = true;
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-")
{
if (_aliasList.Exists(item => item.Name == name && item.SiteId != PageState.Alias.SiteId && item.TenantId != PageState.Alias.TenantId))
var unique = true;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
unique = false;
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (_aliasList.Exists(item => item.Name == name && item.SiteId != PageState.Alias.SiteId && item.TenantId != PageState.Alias.TenantId))
{
unique = false;
}
}
}
}
if (unique)
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
if (unique)
{
site.Name = _name;
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
site.LogoFileId = logofileid;
}
bool reload = false;
bool refresh = (site.DefaultThemeType != _themetype || site.DefaultContainerType != _containertype);
var faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId != -1)
{
site.FaviconFileId = faviconFieldId;
}
site.DefaultThemeType = _themetype;
site.DefaultLayoutType = (_layouttype == "-" ? string.Empty : _layouttype);
site.DefaultContainerType = _containertype;
site.AdminContainerType = _admincontainertype;
site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration));
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.PwaIsEnabled = (_pwaisenabled == null ? true : Boolean.Parse(_pwaisenabled));
var pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid != -1)
{
site.PwaAppIconFileId = pwaappiconfileid;
}
var pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid != -1)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
}
site = await SiteService.UpdateSiteAsync(site);
_urls = _urls.Replace("\n", ",");
var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
if (!names.Contains(alias.Name))
site.Name = _name;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
refresh = true;
reload = true;
}
}
site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration));
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{
site.LogoFileId = logofileid;
}
var faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId != -1)
{
site.FaviconFileId = faviconFieldId;
}
site.DefaultThemeType = _themetype;
site.DefaultContainerType = _containertype;
site.AdminContainerType = _admincontainertype;
site.PwaIsEnabled = (_pwaisenabled == null ? true : Boolean.Parse(_pwaisenabled));
var pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid != -1)
{
site.PwaAppIconFileId = pwaappiconfileid;
}
var pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid != -1)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
}
site = await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", _smtphost);
SettingService.SetSetting(settings, "SMTPPort", _smtpport);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
if (!names.Contains(alias.Name))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
}
}
foreach (string name in names)
{
if (!_aliasList.Exists(item => item.Name == name))
{
Alias alias = new Alias();
alias.Name = name;
alias.TenantId = site.TenantId;
alias.SiteId = site.SiteId;
await AliasService.AddAliasAsync(alias);
}
}
}
await logger.LogInformation("Site Settings Saved {Site}", site);
if (refresh)
{
NavigationManager.NavigateTo(NavigateUrl(), reload); // refresh to show new theme or container
}
else
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
}
}
foreach (string name in names)
{
if (!_aliasList.Exists(item => item.Name == name))
{
Alias alias = new Alias();
alias.Name = name;
alias.TenantId = site.TenantId;
alias.SiteId = site.SiteId;
await AliasService.AddAliasAsync(alias);
}
}
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", _smtphost);
SettingService.SetSetting(settings, "SMTPPort", _smtpport);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Settings Saved {Site}", site);
AddModuleMessage(Localizer["Site Settings Saved"], MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["An Alias Specified Has Already Been Used For Another Site"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task DeleteSite()
{
try
{
var sites = await SiteService.GetSitesAsync();
if (sites.Count > 1)
{
await SiteService.DeleteSiteAsync(PageState.Site.SiteId);
await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId);
var aliases = await AliasService.GetAliasesAsync();
foreach (Alias a in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
{
await AliasService.DeleteAliasAsync(a.AliasId);
}
NavigationManager.NavigateTo(NavigateUrl("admin/sites"));
}
else
{
AddModuleMessage(Localizer["You Must Provide A Site Name, Alias, And Default Theme/Layout/Container"], MessageType.Warning);
AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error Saving Site"], MessageType.Error);
await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error);
}
}
private async Task SendEmail()
{
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", _smtphost);
SettingService.SetSetting(settings, "SMTPPort", _smtpport);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await logger.LogInformation("Site SMTP Settings Saved");
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User.DisplayName, PageState.User.Email, PageState.User.DisplayName, PageState.User.Email, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Testing SMTP Configuration");
AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Message.required.Smtp"], MessageType.Warning);
}
}
}

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Sites
@using Oqtane.Interfaces
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ITenantService TenantService
@ -8,240 +9,194 @@
@inject ISiteTemplateService SiteTemplateService
@inject IUserService UserService
@inject IInstallationService InstallationService
@inject IDatabaseService DatabaseService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_tenants == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="Enter the name of the site" ResourceKey="Name">Site Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="alias" HelpText="Enter the alias for the server" ResourceKey="Aliases">Aliases: </Label>
</td>
<td>
<textarea id="alias" class="form-control" @bind="@_urls" rows="3"></textarea>
</td>
</tr>
<tr>
<td>
<Label For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
</td>
<td>
<select id="defaultTheme" class="form-control" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Select Theme"]&gt;</option>
@foreach (var theme in _themes)
<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="Enter the name of the site" ResourceKey="Name">Site Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they can be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
</div>
<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>
<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="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">
<select id="siteTemplate" class="form-select" @bind="@_sitetemplatetype" required>
<option value="-">&lt;@Localizer["SiteTemplate.Select"]&gt;</option>
@foreach (SiteTemplate siteTemplate in _siteTemplates)
{
<option value="@siteTemplate.TypeName">@siteTemplate.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtime" HelpText="The runtime hosting model" 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>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if the site should be prerendered (for search crawlers, etc...)" ResourceKey="Prerender">Prerender? </Label>
<div class="col-sm-9">
<select id="prerender" class="form-select" @bind="@_prerender" required>
<option value="Prerendered">@SharedLocalizer["Yes"]</option>
<option value="">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="Select the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
<div class="col-sm-9">
<select id="tenant" class="form-select" @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)
{
<option value="@tenant.TenantId">@tenant.Name</option>
}
</select>
</div>
</div>
@if (_tenantid == "+")
{
<div class="row mb-1 align-items-center">
<hr class="app-rule" />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the tenant" ResourceKey="TenantName">Tenant Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
<div class="col-sm-9">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
if (database.IsDefault)
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
}
</select>
</div>
</div>
if (_databaseConfigType != null)
{
<option value="@theme.TypeName">@theme.Name</option>
@DatabaseConfigComponent;
}
</select>
</td>
</tr>
@if (_layouts.Count > 0)
{
<tr>
<td>
<Label For="defaultLayout" HelpText="Select the default layout for the site" ResourceKey="DefaultLayout">Default Layout: </Label>
</td>
<td>
<select id="defaultLayout" class="form-control" @bind="@_layouttype">
<option value="-">&lt;@Localizer["Select Layout"]&gt;</option>
@foreach (var layout in _layouts)
{
<option value="@(layout.TypeName)">@(layout.Name)</option>
}
</select>
</td>
</tr>
}
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
</td>
<td>
<select id="adminContainer" class="form-control" @bind="@_admincontainertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
<option value="">&lt;@Localizer["Default Admin Container"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
</td>
<td>
<select id="siteTemplate" class="form-control" @bind="@_sitetemplatetype">
<option value="-">&lt;@Localizer["Select Site Template"]&gt;</option>
@foreach (SiteTemplate siteTemplate in _siteTemplates)
{
<option value="@siteTemplate.TypeName">@siteTemplate.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="tenant" HelpText="Select the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
</td>
<td>
<select id="tenant" class="form-control" @onchange="(e => TenantChanged(e))">
<option value="-">&lt;@Localizer["Select Tenant"]&gt;</option>
<option value="+">&lt;@Localizer["Create New Tenant"]&gt;</option>
@foreach (Tenant tenant in _tenants)
{
<option value="@tenant.TenantId">@tenant.Name</option>
}
</select>
</td>
</tr>
@if (_tenantid == "+")
{
<tr>
<td colspan="2">
<hr class="app-rule" />
</td>
</tr>
<tr>
<td>
<Label For="name" HelpText="Enter the name for the tenant" ResourceKey="TenantName">Tenant Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_tenantname" />
</td>
</tr>
<tr>
<td>
<Label For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
</td>
<td>
<select id="databaseType" class="custom-select" @bind="@_databasetype">
<option value="LocalDB">@Localizer["Local Database"]</option>
<option value="SQLServer">@Localizer["SQL Server"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="server" HelpText="Enter the server for the tenant" ResourceKey="DatabaseServer">Server: </Label>
</td>
<td>
<input id="server" type="text" class="form-control" @bind="@_server" />
</td>
</tr>
<tr>
<td>
<Label For="database" HelpText="Enter the database for the tenant" ResourceKey="Database">Database: </Label>
</td>
<td>
<input id="database" type="text" class="form-control" @bind="@_database" />
</td>
</tr>
<tr>
<td>
<Label For="integratedSecurity" HelpText="Select if you want integrated security or not" ResourceKey="IntegratedSecurity">Integrated Security: </Label>
</td>
<td>
<select id="integratedSecurity" class="custom-select" @onchange="SetIntegratedSecurity">
<option value="true" selected>@Localizer["True"]</option>
<option value="false">@Localizer["False"]</option>
</select>
</td>
</tr>
@if (!_integratedsecurity)
{
<tr>
<td>
<Label For="username" HelpText="Enter the username for the integrated security" ResourceKey="DatabaseUsername">Database Username: </Label>
</td>
<td>
<input id="username" type="text" class="form-control" @bind="@_username" />
</td>
</tr>
<tr>
<td>
<Label For="password" HelpText="Enter the password for the integrated security" ResourceKey="DatabasePassword">Database Password: </Label>
</td>
<td>
<input id="password" type="password" class="form-control" @bind="@_password" />
</td>
</tr>
}
<tr>
<td>
<Label For="hostUsername" HelpText="Enter the username of the host for this site" ResourceKey="HostUsername">Host Username:</Label>
</td>
<td>
<input id="hostUsername" class="form-control" @bind="@_hostusername" readonly />
</td>
</tr>
<tr>
<td>
<Label For="hostPassword" HelpText="Enter the password for the host of this site" ResourceKey="HostPassword">Host Password:</Label>
</td>
<td>
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" />
</td>
</tr>
}
</table>
<button type="button" class="btn btn-success" @onclick="SaveSite">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of an existing host user" ResourceKey="HostUsername">Host Username:</Label>
<div class="col-sm-9">
<input id="hostUsername" class="form-control" @bind="@_hostusername" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hostPassword" HelpText="Enter the password of an existing host user" ResourceKey="HostPassword">Host Password:</Label>
<div class="col-sm-9">
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" required />
</div>
</div>
}
</div>
<br />
<br />
<button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
}
@code {
private List<Database> _databases;
private ElementReference form;
private bool validated = false;
private string _databaseName = "LocalDB";
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _layouts = 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 _databasetype = "LocalDB";
private string _server = "(LocalDb)\\MSSQLLocalDB";
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
private string _username = string.Empty;
private string _password = string.Empty;
private bool _integratedsecurity = true;
private string _hostusername = UserNames.Host;
private string _tenantName = 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 _layouttype = "-";
private string _containertype = "-";
private string _admincontainertype = "";
private string _sitetemplatetype = "-";
private string _runtime = "Server";
private string _prerender = "Prerendered";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -252,27 +207,45 @@ else
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
_databases = await DatabaseService.GetDatabasesAsync();
LoadDatabaseConfigComponent();
}
private void DatabaseChanged(ChangeEventArgs eventArgs)
{
try
{
_databaseName = (string)eventArgs.Value;
LoadDatabaseConfigComponent();
}
catch
{
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
}
}
private void LoadDatabaseConfigComponent()
{
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (database != null)
{
_databaseConfigType = Type.GetType(database.ControlType);
DatabaseConfigComponent = builder =>
{
builder.OpenComponent(0, _databaseConfigType);
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
builder.CloseComponent();
};
}
}
private void TenantChanged(ChangeEventArgs e)
{
_tenantid = (string)e.Value;
if (string.IsNullOrEmpty(_tenantname))
if (string.IsNullOrEmpty(_tenantName))
{
_tenantname = _name;
}
StateHasChanged();
}
private void SetIntegratedSecurity(ChangeEventArgs e)
{
if (Convert.ToBoolean((string)e.Value))
{
_integratedsecurity = true;
}
else
{
_integratedsecurity = false;
_tenantName = _name;
}
StateHasChanged();
}
@ -284,144 +257,142 @@ else
_themetype = (string)e.Value;
if (_themetype != "-")
{
_layouts = ThemeService.GetLayoutControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_layouts = new List<ThemeControl>();
_containers = new List<ThemeControl>();
}
_layouttype = "-";
_containertype = "-";
_admincontainertype = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error Loading Pane Layouts For Theme"], MessageType.Error);
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
}
}
private async Task SaveSite()
{
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && (_layouts.Count == 0 || _layouttype != "-") && _containertype != "-" && _sitetemplatetype != "-")
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
{
if (aliases.Exists(item => item.Name == name))
var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
duplicates.Add(name);
}
}
if (duplicates.Count == 0)
{
InstallConfig config = new InstallConfig();
if (_tenantid == "+")
{
if (!string.IsNullOrEmpty(_tenantname) && _tenants.FirstOrDefault(item => item.Name == _tenantname) == null)
if (aliases.Exists(item => item.Name == name))
{
// validate host credentials
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = UserNames.Host;
user.Password = _hostpassword;
user = await UserService.LoginUserAsync(user, false, false);
if (user.IsAuthenticated)
duplicates.Add(name);
}
}
if (duplicates.Count == 0)
{
InstallConfig config = new InstallConfig();
if (_tenantid == "+")
{
if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null)
{
if (!string.IsNullOrEmpty(_server) && !string.IsNullOrEmpty(_database))
// validate host credentials
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _hostusername;
user.Password = _hostpassword;
user = await UserService.LoginUserAsync(user, false, false);
if (user.IsAuthenticated)
{
var connectionString = string.Empty;
if (_databasetype == "LocalDB")
var connectionString = String.Empty;
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = "Data Source=" + _server + ";AttachDbFilename=|DataDirectory|\\" + _database + ".mdf;Initial Catalog=" + _database + ";Integrated Security=SSPI;";
connectionString = databaseConfigControl.GetConnectionString();
}
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (connectionString != "")
{
config.TenantName = _tenantName;
config.DatabaseType = database.DBType;
config.ConnectionString = connectionString;
config.HostUsername = _hostusername;
config.HostPassword = _hostpassword;
config.HostEmail = user.Email;
config.HostName = user.DisplayName;
config.IsNewTenant = true;
}
else
{
connectionString = "Data Source=" + _server + ";Initial Catalog=" + _database + ";";
if (_integratedsecurity)
{
connectionString += "Integrated Security=SSPI;";
}
else
{
connectionString += "User ID=" + _username + ";Password=" + _password;
}
AddModuleMessage(Localizer["Error.Required.ServerDatabase"], MessageType.Error);
}
config.ConnectionString = connectionString;
config.HostPassword = _hostpassword;
config.HostEmail = user.Email;
config.HostName = user.DisplayName;
config.TenantName = _tenantname;
config.IsNewTenant = true;
}
else
{
AddModuleMessage(Localizer["You Must Specify A Server And Database"], MessageType.Error);
AddModuleMessage(Localizer["Error.InvalidPassword"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Invalid Host Password"], MessageType.Error);
AddModuleMessage(Localizer["Error.TenantName.Exists"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Tenant Name Is Missing Or Already Exists"], MessageType.Error);
var tenant = _tenants.FirstOrDefault(item => item.TenantId == int.Parse(_tenantid));
if (tenant != null)
{
config.TenantName = tenant.Name;
config.DatabaseType = tenant.DBType;
config.ConnectionString = tenant.DBConnectionString;
config.IsNewTenant = false;
}
}
if (!string.IsNullOrEmpty(config.TenantName))
{
config.SiteName = _name;
config.Aliases = _urls;
config.DefaultTheme = _themetype;
config.DefaultContainer = _containertype;
config.DefaultAdminContainer = _admincontainertype;
config.SiteTemplate = _sitetemplatetype;
config.Runtime = _runtime;
config.RenderMode = _runtime + _prerender;
ShowProgressIndicator();
var installation = await InstallationService.Install(config);
if (installation.Success)
{
var aliasname = config.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
var uri = new Uri(NavigationManager.Uri);
NavigationManager.NavigateTo(uri.Scheme + "://" + aliasname, true);
}
else
{
await logger.LogError("Error Creating Site {Error}", installation.Message);
AddModuleMessage(installation.Message, MessageType.Error);
}
}
}
else
{
var tenant = _tenants.FirstOrDefault(item => item.TenantId == int.Parse(_tenantid));
if (tenant != null)
{
config.TenantName = tenant.Name;
config.ConnectionString= tenant.DBConnectionString;
config.IsNewTenant = false;
}
}
if (!string.IsNullOrEmpty(config.TenantName))
{
config.SiteName = _name;
config.Aliases = _urls.Replace("\n", ",");
config.DefaultTheme = _themetype;
config.DefaultLayout = _layouttype;
config.DefaultContainer = _containertype;
config.DefaultAdminContainer = _admincontainertype;
config.SiteTemplate = _sitetemplatetype;
ShowProgressIndicator();
var installation = await InstallationService.Install(config);
if (installation.Success)
{
var aliasname = config.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
var uri = new Uri(NavigationManager.Uri);
NavigationManager.NavigateTo(uri.Scheme + "://" + aliasname, true);
}
else
{
await logger.LogError("Error Creating Site {Error}", installation.Message);
AddModuleMessage(installation.Message, MessageType.Error);
}
AddModuleMessage(string.Format(Localizer["Message.SiteName.InUse"], string.Join(", ", duplicates.ToArray())), MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["{0} Already Used For Another Site", string.Join(", ", duplicates.ToArray())], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.Tenant"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["You Must Provide A Tenant, Site Name, Alias, Default Theme/Layout/Container, And Site Template"], MessageType.Warning);
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@ -1,315 +0,0 @@
@namespace Oqtane.Modules.Admin.Sites
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ISiteService SiteService
@inject ITenantService TenantService
@inject IAliasService AliasService
@inject IThemeService ThemeService
@inject IStringLocalizer<Edit> Localizer
@if (_initialized)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="Enter the name of the site" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="alias" HelpText="Enter the alias for the server" ResourceKey="Aliases">Aliases: </Label>
</td>
<td>
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" />
</td>
</tr>
<tr>
<td>
<Label For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
</td>
<td>
<select id="defaultTheme" class="form-control" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Select Theme"]&gt;</option>
@foreach (var theme in _themes)
{
if (theme.TypeName == _themetype)
{
<option value="@theme.TypeName" selected>@theme.Name</option>
}
else
{
<option value="@theme.TypeName">@theme.Name</option>
}
}
</select>
</td>
</tr>
@if (_layouts.Count > 0)
{
<tr>
<td>
<Label For="defaultLayout" HelpText="Select the default layout for the site" ResourceKey="DefaultLayout">Default Layout: </Label>
</td>
<td>
<select id="defaultLayout" class="form-control" @bind="@_layouttype">
<option value="-">&lt;@Localizer["Select Layout"]&gt;</option>
@foreach (var layout in _layouts)
{
<option value="@(layout.TypeName)">@(layout.Name)</option>
}
</select>
</td>
</tr>
}
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
</td>
<td>
<select id="defaultIdea" class="form-control" @bind="@_containertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
</td>
<td>
<select id="defaultAdminContainer" class="form-control" @bind="@_admincontainertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
<option value="">&lt;@Localizer["Default Admin Container"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="isDeleted" HelpText="Has this site been deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
</td>
<td>
<select id="isDeleted" class="form-control" @bind="@_isdeleted">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="tenant" HelpText="The tenant for the site" ResourceKey="Tenant">Tenant: </Label>
</td>
<td>
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</td>
</tr>
<tr>
<td>
<Label For="connectionstring" HelpText="The database connection string" ResourceKey="ConnectionString">Connection String: </Label>
</td>
<td>
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="3" readonly></textarea>
</td>
</tr>
</table>
<br />
<button type="button" class="btn btn-success" @onclick="SaveSite">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
}
@code {
private bool _initialized = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _layouts = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private Alias _alias;
private string _name = string.Empty;
private List<Alias> _aliasList;
private string _urls = string.Empty;
private string _themetype;
private string _layouttype;
private string _containertype = "-";
private string _admincontainertype = "-";
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
private string _isdeleted;
private string _tenant = string.Empty;
private string _connectionstring = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeList = await ThemeService.GetThemesAsync();
_aliasList = await AliasService.GetAliasesAsync();
_alias = _aliasList.Find(item => item.AliasId == Int32.Parse(PageState.QueryString["id"]));
SiteService.SetAlias(_alias);
var site = await SiteService.GetSiteAsync(_alias.SiteId);
if (site != null)
{
_name = site.Name;
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
_urls += alias.Name + "\n";
}
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = site.DefaultThemeType;
_layouts = ThemeService.GetLayoutControls(_themeList, _themetype);
_layouttype = site.DefaultLayoutType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = site.DefaultContainerType;
_admincontainertype = site.AdminContainerType;
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy;
_modifiedon = site.ModifiedOn;
_deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
_isdeleted = site.IsDeleted.ToString();
List<Tenant> tenants = await TenantService.GetTenantsAsync();
Tenant tenant = tenants.Find(item => item.TenantId == site.TenantId);
if (tenant != null)
{
_tenant = tenant.Name;
_connectionstring = tenant.DBConnectionString;
}
_initialized = true;
}
}
catch (Exception ex)
{
await Log(_alias, LogLevel.Error, string.Empty, ex, "Error Loading Site {SiteId} {Error}", _alias.SiteId, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_layouts = ThemeService.GetLayoutControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_layouts = new List<ThemeControl>();
_containers = new List<ThemeControl>();
}
_layouttype = "-";
_containertype = "-";
_admincontainertype = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error Loading Pane Layouts For Theme"], MessageType.Error);
}
}
private async Task SaveSite()
{
try
{
if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && (_layouts.Count == 0 || _layouttype != "-") && _containertype != "-")
{
var unique = true;
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (_aliasList.Exists(item => item.Name == name && item.SiteId != _alias.SiteId && item.TenantId != _alias.TenantId))
{
unique = false;
}
}
if (unique)
{
SiteService.SetAlias(_alias);
var site = await SiteService.GetSiteAsync(_alias.SiteId);
if (site != null)
{
site.Name = _name;
site.LogoFileId = null;
site.DefaultThemeType = _themetype;
site.DefaultLayoutType = _layouttype ?? string.Empty;
site.DefaultContainerType = _containertype;
site.AdminContainerType = _admincontainertype;
site.IsDeleted = (_isdeleted == null || Boolean.Parse(_isdeleted));
site = await SiteService.UpdateSiteAsync(site);
_urls = _urls.Replace("\n", ",");
var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
if (!names.Contains(alias.Name))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
}
}
foreach (string name in names)
{
if (!_aliasList.Exists(item => item.Name == name))
{
Alias alias = new Alias
{
Name = name,
TenantId = site.TenantId,
SiteId = site.SiteId
};
await AliasService.AddAliasAsync(alias);
}
}
await Log(_alias, LogLevel.Information, PermissionNames.Edit, null, "Site Saved {Site}", site);
NavigationManager.NavigateTo(NavigateUrl());
}
}
else
{
AddModuleMessage(Localizer["An Alias Specified Has Already Been Used For Another Site"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["You Must Provide A Site Name, Alias, And Default Theme/Container"], MessageType.Warning);
}
}
catch (Exception ex)
{
await Log(_alias, LogLevel.Error, string.Empty, ex, "Error Saving Site {SiteId} {Error}", _alias.SiteId, ex.Message);
AddModuleMessage(Localizer["Error Saving Site"], MessageType.Error);
}
}
}

View File

@ -4,6 +4,7 @@
@inject IAliasService AliasService
@inject ISiteService SiteService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_sites == null)
{
@ -17,12 +18,12 @@ else
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.AliasId.ToString())" ResourceKey="EditSite" /></td>
<td><ActionDialog Header="Delete Site" Message="@Localizer["Are You Sure You Wish To Delete The {0} Site?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteSite(context))" ResourceKey="DeleteSite" /></td>
<td><a href="@(_scheme + context.Name)">@context.Name</a></td>
<td><button type="button" class="btn btn-primary" @onclick="@(async () => Edit(context.Name))">@SharedLocalizer["Edit"]</button></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => Browse(context.Name))">@Localizer["Browse"]</button></td>
<td>@context.Name</td>
</Row>
</Pager>
}
@ -49,33 +50,13 @@ else
}
}
private async Task DeleteSite(Alias alias)
private void Edit(string name)
{
try
{
if (alias.SiteId != PageState.Site.SiteId || alias.TenantId != PageState.Site.TenantId)
{
SiteService.SetAlias(alias);
await SiteService.DeleteSiteAsync(alias.SiteId);
await Log(alias, LogLevel.Information, "", null, "Site Deleted {SiteId}", alias.SiteId);
NavigationManager.NavigateTo(_scheme + name + "/admin/site/?reload");
}
var aliases = await AliasService.GetAliasesAsync();
foreach (Alias a in aliases.Where(item => item.SiteId == alias.SiteId && item.TenantId == alias.TenantId))
{
await AliasService.DeleteAliasAsync(a.AliasId);
}
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["You Can Not Delete The Current Site"], MessageType.Warning);
}
}
catch (Exception ex)
{
await Log(alias, LogLevel.Error, "", ex, "Error Deleting Site {SiteId} {Error}", alias.SiteId, ex.Message);
AddModuleMessage(Localizer["Error Deleting Site"], MessageType.Error);
}
private void Browse(string name)
{
NavigationManager.NavigateTo(_scheme + name + "/?reload");
}
}

View File

@ -2,112 +2,152 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ITenantService TenantService
@inject IDatabaseService DatabaseService
@inject ISqlService SqlService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_tenants == null)
{
<p><em>Loading...</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<table class="table table-borderless">
<tr>
<td>
<Label For="tenant" HelpText="Select the tenant for the SQL server" ResourceKey="Tenant">Tenant: </Label>
</td>
<td>
<select id="teneant" class="form-control" @bind="_tenantid">
<option value="-1">&lt;@Localizer["Select Tenant"]&gt;</option>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="Select the tenant for the SQL server" ResourceKey="Tenant">Tenant: </Label>
<div class="col-sm-9">
<select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))">
<option value="-1">&lt;@Localizer["Tenant.Select"]&gt;</option>
@foreach (Tenant tenant in _tenants)
{
<option value="@tenant.TenantId">@tenant.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="sqlQeury" HelpText="Enter the query for the SQL server" ResourceKey="SqlQuery">SQL Query: </Label>
</td>
<td>
<textarea id="sqlQeury" class="form-control" @bind="@_sql" rows="5"></textarea>
</td>
</tr>
</table>
</div>
</div>
@if (_tenantid != "-1")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="database" HelpText="The database for the tenant" ResourceKey="Database">Database: </Label>
<div class="col-sm-9">
<input id="database" class="form-control" @bind="@_database" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sqlQeury" HelpText="Enter the query for the SQL server" ResourceKey="SqlQuery">SQL Query: </Label>
<div class="col-sm-9">
<textarea id="sqlQeury" class="form-control" @bind="@_sql" rows="3"></textarea>
</div>
</div>
}
</div>
<br />
<button type="button" class="btn btn-success" @onclick="Execute">@Localizer["Execute"]</button>
<br />
<br />
@if (!string.IsNullOrEmpty(_results))
@if (_results != null)
{
@((MarkupString)_results)
@if (_results.Count > 0)
{
<div class="table-responsive">
<Pager Class="table table-bordered" Items="@_results">
<Header>
@foreach (KeyValuePair<string, string> kvp in _results.First())
{
<th>@kvp.Key</th>
}
</Header>
<Row>
@foreach (KeyValuePair<string, string> kvp in context)
{
<td>@kvp.Value</td>
}
</Row>
</Pager>
</div>
}
else
{
@Localizer["Return.NoResult"]
}
<br />
<br />
}
}
@code {
private List<Tenant> _tenants;
private string _tenantid = "-1";
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _sql = string.Empty;
private string _results = string.Empty;
private List<Dictionary<string, string>> _results;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
_tenants = await TenantService.GetTenantsAsync();
try
{
_tenants = await TenantService.GetTenantsAsync();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async void TenantChanged(ChangeEventArgs e)
{
try
{
_tenantid = (string)e.Value;
var tenants = await TenantService.GetTenantsAsync();
var _databases = await DatabaseService.GetDatabasesAsync();
var tenant = tenants.Find(item => item.TenantId == int.Parse(_tenantid));
if (tenant != null)
{
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_connectionstring = tenant.DBConnectionString;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Tenant {TenantId} {Error}", _tenantid, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async Task Execute()
{
if (_tenantid != "-1" && !string.IsNullOrEmpty(_sql))
try
{
var sqlquery = new SqlQuery { TenantId = int.Parse(_tenantid), Query = _sql };
sqlquery = await SqlService.ExecuteQueryAsync(sqlquery);
_results = DisplayResults(sqlquery.Results);
}
else
{
AddModuleMessage(Localizer["You Must Select A Tenant And Provide A SQL Query"], MessageType.Warning);
}
}
private string DisplayResults(List<Dictionary<string, string>> results)
{
var table = string.Empty;
foreach (Dictionary<string, string> item in results)
{
if (table == string.Empty)
if (_tenantid != "-1" && !string.IsNullOrEmpty(_sql))
{
table = "<div class=\"table-responsive\">";
table += "<table class=\"table table-bordered\"><thead><tr>";
foreach (KeyValuePair<string, string> kvp in item)
{
table += "<th scope=\"col\">" + kvp.Key + "</th>";
}
table += "</tr></thead><tbody>";
var sqlquery = new SqlQuery { TenantId = int.Parse(_tenantid), Query = _sql };
sqlquery = await SqlService.ExecuteQueryAsync(sqlquery);
_results = sqlquery.Results;
AddModuleMessage(Localizer["Success.QueryExecuted"], MessageType.Success);
}
table += "<tr>";
foreach (KeyValuePair<string, string> kvp in item)
else
{
table += "<td>" + kvp.Value + "</td>";
AddModuleMessage(Localizer["Message.Required.Tenant"], MessageType.Warning);
}
table += "</tr>";
}
if (table != string.Empty)
catch (Exception ex)
{
table += "</tbody></table></div>";
await logger.LogError(ex, "Error Executing SQL Query {SQL} {Error}", _sql, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
else
{
table = Localizer["No Results Returned"];
}
return table;
}
}

View File

@ -3,74 +3,119 @@
@inject ISystemService SystemService
@inject IInstallationService InstallationService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless">
<tr>
<td>
<Label For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
</td>
<td>
<input id="version" class="form-control" @bind="@_version" readonly />
</td>
</tr>
<tr>
<td>
<Label For="runtime" HelpText="Blazor Runtime (Server or WebAssembly)" ResourceKey="BlazorRunime">Blazor Runtime: </Label>
</td>
<td>
<input id="runtime" class="form-control" @bind="@_runtime" readonly />
</td>
</tr>
<tr>
<td>
<Label For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="ClrVerion">CLR Version: </Label>
</td>
<td>
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
</td>
</tr>
<tr>
<td>
<Label For="osversion" HelpText="Operating System Version" ResourceKey="OsVersion">OS Version: </Label>
</td>
<td>
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
</td>
</tr>
<tr>
<td>
<Label For="serverpath" HelpText="Server Path" ResourceKey="ServerPath">Server Path: </Label>
</td>
<td>
<input id="serverpath" class="form-control" @bind="@_serverpath" readonly />
</td>
</tr>
<tr>
<td>
<Label For="servertime" HelpText="Server Time" ResourceKey="ServerTime">Server Time: </Label>
</td>
<td>
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
</td>
</tr>
</table>
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access Framework API"]</a>&nbsp;
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
<TabStrip>
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
<div class="col-sm-9">
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
<div class="col-sm-9">
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="serverpath" HelpText="Server Path" ResourceKey="ServerPath">Server Path: </Label>
<div class="col-sm-9">
<input id="serverpath" class="form-control" @bind="@_serverpath" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="servertime" HelpText="Server Time" ResourceKey="ServerTime">Server Time: </Label>
<div class="col-sm-9">
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
<div class="col-sm-9">
<input id="installationid" class="form-control" @bind="@_installationid" readonly />
</div>
</div>
</div>
<br /><br />
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
</TabPanel>
<TabPanel Name="Options" Heading="Options" ResourceKey="Options">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="detailederrors" HelpText="Specify If Detailed Errors Are Enabled For Blazor. This Option Should Not Not Be Enabled In Production." ResourceKey="DetailedErrors">Detailed Errors? </Label>
<div class="col-sm-9">
<select id="detailederrors" class="form-select" @bind="@_detailederrors">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logginglevel" HelpText="The Minimum Logging Level For The Event Log. This Option Can Be Used To Control The Volume Of Items Stored In Your Event Log." ResourceKey="LoggingLevel">Logging Level: </Label>
<div class="col-sm-9">
<select id="logginglevel" class="form-select" @bind="@_logginglevel">
<option value="Trace">@Localizer["Trace"]</option>
<option value="Debug">@Localizer["Debug"]</option>
<option value="Information">@Localizer["Information"]</option>
<option value="Warning">@Localizer["Warning"]</option>
<option value="Error">@Localizer["Error"]</option>
<option value="Critical">@Localizer["Critical"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
<div class="col-sm-9">
<select id="swagger" class="form-select" @bind="@_swagger">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</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>
<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>
</div>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>&nbsp;
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>&nbsp;
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
</TabPanel>
</TabStrip>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
private string _version = string.Empty;
private string _runtime = string.Empty;
private string _clrversion = string.Empty;
private string _osversion = string.Empty;
private string _serverpath = string.Empty;
private string _servertime = string.Empty;
private string _installationid = string.Empty;
private string _detailederrors = string.Empty;
private string _logginglevel = string.Empty;
private string _swagger = string.Empty;
private string _packageservice = string.Empty;
protected override async Task OnInitializedAsync()
{
_version = Constants.Version;
_runtime = PageState.Runtime.ToString();
Dictionary<string, string> systeminfo = await SystemService.GetSystemInfoAsync();
if (systeminfo != null)
@ -79,6 +124,31 @@
_osversion = systeminfo["osversion"];
_serverpath = systeminfo["serverpath"];
_servertime = systeminfo["servertime"];
_installationid = systeminfo["installationid"];
_detailederrors = systeminfo["detailederrors"];
_logginglevel = systeminfo["logginglevel"];
_swagger = systeminfo["swagger"];
_packageservice = systeminfo["packageservice"];
}
}
private async Task SaveConfig()
{
try
{
var settings = new Dictionary<string, string>();
settings.Add("detailederrors", _detailederrors);
settings.Add("logginglevel", _logginglevel);
settings.Add("swagger", _swagger);
settings.Add("packageservice", _packageservice);
await SystemService.UpdateSystemInfoAsync(settings);
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Configuration");
AddModuleMessage(Localizer["Error.UpdateConfig"], MessageType.Error);
}
}
@ -88,7 +158,7 @@
{
ShowProgressIndicator();
var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(""), 10);
await interop.RedirectBrowser(NavigateUrl(""), 20);
await InstallationService.RestartAsync();
}
catch (Exception ex)

View File

@ -5,50 +5,122 @@
@inject IThemeService ThemeService
@inject IPackageService PackageService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_packages != null)
{
<TabStrip>
@if (_packages.Count > 0)
<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>
</div>
</div>
@if (_packages != null)
{
<TabPanel Name="Download" ResourceKey="Download">
<ModuleMessage Type="MessageType.Info" Message="Download one or more themes from the list below. Once you are ready click Install to complete the installation."></ModuleMessage>
if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Header>
<th>@Localizer["Name"]</th>
<th>@Localizer["Version"]</th>
<th style="width: 1px;"></th>
</Header>
<Row>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadTheme(context.PackageId, context.Version))>@Localizer["Download"]</button>
<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)(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>
</TabPanel>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
}
<TabPanel Name="Upload" ResourceKey="Upload">
<table class="table table-borderless">
<tr>
<td>
<Label HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation." ResourceKey="Theme">Theme: </Label>
</td>
<td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Themes" UploadMultiple="@true" />
</td>
</tr>
</table>
</TabPanel>
</TabStrip>
</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" />
</div>
</div>
</div>
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="InstallThemes">@Localizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
@if (_productname != "")
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
{
@((MarkupString)_license)
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -56,21 +128,118 @@
{
try
{
var themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme");
await LoadThemes();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Package.Load"], MessageType.Error);
}
}
private async Task LoadThemes()
{
var themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "");
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (themes.Exists(item => Utilities.GetTypeName(item.ThemeName) == package.PackageId))
if (themes.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
}
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadThemes();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Packages"], MessageType.Error);
await logger.LogError(ex, "Error On PriceChanged");
}
}
private async Task Search()
{
try
{
await LoadThemes();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
private async Task Reset()
{
try
{
_search = "";
await LoadThemes();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
}
private void HideModal()
{
_productname = "";
_license = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Theme.Download"], MessageType.Success);
_productname = "";
_license = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
}
}
@ -79,27 +248,11 @@
try
{
await ThemeService.InstallThemesAsync();
AddModuleMessage(Localizer["Theme Installed Successfully. You Must <a href=\"{0}\">Restart</a> Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Theme");
}
}
private async Task DownloadTheme(string packageid, string version)
{
try
{
await PackageService.DownloadPackageAsync(packageid, version, "Themes");
await logger.LogInformation("Theme {ThemeName} {Version} Downloaded Successfully", packageid, version);
AddModuleMessage(Localizer["Themes Downloaded Successfully. Click Install To Complete Installation."], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Module {ThemeName} {Version}", packageid, version);
AddModuleMessage(Localizer["Error Downloading Theme"], MessageType.Error);
}
}
}

View File

@ -0,0 +1,150 @@
@namespace Oqtane.Modules.Admin.Themes
@inherits ModuleBase
@using System.Text.RegularExpressions
@inject NavigationManager NavigationManager
@inject IThemeService ThemeService
@inject IModuleService ModuleService
@inject IPageModuleService PageModuleService
@inject ISettingService SettingService
@inject IStringLocalizer<Create> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_templates != null)
{
<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 theme. 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" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="module" HelpText="Enter a name for this theme. It should not contain spaces or punctuation." ResourceKey="ThemeName">Theme Name: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_theme" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="template" HelpText="Select a theme template. Templates are located in the wwwroot/Themes/Templates folder on the server." ResourceKey="Template">Template: </Label>
<div class="col-sm-9">
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))">
<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">
@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 theme will be created" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_location" readonly />
</div>
</div>
}
</div>
<br />
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
}
@code {
private string _owner = string.Empty;
private string _theme = string.Empty;
private List<Template> _templates;
private string _template = "-";
private string[] _versions;
private string _reference = Constants.Version;
private string _minversion = "2.0.0";
private string _location = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override void OnInitialized()
{
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
}
protected override async Task OnParametersSetAsync()
{
try
{
_templates = await ThemeService.GetThemeTemplatesAsync();
_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 Theme Creator");
}
}
private async Task CreateTheme()
{
try
{
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
{
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference };
theme = await ThemeService.CreateThemeAsync(theme);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Required.ValidName"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Creating Theme");
}
}
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
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 != "" && _theme != "" && _template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _theme;
}
StateHasChanged();
}
}

View File

@ -5,37 +5,44 @@
@inject IThemeService ThemeService
@inject IPackageService PackageService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_themes == null)
{
<p><em>Loading...</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<ActionLink Action="Add" Text="Install Theme" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" />
<Pager Items="@_themes">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th scope="col">@Localizer["Name"]</th>
<th scope="col">@Localizer["Version"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th>&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td>
<td>
@if (context.AssemblyName != "Oqtane.Client")
{
<ActionDialog Header="Delete Theme" Message="@Localizer["Are You Sure You Wish To Delete The {0} Theme?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
}
{
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
}
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if (UpgradeAvailable(context.ThemeName, context.Version))
@((MarkupString)PurchaseLink(context.PackageName))
</td>
<td>
@if (UpgradeAvailable(context.PackageName, context.Version))
{
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.ThemeName, context.Version))>@Localizer["Upgrade"]</button>
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, context.Version))>@SharedLocalizer["Upgrade"]</button>
}
</td>
<td></td>
@ -61,17 +68,38 @@ else
if (_themes == null)
{
await logger.LogError(ex, "Error Loading Themes {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Themes"], MessageType.Error);
AddModuleMessage(Localizer["Error.Theme.Load"], MessageType.Error);
}
}
}
private bool UpgradeAvailable(string themename, string version)
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>";
}
}
}
}
return link;
}
private bool UpgradeAvailable(string packagename, string version)
{
var upgradeavailable = false;
if (_packages != null)
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == Utilities.GetTypeName(themename)).FirstOrDefault();
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
@ -80,19 +108,19 @@ else
return upgradeavailable;
}
private async Task DownloadTheme(string themename, string version)
private async Task DownloadTheme(string packagename, string version)
{
try
{
await PackageService.DownloadPackageAsync(themename, version, "Themes");
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", themename, version);
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version);
await ThemeService.InstallThemesAsync();
AddModuleMessage(Localizer["Theme Installed Successfully. You Must <a href=\"{0}\">Restart</a> Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Theme {ThemeName} {Version} {Error}", themename, version, ex.Message);
AddModuleMessage(Localizer["Error Downloading Theme"], MessageType.Error);
await logger.LogError(ex, "Error Downloading Theme {ThemeName} {Version} {Error}", packagename, version, ex.Message);
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
}
}
@ -101,13 +129,13 @@ else
try
{
await ThemeService.DeleteThemeAsync(Theme.ThemeName);
AddModuleMessage(Localizer["Theme Deleted Successfully"], MessageType.Success);
StateHasChanged();
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Theme {Theme} {Error}", Theme, ex.Message);
AddModuleMessage(Localizer["Error Deleting Theme"], MessageType.Error);
AddModuleMessage(Localizer["Error.Theme.Delete"], MessageType.Error);
}
}
}

View File

@ -4,66 +4,53 @@
@inject IThemeService ThemeService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<View> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
</td>
<td>
<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 />
</td>
</tr>
<tr>
<td>
<Label For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
</td>
<td>
</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 />
</td>
</tr>
<tr>
<td>
<Label For="version" HelpText="The version of the thene" ResourceKey="Version">Version: </Label>
</td>
<td>
</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 />
</td>
</tr>
<tr>
<td>
<Label For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
</td>
<td>
</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 />
</td>
</tr>
<tr>
<td>
<Label For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
</td>
<td>
</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 />
</td>
</tr>
<tr>
<td>
<Label For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
</td>
<td>
</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 />
</td>
</tr>
<tr>
<td>
<Label For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
</td>
<td>
</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>
</td>
</tr>
</table>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
</div>
</div>
</div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _themeName = "";
@ -96,7 +83,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
AddModuleMessage(Localizer["Error Loading Theme"], MessageType.Error);
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
}
}
}

View File

@ -5,36 +5,34 @@
@inject IPackageService PackageService
@inject IInstallationService InstallationService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_package != null)
{
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
@if (_upgradeavailable)
{
<ModuleMessage Type="MessageType.Info" Message="Select The Upgrade Button To Install a New Framework Version"></ModuleMessage>
<button type="button" class="btn btn-success" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@Localizer["Upgrade To"] @_package.Version</button>
}
else
{
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage>
}
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<table class="table table-borderless">
<tr>
<td>
<Label HelpText="Upload a framework package and select Install to complete the installation" ResourceKey="Framework">Framework: </Label>
</td>
<td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Framework" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="Upgrade">@Localizer["Install"]</button>
</TabPanel>
</TabStrip>
}
<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" />
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
</TabPanel>
</TabStrip>
@code {
private Package _package;
@ -46,10 +44,10 @@
{
try
{
List<Package> packages = await PackageService.GetPackagesAsync("framework");
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
if (packages != null)
{
_package = packages.FirstOrDefault();
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
if (_package != null)
{
_upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0);
@ -70,6 +68,7 @@
{
try
{
AddModuleMessage(Localizer["Info.Upgrade.Wait"], MessageType.Info);
ShowProgressIndicator();
var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 10);
@ -78,7 +77,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Executing Upgrade {Error}", ex.Message);
AddModuleMessage(Localizer["Error Executing Upgrade"], MessageType.Error);
AddModuleMessage(Localizer["Error.Upgrade.Execute"], MessageType.Error);
}
}
@ -86,16 +85,14 @@
{
try
{
await PackageService.DownloadPackageAsync(packageid, version, "Framework");
ShowProgressIndicator();
var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 10);
await InstallationService.Upgrade();
await PackageService.DownloadPackageAsync(packageid, version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version, Constants.PackagesFolder);
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Framework {Error}", ex.Message);
AddModuleMessage(Localizer["Error Downloading Framework"], MessageType.Error);
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
}
}
}

View File

@ -1,73 +1,69 @@
@namespace Oqtane.Modules.Admin.UserProfile
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@namespace Oqtane.Modules.Admin.UserProfile
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject INotificationService NotificationService
@inject IStringLocalizer<Add> Localizer
@if (PageState.User != null)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label>
</td>
<td>
<input id="to" class="form-control" @bind="@username" />
</td>
</tr>
<tr>
<td>
<Label For="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label>
</td>
<td>
<input id="subject" class="form-control" @bind="@subject" />
</td>
</tr>
<tr>
<td>
<Label For="message" HelpText="Enter the message" ResourceKey="Message">Message: </Label>
</td>
<td>
<textarea id="message" class="form-control" @bind="@body" rows="5" />
</td>
</tr>
</table>
<button type="button" class="btn btn-primary" @onclick="Send">@Localizer["Send"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
}
@code {
private string username = "";
private string subject = "";
private string body = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override string Title => "Send Notification";
private async Task Send()
{
try
{
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user != null)
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null)
{
<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>
<div class="col-sm-9">
<input id="to" class="form-control" @bind="@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>
<div class="col-sm-9">
<input id="subject" class="form-control" @bind="@subject" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="message" HelpText="Enter the message" ResourceKey="Message">Message: </Label>
<div class="col-sm-9">
<textarea id="message" class="form-control" @bind="@body" rows="5" />
</div>
</div>
</div>
<br/>
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
}
@code {
private string username = "";
private string subject = "";
private string body = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override string Title => "Send Notification";
private async Task Send()
{
try
{
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user != null)
{
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, null);
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {Notification}", notification);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["User Does Not Exist. Please Verify That The Username Provided Is Correct."], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
AddModuleMessage(Localizer["Error Adding Notification"], MessageType.Error);
}
}
}
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Notification.Add"], MessageType.Error);
}
}
}

View File

@ -5,11 +5,14 @@
@inject IProfileService ProfileService
@inject ISettingService SettingService
@inject INotificationService NotificationService
@inject IFileService FileService
@inject IFolderService FolderService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null && photofileid != -1)
@if (PageState.User != null && photo != null)
{
<img src="@(ContentUrl(photofileid))" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
<img src="@ImageUrl(photofileid, 400, 400, "crop")" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
}
else
{
@ -17,119 +20,106 @@ else
}
<TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity">
@if (PageState.User != null)
@if (profiles != null && settings != null)
{
<table class="table table-borderless">
<tr>
<td>
<label for="Name" class="control-label">@Localizer["Username:"] </label>
</td>
<td>
<input class="form-control" @bind="@username" readonly />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">@Localizer["Password:"] </label>
</td>
<td>
<input type="password" class="form-control" @bind="@password" autocomplete="new-password" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">@Localizer["Confirm Password:"] </label>
</td>
<td>
<input type="password" class="form-control" @bind="@confirm" autocomplete="new-password" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">@Localizer["Email:"] </label>
</td>
<td>
<input class="form-control" @bind="@email" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">@Localizer["Full Name:"] </label>
</td>
<td>
<input class="form-control" @bind="@displayname" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">@Localizer["Photo:"] </label>
</td>
<td>
<FileManager FileId="@photofileid" @ref="filemanager" />
</td>
</tr>
</table>
<button type="button" class="btn btn-primary" @onclick="Save">@Localizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button>
<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>
<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="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">
<input id="password" type="password" class="form-control" @bind="@password" autocomplete="new-password" />
</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">
<input id="confirm" type="password" class="form-control" @bind="@confirm" autocomplete="new-password" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@email" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@displayname" />
</div>
</div>
<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" />
</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)
{
<table class="table table-borderless">
@foreach (Profile profile in profiles)
{
var p = profile;
if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
if (p.Category != category)
<div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles)
{
<tr>
<th colspan="2" style="text-align: center;">
@p.Category
</th>
</tr>
category = p.Category;
}
<tr>
<td>
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label>
</td>
<td>
@if (!string.IsNullOrEmpty(p.Options))
var p = profile;
if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
if (p.Category != category)
{
<select id="@p.Name" class="form-control" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
<div class="col text-center pb-2">
@p.Category
</div>
category = p.Category;
}
<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))
{
@if(GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
<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)
{
<option value="@option" selected>@option</option>
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
}
else
{
<option value="@option">@option</option>
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
}
}
</select>
}
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))" />
}
}
</td>
</tr>
}
}
</table>
<button type="button" class="btn btn-primary" @onclick="Save">@Localizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button>
</div>
</div>
}
}
</div>
</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">
@ -157,13 +147,14 @@ else
<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", "");
} }
@{
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>
@ -189,22 +180,23 @@ else
<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", "");
} }
@{
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>
}
<br /><hr />
<select class="form-control" @onchange="(e => FilterChanged(e))">
<select class="form-select" @onchange="(e => FilterChanged(e))">
<option value="to">@Localizer["Inbox"]</option>
<option value="from">@Localizer["Sent Items"]</option>
<option value="from">@Localizer["Items.Sent"]</option>
</select>
}
</TabPanel>
@ -217,7 +209,9 @@ else
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;
@ -226,7 +220,7 @@ else
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
try
{
@ -236,9 +230,22 @@ else
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 (PageState.User.PhotoFileId != null)
{
photofileid = PageState.User.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
@ -248,13 +255,13 @@ else
}
else
{
AddModuleMessage(Localizer["Current User Is Not Logged In"], MessageType.Warning);
AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading User Profile"], MessageType.Error);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
}
}
@ -280,33 +287,32 @@ else
user.Password = password;
user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname);
user.PhotoFileId = null;
photofileid = filemanager.GetFileId();
if (photofileid != -1)
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = photofileid;
user.PhotoFileId = null;
}
await UserService.UpdateUserAsync(user);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
AddModuleMessage(Localizer["User Profile Updated Successfully"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Passwords Entered Do Not Match"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["You Must Provide A Username and Email Address As Well As All Required Profile Information"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error Saving User Profile"], MessageType.Error);
AddModuleMessage(Localizer["Error.Profile.Save"], MessageType.Error);
}
}

View File

@ -1,201 +1,198 @@
@namespace Oqtane.Modules.Admin.UserProfile
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@namespace Oqtane.Modules.Admin.UserProfile
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject INotificationService NotificationService
@inject IStringLocalizer<View> Localizer
@if (PageState.User != null)
{
<table class="table table-borderless">
<tr>
<td>
<label class="control-label">@Localizer["Title:"] </label>
</td>
@if (title == "From")
{
<td>
<input class="form-control" @bind="@username" readonly />
</td>
}
@if (title == "To")
{
<td>
<input class="form-control" @bind="@username" />
</td>
}
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Subject:"] </label>
</td>
@if (title == "From")
{
<td>
<input class="form-control" @bind="@subject" readonly />
</td>
}
@if (title == "To")
{
<td>
<input class="form-control" @bind="@subject" />
</td>
}
</tr>
@if (title == "From")
{
<tr>
<td>
<label class="control-label">@Localizer["Date:"] </label>
</td>
<td>
<input class="form-control" @bind="@createdon" readonly />
</td>
</tr>
}
@if (title == "From")
{
<tr>
<td>
<label class="control-label">@Localizer["Message:"] </label>
</td>
<td>
<textarea class="form-control" @bind="@body" rows="5" readonly />
</td>
</tr>
}
@if (title == "To")
{
<tr>
<td>
<label class="control-label">@Localizer["Message:"] </label>
</td>
<td>
<textarea class="form-control" @bind="@body" rows="5" />
</td>
</tr>
}
</table>
@if (reply != string.Empty)
{
<button type="button" class="btn btn-primary" @onclick="Send">@Localizer["Send"]</button>
}
else
{
if (title == "From")
{
@inject IStringLocalizer<View> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@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")
{
<div class="row mb-1 align-items-center">
<label class="col-sm-3">@Localizer["Date"] </label>
<div class="col-sm-9">
<input class="form-control" @bind="@createdon" 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>
}
@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>
@if (reply != string.Empty)
{
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
}
else
{
if (title == "From")
{
<button type="button" class="btn btn-primary" @onclick="Reply">@Localizer["Reply"]</button>
}
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<br />
<br />
@if (title == "To")
{
<div class="control-group">
<label class="control-label">@Localizer["Original Message"] </label>
<textarea class="form-control" @bind="@reply" rows="5" readonly />
</div>
}
}
@code {
private int notificationid;
private string title = string.Empty;
private string username = "";
private string subject = string.Empty;
private string createdon = string.Empty;
private string body = string.Empty;
private string reply = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override string Title => "View Notification";
protected override async Task OnInitializedAsync()
{
try
{
notificationid = Int32.Parse(PageState.QueryString["id"]);
Notification notification = await NotificationService.GetNotificationAsync(notificationid);
if (notification != null)
{
int userid = -1;
if (notification.ToUserId == PageState.User.UserId)
{
title = "From";
if (notification.FromUserId != null)
{
userid = notification.FromUserId.Value;
}
}
else
{
title = "To";
if (notification.ToUserId != null)
{
userid = notification.ToUserId.Value;
}
}
if (userid != -1)
{
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null)
{
username = user.Username;
}
}
if (username == "")
{
username = "System";
}
subject = notification.Subject;
createdon = notification.CreatedOn.ToString();
body = notification.Body;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Users"], MessageType.Error);
}
}
private void Reply()
{
title = "To";
if (!subject.Contains("RE:"))
{
subject = "RE: " + subject;
}
reply = body;
body = "\n\n____________________________________________\nSent: " + createdon + "\nSubject: " + subject + "\n\n" + body;
StateHasChanged();
}
private async Task Send()
{
try
{
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user != null)
}
}
<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>
}
}
@code {
private int notificationid;
private string title = string.Empty;
private string username = "";
private string subject = string.Empty;
private string createdon = string.Empty;
private string body = string.Empty;
private string reply = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override string Title => "View Notification";
protected override async Task OnInitializedAsync()
{
try
{
notificationid = Int32.Parse(PageState.QueryString["id"]);
Notification notification = await NotificationService.GetNotificationAsync(notificationid);
if (notification != null)
{
int userid = -1;
if (notification.ToUserId == PageState.User.UserId)
{
title = "From";
if (notification.FromUserId != null)
{
userid = notification.FromUserId.Value;
}
}
else
{
title = "To";
if (notification.ToUserId != null)
{
userid = notification.ToUserId.Value;
}
}
if (userid != -1)
{
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null)
{
username = user.Username;
}
}
if (username == "")
{
username = "System";
}
subject = notification.Subject;
createdon = notification.CreatedOn.ToString();
body = notification.Body;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
}
private void Reply()
{
title = "To";
if (!subject.Contains("RE:"))
{
subject = "RE: " + subject;
}
reply = body;
body = "\n\n____________________________________________\nSent: " + createdon + "\nSubject: " + subject + "\n\n" + body;
StateHasChanged();
}
private async Task Send()
{
try
{
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user != null)
{
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid);
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {Notification}", notification);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["User Does Not Exist. Please Verify That The Username Provided Is Correct."], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
AddModuleMessage(Localizer["Error Adding Notification"], MessageType.Error);
}
}
}
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Notification.Add"], MessageType.Error);
}
}
}

View File

@ -5,94 +5,86 @@
@inject IProfileService ProfileService
@inject ISettingService SettingService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null)
{
<table class="table table-borderless">
<tr>
<td>
<label class="control-label">@Localizer["Username:"] </label>
</td>
<td>
<input class="form-control" @bind="@username" />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Password:"] </label>
</td>
<td>
<input type="password" class="form-control" @bind="@password" />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Confirm Password:"] </label>
</td>
<td>
<input type="password" class="form-control" @bind="@confirm" />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Email:"] </label>
</td>
<td>
<input class="form-control" @bind="@email" />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Full Name:"] </label>
</td>
<td>
<input class="form-control" @bind="@displayname" />
</td>
</tr>
</table>
<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">
<input id="password" type="password" class="form-control" @bind="@password" />
</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">
<input id="confirm" type="password" class="form-control" @bind="@confirm" />
</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)
{
<table class="table table-borderless">
@foreach (Profile profile in profiles)
{
var p = profile;
if (p.Category != category)
<div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles)
{
<tr>
<th colspan="2" style="text-align: center;">
@p.Category
</th>
</tr>
category = p.Category;
var p = profile;
if (p.Category != category)
{
<div class="col text-center pb-2">
<strong>@p.Category</strong>
</div>
category = p.Category;
}
<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)
{
<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))" />
}
</div>
</div>
}
<tr>
<td>
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label>
</td>
<td>
@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))" />
}
</td>
</tr>
}
</table>
</div>
</div>
}
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-primary" @onclick="SaveUser">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<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;
@ -116,7 +108,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading User Profile"], MessageType.Error);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
}
}
@ -131,42 +123,50 @@
{
if (password == confirm)
{
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
user = await UserService.AddUserAsync(user);
if (user != null)
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user == null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Created {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
user = await UserService.AddUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Created {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", username, email);
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
}
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", username, email);
AddModuleMessage(Localizer["Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use."], MessageType.Error);
AddModuleMessage(Localizer["Message.Username.Exists"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Passwords Entered Do Not Match"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["You Must Provide A Username, Password, Email Address And All Required Profile Information"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", username, email, ex.Message);
AddModuleMessage(Localizer["Error Adding User"], MessageType.Error);
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
}
}

View File

@ -4,11 +4,13 @@
@inject IUserService UserService
@inject IProfileService ProfileService
@inject ISettingService SettingService
@inject IFileService FileService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null && photofileid != -1)
@if (PageState.User != null && photo != null)
{
<img src="@(ContentUrl(photofileid))" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
<img src="@photo.Url" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
}
else
{
@ -18,108 +20,112 @@ else
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null)
{
<table class="table table-borderless">
<tr>
<td>
<label class="control-label">@Localizer["Username:"] </label>
</td>
<td>
<input class="form-control" @bind="@username" readonly />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Password:"] </label>
</td>
<td>
<input type="password" class="form-control" @bind="@password" />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Confirm Password:"] </label>
</td>
<td>
<input type="password" class="form-control" @bind="@confirm" />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Email:"] </label>
</td>
<td>
<input class="form-control" @bind="@email" />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Full Name:"] </label>
</td>
<td>
<input class="form-control" @bind="@displayname" />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Photo:"] </label>
</td>
<td>
<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>
<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="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@password" />
</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">
<input id="confirm" type="password" class="form-control" @bind="@confirm" />
</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="@photofileid.ToString()" HelpText="A photo of the user" ResourceKey="Photo"></Label>
<div class="col-sm-9">
<FileManager FileId="@photofileid" @ref="filemanager" />
</td>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Is Deleted?"] </label>
</td>
<td>
<select class="form-control" @bind="@isdeleted">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</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">
<select id="isdeleted" class="form-select" @bind="@isdeleted">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
</table>
</div>
</div>
</div>
}
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null)
{
<table class="table table-borderless">
@foreach (Profile profile in profiles)
{
var p = profile;
if (p.Category != category)
<div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles)
{
<tr>
<th colspan="2" style="text-align: center;">
@p.Category
</th>
</tr>
category = p.Category;
var p = profile;
if (p.Category != category)
{
<div class="col text-center pb-2">
<strong>@p.Category</strong>
</div>
category = p.Category;
}
<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))
{
<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)
{
<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))" />
}
}
</div>
</div>
}
<tr>
<td>
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label>
</td>
<td>
@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))" />
}
</td>
</tr>
}
</table>
</div>
</div>
}
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-primary" @onclick="SaveUser">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<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>
@ -133,6 +139,7 @@ else
private string displayname = string.Empty;
private FileManager filemanager;
private int photofileid = -1;
private File photo = null;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
@ -146,39 +153,46 @@ else
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
try
{
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)
// OnParametersSetAsync is called when the edit modal is closed - in which case there is no id parameter
if (PageState.QueryString.ContainsKey("id"))
{
username = user.Username;
email = user.Email;
displayname = user.DisplayName;
if (user.PhotoFileId != null)
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)
{
photofileid = user.PhotoFileId.Value;
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;
}
settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy;
deletedon = user.DeletedOn;
isdeleted = user.IsDeleted.ToString();
}
settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy;
deletedon = user.DeletedOn;
isdeleted = user.IsDeleted.ToString();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error Loading User"], MessageType.Error);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
}
@ -200,11 +214,10 @@ else
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
photofileid = filemanager.GetFileId();
if (photofileid != -1)
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = photofileid;
user.PhotoFileId = null;
}
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
@ -217,18 +230,18 @@ else
}
else
{
AddModuleMessage(Localizer["Passwords Entered Do Not Match"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["You Must Provide A Username, Password, Email Address, And All Required Profile Information"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", username, email, ex.Message);
AddModuleMessage(Localizer["Error Saving User"], MessageType.Error);
AddModuleMessage(Localizer["Error.User.Save"], MessageType.Error);
}
}

View File

@ -4,34 +4,42 @@
@inject IUserService UserService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (userroles == null)
{
<p>
<em>@Localizer["Loading..."]</em>
<em>@SharedLocalizer["Loading"]</em>
</p>
}
else
{
<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" />
<div class="d-flex p-1">
<input class="form-control mr-4" @bind="@_search" /><button class="btn btn-outline-primary ml-1" @onclick="OnSearch">@Localizer["Search"]</button>
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" />
</div>
<div class="col-sm-4">
<input class="form-control" @bind="@_search" />
</div>
<div class="col-sm-4">
<button class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
</div>
</div>
</div>
<Pager Items="@userroles">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td>
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" />
</td>
<td>
<ActionDialog Header="Delete User" Message="@Localizer["Are You Sure You Wish To Delete {0}?", context.User.DisplayName]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" ResourceKey="DeleteUser" />
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
</td>
<td>
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
@ -57,19 +65,19 @@ else
private List<UserRole> Search(string search)
{
var results = allroles.Where(item => item.Role.Name == RoleNames.Registered || (item.Role.Name == RoleNames.Host && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)));
if (string.IsNullOrEmpty(_search))
{
return allroles.Where(item => item.Role.Name == RoleNames.Registered).ToList();
results = results.Where(item =>
(
item.User.Username.Contains(search, StringComparison.OrdinalIgnoreCase) ||
item.User.Email.Contains(search, StringComparison.OrdinalIgnoreCase) ||
item.User.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase)
)
);
}
return allroles
.Where(item => item.Role.Name == RoleNames.Registered &&
(
item.User.Username.Contains(search, StringComparison.OrdinalIgnoreCase) ||
item.User.Email.Contains(search, StringComparison.OrdinalIgnoreCase) ||
item.User.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase)
)
)
.ToList();
return results.ToList();
}
private async Task OnSearch()
@ -85,8 +93,10 @@ else
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
if (user != null)
{
await UserService.DeleteUserAsync(user.UserId);
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
await logger.LogInformation("User Deleted {User}", UserRole.User);
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
userroles = Search(_search);
StateHasChanged();
}
}

View File

@ -4,70 +4,66 @@
@inject IUserService UserService
@inject IUserRoleService UserRoleService
@inject IStringLocalizer<Roles> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (userroles == null)
{
<p><em>@Localizer["Loading..."]</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<table class="table table-borderless">
<tr>
<td>
<Label For="user" HelpText="The user you are assigning roles to" ResourceKey="User">User: </Label>
</td>
<td>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="user" HelpText="The user you are assigning roles to" ResourceKey="User">User: </Label>
<div class="col-sm-9">
<input id="user" class="form-control" @bind="@name" disabled />
</td>
</tr>
<tr>
<td>
<Label For="role" HelpText="Select a role" ResourceKey="Role">Role: </Label>
</td>
<td>
<select id="role" class="form-control" @bind="@roleid">
<option value="-1">&lt;@Localizer["Select Role"]&gt;</option>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="role" HelpText="Select a role" ResourceKey="Role">Role: </Label>
<div class="col-sm-9">
<select id="role" class="form-select" @bind="@roleid">
<option value="-1">&lt;@Localizer["Role.Select"]&gt;</option>
@foreach (Role role in roles)
{
<option value="@(role.RoleId)">@role.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
</td>
<td>
</div>
</div>
<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" />
</td>
</tr>
<tr>
<td>
<Label For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
</td>
<td>
</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" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
</div>
</div>
</div>
<br />
<br />
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<hr class="app-rule" />
<p align="center">
<Pager Items="@userroles">
<Header>
<th>@Localizer["Roles"]</th>
<th>&nbsp;</th>
<th>@Localizer["Roles"]</th>
<th>@Localizer["Effective"]</th>
<th>@Localizer["Expiry"]</th>
<th>&nbsp;</th>
</Header>
<Row>
<td>@context.Role.Name</td>
<td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td>
<td>
@if (context.Role.Name != RoleNames.Registered)
{
<button type="button" class="btn btn-danger" @onclick=@(async () => await DeleteUserRole(context.UserRoleId))>@Localizer["Delete"]</button>
}
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
</td>
</Row>
</Pager>
@ -92,13 +88,21 @@ else
userid = Int32.Parse(PageState.QueryString["id"]);
User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
name = user.DisplayName;
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
roles = roles.Where(item => item.Name != RoleNames.Everyone).ToList();
}
else
{
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
}
await GetUserRoles();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Roles {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Roles"], MessageType.Error);
AddModuleMessage(Localizer["Error.LoadRole"], MessageType.Error);
}
}
@ -112,7 +116,7 @@ else
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Roles {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error Loading User Roles"], MessageType.Error);
AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error);
}
}
@ -171,19 +175,20 @@ else
await UserRoleService.AddUserRoleAsync(userrole);
}
await GetUserRoles();
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
AddModuleMessage(Localizer["User Assigned To Role"], MessageType.Success);
AddModuleMessage(Localizer["Success.User.AssignRole"], MessageType.Success);
await GetUserRoles();
StateHasChanged();
}
else
{
AddModuleMessage(Localizer["You Must Select A Role"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.Role"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving User Roles {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error Saving User Roles"], MessageType.Error);
AddModuleMessage(Localizer["Error.User.SaveRole"], MessageType.Error);
}
}
@ -192,14 +197,15 @@ else
try
{
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
await GetUserRoles();
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
AddModuleMessage(Localizer["User Removed From Role"], MessageType.Success);
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
await GetUserRoles();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message);
AddModuleMessage(Localizer["Error Removing User From Role"], MessageType.Error);
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
}
}
}

View File

@ -9,7 +9,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@Header</h5>
<button type="button" class="close" @onclick="DisplayModal" aria-label="Close">&times;</button>
<button type="button" class="btn-close" aria-label="Close" @onclick="DisplayModal"></button>
</div>
<div class="modal-body">
<p>@Message</p>

View File

@ -35,6 +35,9 @@
[Parameter]
public string Style { get; set; }
[Parameter]
public string DateTimeFormat { get; set; } = "MMM dd yyyy HH:mm:ss";
protected override void OnParametersSet()
{
_text = string.Empty;
@ -44,12 +47,12 @@
if (!String.IsNullOrEmpty(CreatedBy))
{
_text += $" {Localizer["by"]} <b>{CreatedBy}</b>";
_text += $" {Localizer["By"]} <b>{CreatedBy}</b>";
}
if (CreatedOn != null)
{
_text += $" {Localizer["on"]} <b>{CreatedOn.Value.ToString("MMM dd yyyy HH:mm:ss")}</b>";
_text += $" {Localizer["On"]} <b>{CreatedOn.Value.ToString(DateTimeFormat)}</b>";
}
_text += "</p>";
@ -57,7 +60,7 @@
if (!String.IsNullOrEmpty(ModifiedBy) || ModifiedOn.HasValue)
{
_text += $"<p style=\"{Style}\">{Localizer["Last modified"]} ";
_text += $"<p style=\"{Style}\">{Localizer["LastModified"]} ";
if (!String.IsNullOrEmpty(ModifiedBy))
{
@ -66,7 +69,7 @@
if (ModifiedOn != null)
{
_text += $" {Localizer["on"]} <b>{ModifiedOn.Value.ToString("MMM dd yyyy HH:mm:ss")}</b>";
_text += $" {Localizer["on"]} <b>{ModifiedOn.Value.ToString(DateTimeFormat)}</b>";
}
_text += "</p>";
@ -78,12 +81,12 @@
if (!String.IsNullOrEmpty(DeletedBy))
{
_text += $" {Localizer["by"]} <b>{DeletedBy}</b>";
_text += $" {Localizer["By"]} <b>{DeletedBy}</b>";
}
if (DeletedOn != null)
{
_text += $" {Localizer["on"]} <b>{DeletedOn.Value.ToString("MMM dd yyyy HH:mm:ss")}</b>";
_text += $" {Localizer["On"]} <b>{DeletedOn.Value.ToString(DateTimeFormat)}</b>";
}
_text += "</p>";

View File

@ -3,75 +3,69 @@
@inject IFolderService FolderService
@inject IFileService FileService
@inject IStringLocalizer<FileManager> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_folders != null)
{
<div id="@Id" class="container-fluid px-0">
<div class="row">
<div class="col">
@if (ShowFolders || FolderId <= 0)
{
<div>
<select class="form-control" @onchange="(e => FolderChanged(e))">
@if (string.IsNullOrEmpty(Folder))
{
<option value="-1">&lt;@Localizer["Select Folder"]&gt;</option>
}
@foreach (Folder folder in _folders)
{
if (folder.FolderId == FolderId)
<div class="container-fluid px-0">
@if (ShowFolders)
{
<div class="row">
<div class="col">
<select class="form-select" value="@FolderId" @onchange="(e => FolderChanged(e))">
<option value="-1">&lt;@Localizer["Folder.Select"]&gt;</option>
@foreach (Folder folder in _folders)
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
</select>
</div>
</div>
}
@if (ShowFiles)
{
<div class="row mt-1">
<div class="col">
<select class="form-select" value="@FileId" @onchange="(e => FileChanged(e))">
<option value="-1">&lt;@Localizer["File.Select"]&gt;</option>
@foreach (File file in _files)
{
<option value="@(file.FileId)">@(file.Name)</option>
}
</select>
</div>
</div>
}
@if (ShowUpload && _haseditpermission)
{
<div class="row">
<div class="col mt-2">
@if (UploadMultiple)
{
<option value="@(folder.FolderId)" selected>@(new string('-', folder.Level * 2))@(folder.Name)</option>
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
}
else
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
}
}
</select>
</div>
}
@if (ShowFiles)
{
<div>
<select class="form-control" @onchange="(e => FileChanged(e))">
<option value="-1">&lt;@Localizer["Select File"]&gt;</option>
@foreach (File file in _files)
{
if (file.FileId == FileId)
</div>
<div class="col mt-2 text-center">
<button type="button" class="btn btn-success" @onclick="UploadFile">@SharedLocalizer["Upload"]</button>
@if (ShowFiles && GetFileId() != -1)
{
<option value="@(file.FileId)" selected>@(file.Name)</option>
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
}
else
{
<option value="@(file.FileId)">@(file.Name)</option>
}
}
</select>
</div>
}
@if (ShowUpload && _haseditpermission)
{
<div>
@if (UploadMultiple)
{
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
}
else
{
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
}
<span id="@_progressinfoid"></span><progress id="@_progressbarid" style="width: 150px; visibility: hidden;"></progress>
<span class="float-right">
<button type="button" class="btn btn-success" @onclick="UploadFile">@Localizer["Upload"]</button>
@if (ShowFiles && GetFileId() != -1)
{
<button type="button" class="btn btn-danger" @onclick="DeleteFile">@Localizer["Delete"]</button>
}
</span>
</div>
}
<ModuleMessage Message="@_message" Type="@_messagetype"></ModuleMessage>
</div>
</div>
<div class="row">
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
<div class="col text-center mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
</div>
}
</div>
</div>
@if (_image != string.Empty)
{
@ -80,6 +74,14 @@
</div>
}
</div>
@if (!string.IsNullOrEmpty(_message))
{
<div class="row mt-1">
<div class="col">
<ModuleMessage Message="@_message" Type="@_messagetype" />
</div>
</div>
}
</div>
}
@ -93,6 +95,7 @@
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;
@ -101,10 +104,16 @@
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
[Parameter]
public string Folder { get; set; } // optional - for setting a specific folder by default
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 folderid by default
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 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
@ -116,14 +125,23 @@
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter]
public int FileId { get; set; } = -1; // optional - for setting a specific file by default
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
[Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
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 UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
[Parameter]
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
[Parameter]
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
protected override async Task OnInitializedAsync()
{
if (!string.IsNullOrEmpty(Id))
@ -131,14 +149,35 @@
_id = Id;
}
if (!string.IsNullOrEmpty(Folder))
// packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder)
{
_folders = new List<Folder> { new Folder { FolderId = -1, Name = Folder } };
FolderId = -1;
ShowFiles = false;
ShowFolders = false;
Filter = "nupkg";
ShowSuccess = true;
}
else
if (!ShowFiles)
{
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
ShowImage = false;
}
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
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)
@ -147,12 +186,16 @@
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;
}
}
await SetImage();
if (!string.IsNullOrEmpty(Filter))
@ -172,10 +215,10 @@
private async Task GetFiles()
{
_haseditpermission = false;
if (!string.IsNullOrEmpty(Folder))
if (Folder == Constants.PackagesFolder)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = await FileService.GetFilesAsync(Folder);
_files = new List<File>();
}
else
{
@ -213,14 +256,14 @@
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 Loading Files"];
_message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error;
}
}
@ -229,6 +272,10 @@
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
if (FileId != -1)
{
await OnSelect.InvokeAsync(FileId);
}
await SetImage();
StateHasChanged();
@ -237,21 +284,22 @@
private async Task SetImage()
{
_image = string.Empty;
_file = null;
if (FileId != -1)
{
File file = await FileService.GetFileAsync(FileId);
if (file != null && file.ImageHeight != 0 && file.ImageWidth != 0)
_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 ratioX = (double)maxwidth / (double)_file.ImageWidth;
var ratioY = (double)maxheight / (double)_file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY;
_image = "<img src=\"" + ContentUrl(FileId) + "\" 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() + "\" />";
}
}
}
@ -266,7 +314,7 @@
try
{
string result;
if (!string.IsNullOrEmpty(Folder))
if (Folder == Constants.PackagesFolder)
{
result = await FileService.UploadFilesAsync(Folder, upload, _guid);
}
@ -278,20 +326,20 @@
if (result == string.Empty)
{
await logger.LogInformation("File Upload Succeeded {Files}", upload);
_message = Localizer["File Upload Succeeded"];
_messagetype = MessageType.Success;
await GetFiles();
if (upload.Length == 1)
if (ShowSuccess)
{
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
}
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
// set FileId to first file in upload collection
await GetFiles();
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
StateHasChanged();
}
@ -299,7 +347,7 @@
{
await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", "));
_message = Localizer["File Upload Failed"];
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
}
@ -307,13 +355,13 @@
{
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["File Upload Failed"];
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
}
else
{
_message = Localizer["You Have Not Selected A File To Upload"];
_message = Localizer["Message.File.NotSelected"];
_messagetype = MessageType.Warning;
}
}
@ -325,8 +373,9 @@
{
await FileService.DeleteFileAsync(FileId);
await logger.LogInformation("File Deleted {File}", FileId);
await OnDelete.InvokeAsync(FileId);
_message = Localizer["File Deleted"];
_message = Localizer["Success.File.Delete"];
_messagetype = MessageType.Success;
await GetFiles();
@ -338,11 +387,14 @@
{
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
_message = Localizer["Error Deleting File"];
_message = Localizer["Error.File.Delete"];
_messagetype = MessageType.Error;
}
}
public int GetFileId() => FileId;
public int GetFolderId() => FolderId;
public File GetFile() => _file;
}

View File

@ -3,17 +3,16 @@
@if (!string.IsNullOrEmpty(HelpText))
{
<span class="app-tooltip" data-tip="@((MarkupString)HelpText)">@((MarkupString)_openLabel)@ChildContent@((MarkupString)_closeLabel) <img src="images/help.png" /></span>
<span class="@_spanclass" data-tip="@((MarkupString)@_helptext)">
<label for="@For" class="@_labelclass">@ChildContent</label> <img src="images/help.png" />
</span>
}
else
{
@((MarkupString)_openLabel)@ChildContent@((MarkupString)_closeLabel)
<label for="@For" class="@_labelclass">@ChildContent</label>
}
@code {
private string _openLabel = string.Empty;
private string _closeLabel = "</label>";
[Parameter]
public RenderFragment ChildContent { get; set; }
@ -21,34 +20,33 @@ else
public string For { get; set; } // optional - the id of the associated input control for accessibility
[Parameter]
public string Class { get; set; } // optional - the class for the label ( ie. control-label )
public string Class { get; set; } // optional - CSS classes
[Parameter]
public string HelpText { get; set; } // optional - tooltip for this label
private string _spanclass = "app-tooltip";
private string _labelclass = "form-label";
private string _helptext = string.Empty;
protected override void OnParametersSet()
{
base.OnParametersSet();
_openLabel = "<label";
if (!string.IsNullOrEmpty(For))
if (!string.IsNullOrEmpty(HelpText))
{
_openLabel += " for=\"" + For + "\"";
_helptext = Localize(nameof(HelpText), HelpText);
_spanclass += (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
}
if (!string.IsNullOrEmpty(Class))
else
{
_openLabel += " class=\"" + Class + "\"";
_labelclass += (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
}
_openLabel += ">";
var text = Localize("Text", String.Empty);
if (text != String.Empty)
if (!string.IsNullOrEmpty(text))
{
ChildContent =@<text>@text</text>;
}
HelpText = Localize(nameof(HelpText), HelpText);
}
}

View File

@ -4,14 +4,14 @@
@if (!string.IsNullOrEmpty(_message))
{
<div class="@_classname" role="alert">
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message)
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
@((MarkupString)"&nbsp;&nbsp;")<NavLink href="@NavigateUrl("admin/log")">View Details</NavLink>
}
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
</div>
<br />
}
@code {
@ -54,4 +54,10 @@
return classname;
}
private void DismissModal()
{
_message = "";
StateHasChanged();
}
}

View File

@ -1,46 +1,58 @@
@namespace Oqtane.Modules.Controls
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@typeparam TableItem
<p>
@if (Toolbar == "Top")
@if (ItemList != null)
{
@if (Toolbar == "Top" && _pages > 0 && Items.Count() > _maxItems)
{
<div class="mx-auto text-center">
@if (_endPage > 1)
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="first" aria-hidden="true"></span></button>
<li class="page-item@((_page > _displayPages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
@if (_page > _maxPages)
<li class="page-item@((_page > 1) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("back"))><span class="oi oi-media-skip-backward" title="back" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button>
@for (int i = _startPage; i <= _endPage; i++)
var pager = i;
if (pager == _page)
{
var pager = i;
<button class="btn @((pager == _page) ? "btn-primary" : "btn-link")" @onclick=@(async () => UpdateList(pager))>
@pager
</button>
<li class="page-item active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></button>
}
@if (_endPage < _pages)
<li class="page-item@((_page < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("forward"))><span class="oi oi-media-skip-forward" title="forward" aria-hidden="true"></span></button>
<li class="page-item@((_endPage < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="last" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<span class="btn btn-link disabled">Page @_page of @_pages</span>
}
</div>
<li class="page-item@((_page < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
</li>
</ul>
}
@if (Format == "Table")
@if (Format == "Table" && Row != null)
{
<table class="@Class">
<thead>
@ -58,90 +70,125 @@
</tbody>
</table>
}
@if (Format == "Grid")
@if (Format == "Grid" && Row != null)
{
int count = 0;
if (ItemList != null)
{
count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns;
}
<div class="@Class">
<div class="row">@Header</div>
@foreach (var item in ItemList)
@if (Header != null)
{
<div class="row">@Row(item)</div>
@if (Detail != null)
{
<div class="row">@Detail(item)</div>
}
<div class="row"><div class="col">@Header</div></div>
}
@for (int row = 0; row < (count / _columns); row++)
{
<div class="row">
@for (int col = 0; col < _columns; col++)
{
int index = (row * _columns) + col;
if (index < ItemList.Count())
{
<div class="col">@Row(ItemList.ElementAt(index))</div>
}
else
{
<div class="col">&nbsp;</div>
}
}
</div>
}
</div>
}
@if (Toolbar == "Bottom")
@if (Toolbar == "Bottom" && _pages > 0 && Items.Count() > _maxItems)
{
<div class="mx-auto text-center">
@if (_endPage > 1)
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="first" aria-hidden="true"></span></button>
<li class="page-item@((_page > _displayPages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
@if (_page > _maxPages)
<li class="page-item@((_page > 1) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("back"))><span class="oi oi-media-skip-backward" title="back" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button>
@for (int i = _startPage; i <= _endPage; i++)
var pager = i;
if (pager == _page)
{
var pager = i;
<button class="btn @((pager == _page) ? "btn-primary" : "btn-link")" @onclick=@(async () => UpdateList(pager))>
@pager
</button>
<li class="page-item active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></button>
}
@if (_endPage < _pages)
<li class="page-item@((_page < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("forward"))><span class="oi oi-media-skip-forward" title="forward" aria-hidden="true"></span></button>
<li class="page-item@((_endPage < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="last" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<span class="btn btn-link disabled">Page @_page of @_pages</span>
}
</div>
<li class="page-item@((_page < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link">Page @_page of @_pages</a>
</li>
</ul>
}
</p>
}
@code {
private int _pages = 0;
private int _page = 1;
private int _maxItems;
private int _maxPages;
private int _startPage;
private int _endPage;
private int _maxItems = 10;
private int _displayPages = 5;
private int _startPage = 0;
private int _endPage = 0;
private int _columns = 1;
[Parameter]
public string Format { get; set; }
public string Format { get; set; } // Table or Grid
[Parameter]
public string Toolbar { get; set; }
public string Toolbar { get; set; } // Top or Bottom
[Parameter]
public RenderFragment Header { get; set; }
public RenderFragment Header { get; set; } = null;
[Parameter]
public RenderFragment<TableItem> Row { get; set; }
public RenderFragment<TableItem> Row { get; set; } = null;
[Parameter]
public RenderFragment<TableItem> Detail { get; set; }
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
[Parameter]
public IEnumerable<TableItem> Items { get; set; }
public IEnumerable<TableItem> Items { get; set; } // the IEnumerable data source
[Parameter]
public string PageSize { get; set; }
public string PageSize { get; set; } // number of items to display on a page
[Parameter]
public string DisplayPages { get; set; }
public string Columns { get; set; } // only applicable to Grid layouts
[Parameter]
public string CurrentPage { get; set; } // optional property to set the initial page to display
[Parameter]
public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection
[Parameter]
public string Class { get; set; }
@ -168,99 +215,98 @@
}
else
{
Class = "container";
Class = "container-fluid px-0";
}
}
if (string.IsNullOrEmpty(PageSize))
{
_maxItems = 10;
}
else
if (!string.IsNullOrEmpty(PageSize))
{
_maxItems = int.Parse(PageSize);
}
if (string.IsNullOrEmpty(DisplayPages))
if (!string.IsNullOrEmpty(Columns))
{
_maxPages = 5;
_columns = int.Parse(Columns);
}
if (!string.IsNullOrEmpty(DisplayPages))
{
_displayPages = int.Parse(DisplayPages);
}
if (!string.IsNullOrEmpty(CurrentPage))
{
_page = int.Parse(CurrentPage);
}
else
{
_maxPages = int.Parse(DisplayPages);
_page = 1;
}
_startPage = 0;
_endPage = 0;
if (Items != null)
{
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
if (_page > _pages)
{
_page = _pages;
}
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
SetPagerSize();
}
SetPagerSize("forward");
}
public void UpdateList(int currentPage)
public void SetPagerSize()
{
ItemList = Items.Skip((currentPage - 1) * _maxItems).Take(_maxItems);
_page = currentPage;
_startPage = ((_page - 1) / _displayPages) * _displayPages + 1;
_endPage = _startPage + _displayPages - 1;
if (_endPage > _pages)
{
_endPage = _pages;
}
StateHasChanged();
}
public void SetPagerSize(string direction)
public void UpdateList(int page)
{
if (direction == "forward")
{
if (_endPage + 1 < _pages)
{
_startPage = _endPage + 1;
}
else
{
_startPage = 1;
}
ItemList = Items.Skip((page - 1) * _maxItems).Take(_maxItems);
_page = page;
SetPagerSize();
}
if (_endPage + _maxPages < _pages)
{
_endPage = _startPage + _maxPages - 1;
}
else
{
_endPage = _pages;
}
StateHasChanged();
}
else if (direction == "back")
public void SkipPages(string direction)
{
switch (direction)
{
_endPage = _startPage - 1;
_startPage = _startPage - _maxPages;
case "forward":
_page = _endPage + 1;
break;
case "back":
_page = _startPage - 1;
break;
}
SetPagerSize();
}
public void NavigateToPage(string direction)
{
if (direction == "next")
switch (direction)
{
if (_page < _pages)
{
if (_page == _endPage)
case "next":
if (_page < _pages)
{
SetPagerSize("forward");
_page += 1;
}
_page += 1;
}
}
else if (direction == "previous")
{
if (_page > 1)
{
if (_page == _startPage)
break;
case "previous":
if (_page > 1)
{
SetPagerSize("back");
_page -= 1;
}
_page -= 1;
}
break;
}
UpdateList(_page);

View File

@ -2,77 +2,101 @@
@inherits ModuleControlBase
@inject IRoleService RoleService
@inject IUserService UserService
@inject IStringLocalizer<PermissionGrid> Localizer
@inject IStringLocalizer<PermissionGrid> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_permissions != null)
{
<br />
<table class="table" style="width: 50%; min-width: 250px;">
<tbody>
<tr>
<th scope="col">@Localizer["Role"]</th>
@foreach (PermissionString permission in _permissions)
{
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th>
}
</tr>
@foreach (Role role in _roles)
{
<tr>
<td>@role.Name</td>
@foreach (PermissionString permission in _permissions)
{
var p = permission;
<td style="text-align: center;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled=@GetPermissionDisabled(role.Name) OnChange="@(e => PermissionChanged(e, p.PermissionName, role.Name))" />
</td>
}
</tr>
}
</tbody>
</table>
@if (_users.Count != 0)
{
<table class="table" style="width: 50%; min-width: 250px;">
<thead>
<tr>
<th scope="col">@Localizer["User"]</th>
@foreach (PermissionString permission in _permissions)
{
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th>
}
</tr>
</thead>
<tbody>
@foreach (User user in _users)
{
string userid = "[" + user.UserId.ToString() + "]";
<div class="container">
<div class="row">
<div class="col">
<table class="table table-borderless">
<tbody>
<tr>
<td>@user.DisplayName</td>
<th scope="col">@Localizer["Role"]</th>
@foreach (PermissionString permission in _permissions)
{
var p = permission;
<td style="text-align: center; width: 1px;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled=false OnChange="@(e => PermissionChanged(e, p.PermissionName, userid))" />
</td>
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th>
}
</tr>
}
</tbody>
</table>
}
<table class="table" style="width: 50%; min-width: 250px;">
<tbody>
<tr>
<td class="input-group">
<input type="text" name="Username" class="form-control" placeholder="@Localizer["Enter Username"]" @bind="@_username" />
<button type="button" class="btn btn-primary" @onclick="AddUser">@Localizer["Add"]</button>
</td>
</tr>
</tbody>
</table>
<br />
<ModuleMessage Type="MessageType.Error" Message="@_message" />
@foreach (Role role in _roles)
{
<tr>
<td>@role.Name</td>
@foreach (PermissionString permission in _permissions)
{
var p = permission;
<td style="text-align: center;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled=@GetPermissionDisabled(role.Name) OnChange="@(e => PermissionChanged(e, p.PermissionName, role.Name))" />
</td>
}
</tr>
}
</tbody>
</table>
<br />
</div>
</div>
<div class="row">
<div class="col">
@if (_users.Count != 0)
{
<div class="row">
<div class="col">
</div>
</div>
<table class="table table-borderless">
<thead>
<tr>
<th scope="col">@Localizer["User"]</th>
@foreach (PermissionString permission in _permissions)
{
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th>
}
</tr>
</thead>
<tbody>
@foreach (User user in _users)
{
string userid = "[" + user.UserId.ToString() + "]";
<tr>
<td>@user.DisplayName</td>
@foreach (PermissionString permission in _permissions)
{
var p = permission;
<td style="text-align: center; width: 1px;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled=false OnChange="@(e => PermissionChanged(e, p.PermissionName, userid))" />
</td>
}
</tr>
}
</tbody>
</table>
<br />
}
</div>
</div>
<div class="row">
<div class="col">
<table class="table table-borderless">
<tbody>
<tr>
<td class="input-group">
<input type="text" name="Username" class="form-control" placeholder="@Localizer["Username.Enter"]" @bind="@_username" />
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
</td>
</tr>
</tbody>
</table>
<br />
</div>
</div>
<div class="row">
<div class="col">
<ModuleMessage Type="MessageType.Error" Message="@_message" />
</div>
</div>
</div>
}
@code {
@ -180,7 +204,7 @@
}
catch
{
_message = Localizer["Username Does Not Exist"];
_message = Localizer["Message.Username.DontExist"];
}
}

View File

@ -6,21 +6,24 @@
<div class="col">
<TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor">
@if (_filemanagervisible)
@if (AllowFileManagement)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="row justify-content-center" style="margin-bottom: 20px;">
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["Synchronize Content"]</button>&nbsp;&nbsp;
<button type="button" class="btn btn-primary" @onclick="InsertImage">@Localizer["Insert Image"]</button>
@if (_filemanagervisible)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseFileManager">@Localizer["Close"]</button>
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
</div>
<div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>&nbsp;&nbsp;
<button type="button" class="btn btn-primary" @onclick="InsertImage">@Localizer["InsertImage"]</button>
@if (_filemanagervisible)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseFileManager">@Localizer["Close"]</button>
}
</div>
}
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@ -63,8 +66,8 @@
</div>
</TabPanel>
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
<div class="row justify-content-center" style="margin-bottom: 20px;">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["Synchronize Content"]</button>
<div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>
</div>
@if (ReadOnly)
{
@ -107,6 +110,9 @@
[Parameter]
public string DebugLevel { get; set; } = "info";
[Parameter]
public bool AllowFileManagement { get; set; } = true;
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill1.3.6.min.js" },
@ -137,6 +143,8 @@
await interop.LoadEditorContent(_editorElement, Content);
_content = Content; // raw HTML
// preserve a copy of the rich text content ( Quill sanitizes content so we need to retrieve it from the editor )
_original = await interop.GetHtml(_editorElement);
}
@ -185,16 +193,16 @@
_message = string.Empty;
if (_filemanagervisible)
{
var fileid = _fileManager.GetFileId();
if (fileid != -1)
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new RichTextEditorInterop(JSRuntime);
await interop.InsertImage(_editorElement, ContentUrl(fileid));
await interop.InsertImage(_editorElement, file.Url, file.Name);
_filemanagervisible = false;
}
else
{
_message = Localizer["You Must Select An Image To Insert"];
_message = Localizer["Message.Require.Image"];
}
}
else

View File

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Threading.Tasks;
@ -105,13 +105,13 @@ namespace Oqtane.Modules.Controls
}
}
public Task InsertImage(ElementReference quillElement, string imageUrl)
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText)
{
try
{
_jsRuntime.InvokeAsync<object>(
"Oqtane.RichTextEditor.insertQuillImage",
quillElement, imageUrl);
quillElement, imageUrl, altText);
return Task.CompletedTask;
}
catch

View File

@ -1,14 +1,14 @@
@namespace Oqtane.Modules.Controls
@inherits LocalizableComponent
<div class="d-flex">
<div class="d-flex mt-2">
<div>
<a data-toggle="collapse" class="app-link-unstyled" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
<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="ml-auto">
<a data-toggle="collapse" class="app-link-unstyled float-right" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
<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>

View File

@ -30,23 +30,15 @@ else
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to specify SecurityAccessLevel
protected override void OnInitialized()
{
base.OnInitialized();
Parent.AddTabPanel((TabPanel)this);
}
protected override void OnParametersSet()
{
base.OnParametersSet();
Parent.AddTabPanel((TabPanel)this);
if (string.IsNullOrEmpty(Heading))
{
Name = Localize(nameof(Name), Name);
}
else
{
Heading = Localize(nameof(Heading), Heading);
Heading = Localize(nameof(Name), Name);
}
}

View File

@ -7,23 +7,20 @@
<ul class="nav nav-tabs" role="tablist">
@foreach (TabPanel tabPanel in _tabPanels)
{
@if (IsAuthorized(tabPanel))
{
<li class="nav-item">
@if (tabPanel.Name == ActiveTab)
{
<a class="nav-link active" data-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()
</a>
}
else
{
<a class="nav-link" data-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()
</a>
}
</li>
}
<li class="nav-item" @key="tabPanel.Name">
@if (tabPanel.Name == ActiveTab)
{
<a class="nav-link active" data-bs-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()
</a>
}
else
{
<a class="nav-link" data-bs-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()
</a>
}
</li>
}
</ul>
<div class="tab-content">
@ -35,7 +32,7 @@
</CascadingValue>
@code {
private List<TabPanel> _tabPanels = new List<TabPanel>();
private List<TabPanel> _tabPanels;
[Parameter]
public RenderFragment ChildContent { get; set; } // contains the TabPanels
@ -43,6 +40,9 @@
[Parameter]
public string ActiveTab { get; set; } // optional - defaults to first TabPanel if not specified. Can also be set using a "tab=" querystring parameter.
[Parameter]
public bool Refresh { get; set; } // optional - used in scenarios where TabPanels are added/removed dynamically within a parent form. ActiveTab may need to be reset as well when this property is used.
protected override void OnInitialized()
{
if (PageState.QueryString.ContainsKey("tab"))
@ -51,14 +51,25 @@
}
}
protected override void OnParametersSet()
{
if (_tabPanels == null || Refresh)
{
_tabPanels = new List<TabPanel>();
}
}
internal void AddTabPanel(TabPanel tabPanel)
{
_tabPanels.Add(tabPanel);
if (!_tabPanels.Exists(item => item.Name == tabPanel.Name) && IsAuthorized(tabPanel))
{
_tabPanels.Add(tabPanel);
StateHasChanged();
}
if (string.IsNullOrEmpty(ActiveTab))
{
ActiveTab = tabPanel.Name;
}
StateHasChanged();
}
private bool IsAuthorized(TabPanel tabPanel)

View File

@ -52,11 +52,11 @@
{
case true:
_src = "images/checked.png";
_title = Localizer["Permission Granted"];
_title = Localizer["PermissionGranted"];
break;
case false:
_src = "images/unchecked.png";
_title = Localizer["Permission Denied"];
_title = Localizer["PermissionDenied"];
break;
case null:
_src = "images/null.png";

View File

@ -4,14 +4,16 @@
@namespace Oqtane.Modules.HtmlText
@inherits ModuleBase
@inject IHtmlTextService HtmlTextService
@inject ISettingService SettingService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_content != null)
{
<RichTextEditor Content="@_content" @ref="@RichTextEditorHtml"></RichTextEditor>
<button type="button" class="btn btn-success" @onclick="SaveContent">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" @ref="@RichTextEditorHtml"></RichTextEditor>
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@if (!string.IsNullOrEmpty(_content))
{
<br />
@ -26,13 +28,14 @@
public override string Title => "Edit Html/Text";
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/quill1.3.6.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.snow.css" }
};
private RichTextEditor RichTextEditorHtml;
private bool _allowfilemanagement;
private string _content = null;
private string _createdby;
private DateTime _createdon;
@ -43,11 +46,13 @@
{
try
{
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
_content = htmltext.Content;
_content = _content.Replace(Constants.ContentUrl, "/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl);
_content = Utilities.FormatContent(_content, PageState.Alias, "render");
_createdby = htmltext.CreatedBy;
_createdon = htmltext.CreatedOn;
_modifiedby = htmltext.ModifiedBy;
@ -68,7 +73,7 @@
private async Task SaveContent()
{
string content = await RichTextEditorHtml.GetHtml();
content = content.Replace("/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl, Constants.ContentUrl);
content = Utilities.FormatContent(content, PageState.Alias, "save");
try
{
@ -80,7 +85,7 @@
}
else
{
htmltext = new HtmlTextInfo();
htmltext = new HtmlText();
htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content;
await HtmlTextService.AddHtmlTextAsync(htmltext);
@ -92,7 +97,7 @@
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error Saving Content"], MessageType.Error);
AddModuleMessage(Localizer["Error.Content.Save"], MessageType.Error);
}
}
}

View File

@ -2,12 +2,16 @@
@namespace Oqtane.Modules.HtmlText
@inherits ModuleBase
@inject IHtmlTextService HtmlTextService
@inject IStringLocalizer<Index> Localizer
@((MarkupString)content)
@if (PageState.EditMode)
{
<br /><ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" /><br /><br />
<br />
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
<br />
<br />
}
@code {
@ -26,7 +30,7 @@
if (htmltext != null)
{
content = htmltext.Content;
content = content.Replace(Constants.ContentUrl, "/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl);
content = Utilities.FormatContent(content, PageState.Alias, "render");
}
}
catch (Exception ex)

View File

@ -1,4 +1,4 @@
using Oqtane.Models;
using Oqtane.Models;
namespace Oqtane.Modules.HtmlText
{
@ -8,9 +8,10 @@ namespace Oqtane.Modules.HtmlText
{
Name = "HtmlText",
Description = "Renders HTML or Text Content",
Version = "1.0.0",
Version = "1.0.1",
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
ReleaseVersions = "1.0.0"
ReleaseVersions = "1.0.0,1.0.1",
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"
};
}
}

View File

@ -1,8 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Modules.HtmlText.Models;
using Oqtane.Services;
using Oqtane.Shared;
@ -10,34 +7,28 @@ namespace Oqtane.Modules.HtmlText.Services
{
public class HtmlTextService : ServiceBase, IHtmlTextService, IService
{
private readonly SiteState _siteState;
public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {}
public HtmlTextService(HttpClient http, SiteState siteState) : base(http)
private string ApiUrl => CreateApiUrl("HtmlText");
public async Task<Models.HtmlText> GetHtmlTextAsync(int moduleId)
{
_siteState = siteState;
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", EntityNames.Module, moduleId));
}
private string ApiUrl => CreateApiUrl(_siteState.Alias, "HtmlText");
public async Task<HtmlTextInfo> GetHtmlTextAsync(int moduleId)
public async Task AddHtmlTextAsync(Models.HtmlText htmlText)
{
var htmltext = await GetJsonAsync<List<HtmlTextInfo>>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", moduleId));
return htmltext.FirstOrDefault();
await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);
}
public async Task AddHtmlTextAsync(HtmlTextInfo htmlText)
public async Task UpdateHtmlTextAsync(Models.HtmlText htmlText)
{
await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", htmlText.ModuleId), htmlText);
}
public async Task UpdateHtmlTextAsync(HtmlTextInfo htmlText)
{
await PutJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlText.HtmlTextId}", htmlText.ModuleId), htmlText);
await PutJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlText.HtmlTextId}", EntityNames.Module, htmlText.ModuleId), htmlText);
}
public async Task DeleteHtmlTextAsync(int moduleId)
{
await DeleteAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", moduleId));
await DeleteAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", EntityNames.Module, moduleId));
}
}
}

View File

@ -6,11 +6,11 @@ namespace Oqtane.Modules.HtmlText.Services
{
public interface IHtmlTextService
{
Task<HtmlTextInfo> GetHtmlTextAsync(int ModuleId);
Task<Models.HtmlText> GetHtmlTextAsync(int ModuleId);
Task AddHtmlTextAsync(HtmlTextInfo htmltext);
Task AddHtmlTextAsync(Models.HtmlText htmltext);
Task UpdateHtmlTextAsync(HtmlTextInfo htmltext);
Task UpdateHtmlTextAsync(Models.HtmlText htmltext);
Task DeleteHtmlTextAsync(int ModuleId);
}

View File

@ -0,0 +1,48 @@
@namespace Oqtane.Modules.HtmlText
@inherits ModuleBase
@inject ISettingService SettingService
@implements Oqtane.Interfaces.ISettingsControl
@inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="files" ResourceKey="Allow File Management" HelpText="Specify If Editors Can Upload and Select Files">Allow File Management: </Label>
<div class="col-sm-9">
<select id="files" class="form-select" @bind="@_allowfilemanagement">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
@code {
private string _allowfilemanagement;
protected override void OnInitialized()
{
try
{
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
}
public async Task UpdateSettings()
{
try
{
var settings = ModuleState.Settings;
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

@ -30,8 +30,8 @@ namespace Oqtane.Modules
[CascadingParameter]
protected Module ModuleState { get; set; }
[CascadingParameter]
protected ModuleInstance ModuleInstance { get; set; }
[Parameter]
public ModuleInstance ModuleInstance { get; set; }
// optional interface properties
public virtual SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } set { } } // default security
@ -84,11 +84,21 @@ namespace Oqtane.Modules
return NavigateUrl(path, "");
}
public string NavigateUrl(bool refresh)
{
return NavigateUrl(PageState.Page.Path, refresh);
}
public string NavigateUrl(string path, string parameters)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);
}
public string NavigateUrl(string path, bool refresh)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, refresh ? "refresh" : "");
}
public string EditUrl(string action)
{
return EditUrl(ModuleState.ModuleId, action);
@ -124,6 +134,11 @@ namespace Oqtane.Modules
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment);
}
public string ImageUrl(int fileid, int width, int height, string mode)
{
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode);
}
public virtual Dictionary<string, string> GetUrlParameters(string parametersTemplate = "")
{
var urlParameters = new Dictionary<string, string>();
@ -195,6 +210,38 @@ namespace Oqtane.Modules
// logging methods
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
{
LogFunction logFunction;
if (string.IsNullOrEmpty(function))
{
// try to infer from page action
function = PageState.Action;
}
if (!Enum.TryParse(function, out logFunction))
{
switch (function.ToLower())
{
case "add":
logFunction = LogFunction.Create;
break;
case "edit":
logFunction = LogFunction.Update;
break;
case "delete":
logFunction = LogFunction.Delete;
break;
case "":
logFunction = LogFunction.Read;
break;
default:
logFunction = LogFunction.Other;
break;
}
}
await Log(alias, level, logFunction, exception, message, args);
}
public async Task Log(Alias alias, LogLevel level, LogFunction function, Exception exception, string message, params object[] args)
{
int pageId = ModuleState.PageId;
int moduleId = ModuleState.ModuleId;
@ -205,34 +252,8 @@ namespace Oqtane.Modules
}
string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1);
LogFunction logFunction;
if (string.IsNullOrEmpty(function))
{
function = PageState.Action;
}
switch (function.ToLower())
{
case "add":
logFunction = LogFunction.Create;
break;
case "edit":
logFunction = LogFunction.Update;
break;
case "delete":
logFunction = LogFunction.Delete;
break;
default:
logFunction = LogFunction.Read;
break;
}
if (feature == "Login")
{
logFunction = LogFunction.Security;
}
await LoggingService.Log(alias, pageId, moduleId, userId, category, feature, logFunction, level, exception, message, args);
await LoggingService.Log(alias, pageId, moduleId, userId, category, feature, function, level, exception, message, args);
}
public class Logger
@ -249,6 +270,11 @@ namespace Oqtane.Modules
await _moduleBase.Log(null, LogLevel.Trace, "", null, message, args);
}
public async Task LogTrace(LogFunction function, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Trace, function, null, message, args);
}
public async Task LogTrace(Exception exception, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Trace, "", exception, message, args);
@ -259,6 +285,11 @@ namespace Oqtane.Modules
await _moduleBase.Log(null, LogLevel.Debug, "", null, message, args);
}
public async Task LogDebug(LogFunction function, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Debug, function, null, message, args);
}
public async Task LogDebug(Exception exception, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Debug, "", exception, message, args);
@ -269,6 +300,11 @@ namespace Oqtane.Modules
await _moduleBase.Log(null, LogLevel.Information, "", null, message, args);
}
public async Task LogInformation(LogFunction function, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Information, function, null, message, args);
}
public async Task LogInformation(Exception exception, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Information, "", exception, message, args);
@ -279,6 +315,11 @@ namespace Oqtane.Modules
await _moduleBase.Log(null, LogLevel.Warning, "", null, message, args);
}
public async Task LogWarning(LogFunction function, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Warning, function, null, message, args);
}
public async Task LogWarning(Exception exception, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Warning, "", exception, message, args);
@ -289,6 +330,11 @@ namespace Oqtane.Modules
await _moduleBase.Log(null, LogLevel.Error, "", null, message, args);
}
public async Task LogError(LogFunction function, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Error, function, null, message, args);
}
public async Task LogError(Exception exception, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Error, "", exception, message, args);
@ -299,6 +345,11 @@ namespace Oqtane.Modules
await _moduleBase.Log(null, LogLevel.Critical, "", null, message, args);
}
public async Task LogCritical(LogFunction function, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Critical, function, null, message, args);
}
public async Task LogCritical(Exception exception, string message, params object[] args)
{
await _moduleBase.Log(null, LogLevel.Critical, "", exception, message, args);

View File

@ -1,42 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations>
<Version>2.0.1</Version>
<Version>3.0.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
<Description>Modular Application Framework for Blazor</Description>
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<RepositoryUrl>https://github.com/oqtane</RepositoryUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.1</PackageReleaseNotes>
<RootNamespace>Oqtane</RootNamespace>
<IsPackable>true</IsPackable>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Modules\Admin\ModuleCreator\Templates\**" />
<Content Remove="Modules\Admin\ModuleCreator\Templates\**" />
<EmbeddedResource Remove="Modules\Admin\ModuleCreator\Templates\**" />
<None Remove="Modules\Admin\ModuleCreator\Templates\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="5.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Resources\Themes\Controls\Theme\" />
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="System.Runtime" />
<TrimmerRootAssembly Include="System.Linq.Parallel" />
<TrimmerRootAssembly Include="System.Runtime.CompilerServices.VisualC" />
</ItemGroup>
<PropertyGroup>
<BlazorEnableCompression>false</BlazorEnableCompression>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinterfaces/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -8,13 +8,11 @@ using System.Net.Http;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Oqtane.Modules;
using Oqtane.Providers;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
@ -26,8 +24,8 @@ namespace Oqtane.Client
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
HttpClient httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
var httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
builder.Services.AddSingleton(httpClient);
builder.Services.AddOptions();
@ -36,77 +34,26 @@ namespace Oqtane.Client
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<IdentityAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
builder.Services.AddOqtaneAuthorization();
// register scoped core services
builder.Services.AddScoped<SiteState>();
builder.Services.AddScoped<IInstallationService, InstallationService>();
builder.Services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
builder.Services.AddScoped<IThemeService, ThemeService>();
builder.Services.AddScoped<IAliasService, AliasService>();
builder.Services.AddScoped<ITenantService, TenantService>();
builder.Services.AddScoped<ISiteService, SiteService>();
builder.Services.AddScoped<IPageService, PageService>();
builder.Services.AddScoped<IModuleService, ModuleService>();
builder.Services.AddScoped<IPageModuleService, PageModuleService>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IProfileService, ProfileService>();
builder.Services.AddScoped<IRoleService, RoleService>();
builder.Services.AddScoped<IUserRoleService, UserRoleService>();
builder.Services.AddScoped<ISettingService, SettingService>();
builder.Services.AddScoped<IPackageService, PackageService>();
builder.Services.AddScoped<ILogService, LogService>();
builder.Services.AddScoped<IJobService, JobService>();
builder.Services.AddScoped<IJobLogService, JobLogService>();
builder.Services.AddScoped<INotificationService, NotificationService>();
builder.Services.AddScoped<IFolderService, FolderService>();
builder.Services.AddScoped<IFileService, FileService>();
builder.Services.AddScoped<ISiteTemplateService, SiteTemplateService>();
builder.Services.AddScoped<ISqlService, SqlService>();
builder.Services.AddScoped<ISystemService, SystemService>();
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
builder.Services.AddScoped<ILanguageService, LanguageService>();
builder.Services.AddOqtaneScopedServices();
await LoadClientAssemblies(httpClient);
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// dynamically register module services
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
builder.Services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
// dynamically register module services
RegisterModuleServices(assembly, builder.Services);
// register client startup services
var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(builder.Services);
}
RegisterClientStartups(assembly, builder.Services);
}
var host = builder.Build();
var jsRuntime = host.Services.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime);
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie).UICultures[0].Value;
var localizationService = host.Services.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync();
if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase)))
{
culture = cultures.Single(c => c.IsDefault).Name;
}
SetCulture(culture);
await SetCultureFromLocalizationCookie(host.Services);
ServiceActivator.Configure(host.Services);
@ -115,11 +62,11 @@ namespace Oqtane.Client
private static async Task LoadClientAssemblies(HttpClient http)
{
// get list of loaded assemblies on the client
// get list of loaded assemblies on the client
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList();
// get assemblies from server and load into client app domain
var zip = await http.GetByteArrayAsync($"/~/api/Installation/load");
var zip = await http.GetByteArrayAsync($"/api/Installation/load");
// asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
@ -129,19 +76,19 @@ namespace Oqtane.Client
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (!assemblies.Contains(Path.GetFileNameWithoutExtension(entry.Name)))
if (!assemblies.Contains(Path.GetFileNameWithoutExtension(entry.FullName)))
{
using (var memoryStream = new MemoryStream())
{
entry.Open().CopyTo(memoryStream);
byte[] file = memoryStream.ToArray();
switch (Path.GetExtension(entry.Name))
switch (Path.GetExtension(entry.FullName))
{
case ".dll":
dlls.Add(entry.Name, file);
dlls.Add(entry.FullName, file);
break;
case ".pdb":
pdbs.Add(entry.Name, file);
pdbs.Add(entry.FullName, file);
break;
}
}
@ -162,6 +109,45 @@ namespace Oqtane.Client
}
}
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
{
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
}
private static void RegisterClientStartups(Assembly assembly, IServiceCollection services)
{
var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(services);
}
}
private static async Task SetCultureFromLocalizationCookie(IServiceProvider serviceProvider)
{
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime);
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync();
if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase)))
{
culture = cultures.Single(c => c.IsDefault).Name;
}
SetCulture(culture);
}
private static void SetCulture(string culture)
{
var cultureInfo = CultureInfo.GetCultureInfo(culture);

View File

@ -1,49 +1,42 @@
using System;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
using Oqtane.Services;
using Oqtane.Security;
using Oqtane.Shared;
namespace Oqtane.Providers
{
public class IdentityAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly NavigationManager _navigationManager;
private readonly SiteState _siteState;
private readonly IServiceProvider _serviceProvider;
public IdentityAuthenticationStateProvider(NavigationManager navigationManager, SiteState siteState, IServiceProvider serviceProvider)
private readonly NavigationManager _navigationManager;
public IdentityAuthenticationStateProvider(IServiceProvider serviceProvider, NavigationManager navigationManager)
{
_navigationManager = navigationManager;
_siteState = siteState;
_serviceProvider = serviceProvider;
_navigationManager = navigationManager;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
// get HttpClient lazily from IServiceProvider as you cannot use standard dependency injection due to the AuthenticationStateProvider being initialized prior to NavigationManager ( https://github.com/aspnet/AspNetCore/issues/11867 )
var http = _serviceProvider.GetRequiredService<HttpClient>();
string apiurl = "/~/api/User/authenticate";
User user = await http.GetFromJsonAsync<User>(apiurl);
ClaimsIdentity identity = new ClaimsIdentity();
// get HttpClient lazily from IServiceProvider as you cannot use standard dependency injection due to the AuthenticationStateProvider being initialized prior to NavigationManager(https://github.com/aspnet/AspNetCore/issues/11867 )
var http = _serviceProvider.GetRequiredService<HttpClient>();
var siteState = _serviceProvider.GetRequiredService<SiteState>();
User user = await http.GetFromJsonAsync<User>(Utilities.TenantUrl(siteState.Alias, "/api/User/authenticate"));
if (user.IsAuthenticated)
{
identity = new ClaimsIdentity("Identity.Application");
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
identity.AddClaim(new Claim(ClaimTypes.PrimarySid, user.UserId.ToString()));
foreach (string role in user.Roles.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
identity = UserSecurity.CreateClaimsIdentity(siteState.Alias, user);
}
return new AuthenticationState(new ClaimsPrincipal(identity));
}

View File

@ -0,0 +1,132 @@
<?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 xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<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="Server.Text" xml:space="preserve">
<value>Server:</value>
</data>
<data name="Server.HelpText" xml:space="preserve">
<value>Enter the database server</value>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database:</value>
</data>
<data name="Database.HelpText" xml:space="preserve">
<value>Enter the name of the database</value>
</data>
</root>

View File

@ -0,0 +1,150 @@
<?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 xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<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="Server.Text" xml:space="preserve">
<value>Server:</value>
</data>
<data name="Server.HelpText" xml:space="preserve">
<value>Enter the database server</value>
</data>
<data name="Port.Text" xml:space="preserve">
<value>Port:</value>
</data>
<data name="Port.HelpText" xml:space="preserve">
<value>Enter the port used to connect to the server</value>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database:</value>
</data>
<data name="Database.HelpText" xml:space="preserve">
<value>Enter the name of the database</value>
</data>
<data name="Uid.Text" xml:space="preserve">
<value>User Id:</value>
</data>
<data name="Uid.HelpText" xml:space="preserve">
<value>Enter the username to use for the database</value>
</data>
<data name="Pwd.Text" xml:space="preserve">
<value>Password:</value>
</data>
<data name="Pwd.HelpText" xml:space="preserve">
<value>Enter the password to use for the database</value>
</data>
</root>

View File

@ -0,0 +1,162 @@
<?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="Server.Text" xml:space="preserve">
<value>Server:</value>
</data>
<data name="Server.HelpText" xml:space="preserve">
<value>Enter the database server</value>
</data>
<data name="Port.Text" xml:space="preserve">
<value>Port:</value>
</data>
<data name="Port.HelpText" xml:space="preserve">
<value>Enter the port used to connect to the server</value>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database:</value>
</data>
<data name="Database.HelpText" xml:space="preserve">
<value>Enter the name of the database</value>
</data>
<data name="IntegratedSecurity.Text" xml:space="preserve">
<value>Integrated Security:</value>
</data>
<data name="IntegratedSecurity.HelpText" xml:space="preserve">
<value>Select if you want integrated security or not</value>
</data>
<data name="Uid.Text" xml:space="preserve">
<value>User Id:</value>
</data>
<data name="Uid.HelpText" xml:space="preserve">
<value>Enter the username to use for the database</value>
</data>
<data name="Pwd.Text" xml:space="preserve">
<value>Password:</value>
</data>
<data name="Pwd.HelpText" xml:space="preserve">
<value>Enter the password to use for the database</value>
</data>
<data name="Custom" xml:space="preserve">
<value>Custom</value>
</data>
<data name="Integrated" xml:space="preserve">
<value>Integrated</value>
</data>
</root>

View File

@ -0,0 +1,174 @@
<?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="Server.Text" xml:space="preserve">
<value>Server:</value>
</data>
<data name="Server.HelpText" xml:space="preserve">
<value>Enter the database server</value>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database:</value>
</data>
<data name="Database.HelpText" xml:space="preserve">
<value>Enter the name of the database</value>
</data>
<data name="IntegratedSecurity.Text" xml:space="preserve">
<value>Integrated Security:</value>
</data>
<data name="IntegratedSecurity.HelpText" xml:space="preserve">
<value>Select if you want integrated security or not</value>
</data>
<data name="Uid.Text" xml:space="preserve">
<value>User Id:</value>
</data>
<data name="Uid.HelpText" xml:space="preserve">
<value>Enter the username to use for the database</value>
</data>
<data name="Pwd.Text" xml:space="preserve">
<value>Password:</value>
</data>
<data name="Pwd.HelpText" xml:space="preserve">
<value>Enter the password to use for the database</value>
</data>
<data name="Custom" xml:space="preserve">
<value>Custom</value>
</data>
<data name="Integrated" xml:space="preserve">
<value>Integrated</value>
</data>
<data name="Encryption,Text" xml:space="preserve">
<value>Encryption:</value>
</data>
<data name="Encryption.HelpText" xml:space="preserve">
<value>Specify if you are using an encrypted database connection. It is highly recommended to use encryption in a production environment.</value>
</data>
<data name="Self Signed" xml:space="preserve">
<value>Self Signed</value>
</data>
<data name="TrustServerCertificate.HelpText" xml:space="preserve">
<value>Specify the type of certificate you are using for encryption</value>
</data>
<data name="TrustServerCertificate.Text" xml:space="preserve">
<value>Trust Server Certificate:</value>
</data>
<data name="Verifiable" xml:space="preserve">
<value>Verifiable</value>
</data>
</root>

View File

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

View File

@ -0,0 +1,168 @@
<?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="DatabaseConfig" xml:space="preserve">
<value>Database Configuration</value>
</data>
<data name="DatabaseType.Text" xml:space="preserve">
<value>Database:</value>
</data>
<data name="ApplicationAdmin" xml:space="preserve">
<value>Application Administrator</value>
</data>
<data name="InstallNow" xml:space="preserve">
<value>Install Now</value>
</data>
<data name="Error.DbConfig.Load" xml:space="preserve">
<value>Error loading Database Configuration Control</value>
</data>
<data name="Message.Require.DbInfo" xml:space="preserve">
<value>Please Enter All Required Fields. Ensure Passwords Match And Are Greater Than 5 Characters In Length. Ensure Email Address Provided Is Valid.</value>
</data>
<data name="Register" xml:space="preserve">
<value>Please Register Me For Major Product Updates And Security Bulletins</value>
</data>
<data name="Confirm.HelpText" xml:space="preserve">
<value>Please confirm the password entered above by entering it again</value>
</data>
<data name="Confirm.Text" xml:space="preserve">
<value>Confirm:</value>
</data>
<data name="DatabaseType.HelpText" xml:space="preserve">
<value>Select the type of database you wish to use</value>
</data>
<data name="Email.HelpText" xml:space="preserve">
<value>Provide the email address for the host user account</value>
</data>
<data name="Email.Text" xml:space="preserve">
<value>Email:</value>
</data>
<data name="Password.HelpText" xml:space="preserve">
<value>Provide a password for the primary user account</value>
</data>
<data name="Password.Text" xml:space="preserve">
<value>Password:</value>
</data>
<data name="Username.HelpText" xml:space="preserve">
<value>Provide a username for the primary user account</value>
</data>
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
</root>

View File

@ -0,0 +1,120 @@
<?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>
</root>

View File

@ -0,0 +1,123 @@
<?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="Error.Module.Load" xml:space="preserve">
<value>A Problem Was Encountered Loading Module {0}</value>
</data>
</root>

View File

@ -0,0 +1,165 @@
<?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="Folder.Select" xml:space="preserve">
<value>Select Folder</value>
</data>
<data name="Message.Required.UrlFolder" xml:space="preserve">
<value>You Must Enter A Url And Select A Folder</value>
</data>
<data name="Message.Download.InvalidExtension" xml:space="preserve">
<value>File Could Not Be Downloaded From Url Due To Its File Extension</value>
</data>
<data name="Message.Required.UrlName" xml:space="preserve">
<value>You Must Enter A Url With A Valid File Name</value>
</data>
<data name="Success.Download.File" xml:space="preserve">
<value>File Downloaded Successfully From Url</value>
</data>
<data name="Error.Download.InvalidUrl" xml:space="preserve">
<value>Error Downloading File From Url. Please Verify That The Url Is Valid.</value>
</data>
<data name="Upload.HelpText" xml:space="preserve">
<value>Upload the file you want</value>
</data>
<data name="Url.HelpText" xml:space="preserve">
<value>Enter the url of the file you wish to download</value>
</data>
<data name="Folder.HelpText" xml:space="preserve">
<value>Select the folder to save the file in</value>
</data>
<data name="Upload.Text" xml:space="preserve">
<value>Upload: </value>
</data>
<data name="Url.Text" xml:space="preserve">
<value>Url: </value>
</data>
<data name="Folder.Text" xml:space="preserve">
<value>Folder: </value>
</data>
<data name="UploadFiles.Heading" xml:space="preserve">
<value>Upload Files</value>
</data>
<data name="Name.HelpText" xml:space="preserve">
<value>Enter the name of the file being downloaded</value>
</data>
<data name="Name.Text" xml:space="preserve">
<value>Name:</value>
</data>
</root>

View File

@ -0,0 +1,153 @@
<?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="Folder.Text" xml:space="preserve">
<value>Folder: </value>
</data>
<data name="Error.File.Load" xml:space="preserve">
<value>Error Loading File</value>
</data>
<data name="Message.File.InvalidName" xml:space="preserve">
<value>File Name Not Valid</value>
</data>
<data name="Error.File.Save" xml:space="preserve">
<value>Error Saving File</value>
</data>
<data name="Name.HelpText" xml:space="preserve">
<value>The name of the file</value>
</data>
<data name="Folder.HelpText" xml:space="preserve">
<value>The folder where the file is located</value>
</data>
<data name="Size.HelpText" xml:space="preserve">
<value>The size of the file (in bytes)</value>
</data>
<data name="Name.Text" xml:space="preserve">
<value>Name: </value>
</data>
<data name="Size.Text" xml:space="preserve">
<value>Size: </value>
</data>
<data name="Description.HelpText" xml:space="preserve">
<value>A description of the file. This can be used as a caption for image files.</value>
</data>
<data name="Description.Text" xml:space="preserve">
<value>Description:</value>
</data>
</root>

View File

@ -0,0 +1,186 @@
<?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="Name.Text" xml:space="preserve">
<value>Name: </value>
</data>
<data name="NoParent" xml:space="preserve">
<value>No Parent</value>
</data>
<data name="Error.Folder.Load" xml:space="preserve">
<value>Error Loading Folder</value>
</data>
<data name="Message.Required.FolderParent" xml:space="preserve">
<value>Folders Must Have A Parent And A Name</value>
</data>
<data name="Message.Folder.InvalidName" xml:space="preserve">
<value>Folder Name Not Valid.</value>
</data>
<data name="Error.Folder.Save" xml:space="preserve">
<value>Error Saving Folder</value>
</data>
<data name="Message.Folder.Files.InvalidDelete" xml:space="preserve">
<value>Folder Has Files And Cannot Be Deleted</value>
</data>
<data name="Message.Folder.Subfolders.InvalidDelete" xml:space="preserve">
<value>Folder Has Subfolders And Cannot Be Deleted</value>
</data>
<data name="Error.Folder.Delete" xml:space="preserve">
<value>Error Deleting Folder</value>
</data>
<data name="Parent.HelpText" xml:space="preserve">
<value>Select the parent folder</value>
</data>
<data name="Name.HelpText" xml:space="preserve">
<value>Enter the folder name</value>
</data>
<data name="Permissions.HelpText" xml:space="preserve">
<value>Select the permissions you want for the folder</value>
</data>
<data name="Parent.Text" xml:space="preserve">
<value>Parent: </value>
</data>
<data name="Permissions.Text" xml:space="preserve">
<value>Permissions: </value>
</data>
<data name="DeleteFolder.Header" xml:space="preserve">
<value>Delete Folder</value>
</data>
<data name="DeleteFolder.Message" xml:space="preserve">
<value>Are You Sure You Wish To Delete This Folder?</value>
</data>
<data name="Type.HelpText" xml:space="preserve">
<value>Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users</value>
</data>
<data name="Type.Text" xml:space="preserve">
<value>Type:</value>
</data>
<data name="Capacity.HelpText" xml:space="preserve">
<value>Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited.</value>
</data>
<data name="Capacity.Text" xml:space="preserve">
<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>
</data>
<data name="ImageSizes.Text" xml:space="preserve">
<value>Image Sizes:</value>
</data>
</root>

View File

@ -0,0 +1,162 @@
<?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="Error.File.Load" xml:space="preserve">
<value>Error Loading Files</value>
</data>
<data name="UploadFiles.Heading" xml:space="preserve">
<value>Upload Files</value>
</data>
<data name="Confirm.File.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete {0}?</value>
</data>
<data name="NoFiles" xml:space="preserve">
<value>No Files Exist In Selected Folder</value>
</data>
<data name="Success.File.Delete" xml:space="preserve">
<value>File {0} Deleted</value>
</data>
<data name="Error.File.Delete" xml:space="preserve">
<value>Error Deleting File {0}</value>
</data>
<data name="EditFolder.Text" xml:space="preserve">
<value>Edit Folder</value>
</data>
<data name="AddFolder.Text" xml:space="preserve">
<value>Add Folder</value>
</data>
<data name="Details.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="DeleteFile.Header" xml:space="preserve">
<value>Delete File</value>
</data>
<data name="Folder" xml:space="preserve">
<value>Folder</value>
</data>
<data name="Modified" xml:space="preserve">
<value>Modified</value>
</data>
<data name="Size" xml:space="preserve">
<value>Size</value>
</data>
<data name="Type" xml:space="preserve">
<value>Type</value>
</data>
</root>

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